mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-13 10:11:20 +00:00
Merge pull request #42648 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
2
.github/workflows/linters.yml
vendored
2
.github/workflows/linters.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
||||||
|
|
||||||
- name: Download semgrep
|
- name: Download semgrep
|
||||||
run: pip install semgrep==0.97.0
|
run: pip install semgrep
|
||||||
|
|
||||||
- name: Run Semgrep rules
|
- name: Run Semgrep rules
|
||||||
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
|
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
|
||||||
|
|||||||
@@ -145,10 +145,27 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
filters: filters
|
filters: filters
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
refresh: function(frm) {
|
frm.set_query("sales_taxes_and_charges_template", function () {
|
||||||
erpnext.hide_company();
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("purchase_taxes_and_charges_template", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
refresh: function (frm) {
|
||||||
|
erpnext.hide_company(frm);
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
frm.events.set_dynamic_labels(frm);
|
frm.events.set_dynamic_labels(frm);
|
||||||
frm.events.show_general_ledger(frm);
|
frm.events.show_general_ledger(frm);
|
||||||
|
|||||||
@@ -343,12 +343,14 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
|||||||
AND ja.party in %s
|
AND ja.party in %s
|
||||||
AND j.apply_tds = 1
|
AND j.apply_tds = 1
|
||||||
AND j.tax_withholding_category = %s
|
AND j.tax_withholding_category = %s
|
||||||
|
AND j.company = %s
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
tax_details.from_date,
|
tax_details.from_date,
|
||||||
tax_details.to_date,
|
tax_details.to_date,
|
||||||
tuple(parties),
|
tuple(parties),
|
||||||
tax_details.get("tax_withholding_category"),
|
tax_details.get("tax_withholding_category"),
|
||||||
|
company,
|
||||||
),
|
),
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
@@ -465,6 +467,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
|
|||||||
"unallocated_amount": (">", 0),
|
"unallocated_amount": (">", 0),
|
||||||
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
|
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
|
||||||
"tax_withholding_category": tax_details.get("tax_withholding_category"),
|
"tax_withholding_category": tax_details.get("tax_withholding_category"),
|
||||||
|
"company": inv.company,
|
||||||
}
|
}
|
||||||
|
|
||||||
field = "sum(tax_withholding_net_total)"
|
field = "sum(tax_withholding_net_total)"
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
|
|||||||
if total_credit:
|
if total_credit:
|
||||||
data.append(total_credit)
|
data.append(total_credit)
|
||||||
|
|
||||||
report_summary = get_bs_summary(
|
report_summary, primitive_summary = get_bs_summary(
|
||||||
companies,
|
companies,
|
||||||
asset,
|
asset,
|
||||||
liability,
|
liability,
|
||||||
@@ -180,7 +180,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
|
|||||||
|
|
||||||
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
|
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
|
||||||
|
|
||||||
report_summary = get_pl_summary(
|
report_summary, primitive_summary = get_pl_summary(
|
||||||
companies, "", income, expense, net_profit_loss, company_currency, filters, True
|
companies, "", income, expense, net_profit_loss, company_currency, filters, True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -199,6 +199,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",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -234,6 +234,20 @@ def get_conditions(filters):
|
|||||||
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,
|
||||||
|
},
|
||||||
|
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_records()
|
||||||
|
|
||||||
|
def clear_old_records(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)
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
|
|
||||||
|
|
||||||
def apply_conditions(query, pi, pii, filters):
|
def apply_conditions(query, pi, pii, filters):
|
||||||
for opts in ("company", "supplier", "item_code", "mode_of_payment"):
|
for opts in ("company", "supplier", "mode_of_payment"):
|
||||||
if filters.get(opts):
|
if filters.get(opts):
|
||||||
query = query.where(pi[opts] == filters[opts])
|
query = query.where(pi[opts] == filters[opts])
|
||||||
|
|
||||||
@@ -299,6 +299,9 @@ def apply_conditions(query, pi, pii, filters):
|
|||||||
if filters.get("to_date"):
|
if filters.get("to_date"):
|
||||||
query = query.where(pi.posting_date <= filters.get("to_date"))
|
query = query.where(pi.posting_date <= filters.get("to_date"))
|
||||||
|
|
||||||
|
if filters.get("item_code"):
|
||||||
|
query = query.where(pii.item_code == filters.get("item_code"))
|
||||||
|
|
||||||
if filters.get("item_group"):
|
if filters.get("item_group"):
|
||||||
query = query.where(pii.item_group == filters.get("item_group"))
|
query = query.where(pii.item_group == filters.get("item_group"))
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ frappe.query_reports["Item-wise Sales Register"] = {
|
|||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Brand",
|
options: "Brand",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: "item_code",
|
||||||
|
label: __("Item"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Item",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldname: "item_group",
|
fieldname: "item_group",
|
||||||
label: __("Item Group"),
|
label: __("Item Group"),
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
|
|
||||||
|
|
||||||
def apply_conditions(query, si, sii, filters, additional_conditions=None):
|
def apply_conditions(query, si, sii, filters, additional_conditions=None):
|
||||||
for opts in ("company", "customer", "item_code"):
|
for opts in ("company", "customer"):
|
||||||
if filters.get(opts):
|
if filters.get(opts):
|
||||||
query = query.where(si[opts] == filters[opts])
|
query = query.where(si[opts] == filters[opts])
|
||||||
|
|
||||||
@@ -371,6 +371,9 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None):
|
|||||||
if filters.get("brand"):
|
if filters.get("brand"):
|
||||||
query = query.where(sii.brand == filters.get("brand"))
|
query = query.where(sii.brand == filters.get("brand"))
|
||||||
|
|
||||||
|
if filters.get("item_code"):
|
||||||
|
query = query.where(sii.item_code == filters.get("item_code"))
|
||||||
|
|
||||||
if filters.get("item_group"):
|
if filters.get("item_group"):
|
||||||
query = query.where(sii.item_group == filters.get("item_group"))
|
query = query.where(sii.item_group == filters.get("item_group"))
|
||||||
|
|
||||||
|
|||||||
@@ -362,11 +362,16 @@ class Asset(AccountsController):
|
|||||||
final_number_of_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
|
final_number_of_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
|
||||||
self.number_of_depreciations_booked
|
self.number_of_depreciations_booked
|
||||||
)
|
)
|
||||||
has_pro_rata = self.check_is_pro_rata(finance_book)
|
for_income_tax = 0
|
||||||
|
if frappe.db.has_column("Finance Book", "for_income_tax"):
|
||||||
|
for_income_tax = frappe.db.get_value("Finance Book", finance_book.finance_book, "for_income_tax")
|
||||||
|
has_pro_rata = False
|
||||||
|
if not for_income_tax:
|
||||||
|
has_pro_rata = self.check_is_pro_rata(finance_book)
|
||||||
depr_already_booked = any(
|
depr_already_booked = any(
|
||||||
[d.journal_entry for d in self.get("schedules") if d.finance_book == finance_book.finance_book]
|
[d.journal_entry for d in self.get("schedules") if d.finance_book == finance_book.finance_book]
|
||||||
)
|
)
|
||||||
if has_pro_rata and not depr_already_booked:
|
if has_pro_rata and not depr_already_booked and not for_income_tax:
|
||||||
final_number_of_depreciations += 1
|
final_number_of_depreciations += 1
|
||||||
|
|
||||||
has_wdv_or_dd_non_yearly_pro_rata = False
|
has_wdv_or_dd_non_yearly_pro_rata = False
|
||||||
@@ -517,10 +522,13 @@ class Asset(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Adjust depreciation amount in the last period based on the expected value after useful life
|
# Adjust depreciation amount in the last period based on the expected value after useful life
|
||||||
if (
|
if not for_income_tax and (
|
||||||
n == cint(final_number_of_depreciations) - 1
|
(
|
||||||
and flt(value_after_depreciation) != flt(finance_book.expected_value_after_useful_life)
|
n == cint(final_number_of_depreciations) - 1
|
||||||
) or flt(value_after_depreciation) < flt(finance_book.expected_value_after_useful_life):
|
and flt(value_after_depreciation) != flt(finance_book.expected_value_after_useful_life)
|
||||||
|
)
|
||||||
|
or flt(value_after_depreciation) < flt(finance_book.expected_value_after_useful_life)
|
||||||
|
):
|
||||||
depreciation_amount += flt(value_after_depreciation) - flt(
|
depreciation_amount += flt(value_after_depreciation) - flt(
|
||||||
finance_book.expected_value_after_useful_life
|
finance_book.expected_value_after_useful_life
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_
|
|||||||
def update_last_purchase_rate(doc, is_submit) -> None:
|
def update_last_purchase_rate(doc, is_submit) -> None:
|
||||||
"""updates last_purchase_rate in item table for each item"""
|
"""updates last_purchase_rate in item table for each item"""
|
||||||
|
|
||||||
|
if doc.get("is_internal_supplier"):
|
||||||
|
return
|
||||||
|
|
||||||
this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date"))
|
this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date"))
|
||||||
|
|
||||||
for d in doc.get("items"):
|
for d in doc.get("items"):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -472,6 +472,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
quotation_to: me.frm.doc.quotation_to,
|
quotation_to: me.frm.doc.quotation_to,
|
||||||
supplier: me.frm.doc.supplier,
|
supplier: me.frm.doc.supplier,
|
||||||
currency: me.frm.doc.currency,
|
currency: me.frm.doc.currency,
|
||||||
|
is_internal_supplier: me.frm.doc.is_internal_supplier,
|
||||||
|
is_internal_customer: me.frm.doc.is_internal_customer,
|
||||||
update_stock: update_stock,
|
update_stock: update_stock,
|
||||||
conversion_rate: me.frm.doc.conversion_rate,
|
conversion_rate: me.frm.doc.conversion_rate,
|
||||||
price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list,
|
price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list,
|
||||||
@@ -1489,7 +1491,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
|
"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
|
||||||
"conversion_factor": me.frm.doc.conversion_factor,
|
"conversion_factor": me.frm.doc.conversion_factor,
|
||||||
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
|
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
|
||||||
"coupon_code": me.frm.doc.coupon_code
|
"coupon_code": me.frm.doc.coupon_code,
|
||||||
|
"is_internal_supplier": me.frm.doc.is_internal_supplier,
|
||||||
|
"is_internal_customer": me.frm.doc.is_internal_customer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -193,6 +193,9 @@ erpnext.SalesFunnel = class SalesFunnel {
|
|||||||
this.options.width = ($(this.elements.funnel_wrapper).width() * 2.0) / 3.0;
|
this.options.width = ($(this.elements.funnel_wrapper).width() * 2.0) / 3.0;
|
||||||
this.options.height = (Math.sqrt(3) * this.options.width) / 2.0;
|
this.options.height = (Math.sqrt(3) * this.options.width) / 2.0;
|
||||||
|
|
||||||
|
const min_height = (this.options.height * 0.1) / this.options.data.length;
|
||||||
|
const height = this.options.height * 0.9;
|
||||||
|
|
||||||
// calculate total weightage
|
// calculate total weightage
|
||||||
// as height decreases, area decreases by the square of the reduction
|
// as height decreases, area decreases by the square of the reduction
|
||||||
// hence, compensating by squaring the index value
|
// hence, compensating by squaring the index value
|
||||||
@@ -202,7 +205,7 @@ erpnext.SalesFunnel = class SalesFunnel {
|
|||||||
|
|
||||||
// calculate height for each data
|
// calculate height for each data
|
||||||
$.each(this.options.data, function (i, d) {
|
$.each(this.options.data, function (i, d) {
|
||||||
d.height = (me.options.height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage;
|
d.height = (height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage + min_height;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.elements.canvas = $("<canvas></canvas>")
|
this.elements.canvas = $("<canvas></canvas>")
|
||||||
|
|||||||
@@ -855,6 +855,9 @@ def get_price_list_rate(args, item_doc, out=None):
|
|||||||
if price_list_rate is None or frappe.db.get_single_value(
|
if price_list_rate is None or frappe.db.get_single_value(
|
||||||
"Stock Settings", "update_existing_price_list_rate"
|
"Stock Settings", "update_existing_price_list_rate"
|
||||||
):
|
):
|
||||||
|
if args.get("is_internal_supplier") or args.get("is_internal_customer"):
|
||||||
|
return out
|
||||||
|
|
||||||
if args.price_list and args.rate:
|
if args.price_list and args.rate:
|
||||||
insert_item_price(args)
|
insert_item_price(args)
|
||||||
|
|
||||||
@@ -866,7 +869,11 @@ def get_price_list_rate(args, item_doc, out=None):
|
|||||||
if frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"):
|
if frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
if not out.price_list_rate and args.transaction_type == "buying":
|
if (
|
||||||
|
not args.get("is_internal_supplier")
|
||||||
|
and not out.price_list_rate
|
||||||
|
and args.transaction_type == "buying"
|
||||||
|
):
|
||||||
from erpnext.stock.doctype.item.item import get_last_purchase_details
|
from erpnext.stock.doctype.item.item import get_last_purchase_details
|
||||||
|
|
||||||
out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate))
|
out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate))
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ def get_columns(filters):
|
|||||||
"width": 150,
|
"width": 150,
|
||||||
"options": "Batch",
|
"options": "Batch",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": _("Expiry Date"),
|
||||||
|
"fieldname": "expiry_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
{"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150},
|
{"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -96,6 +102,7 @@ def get_batchwise_data_from_stock_ledger(filters):
|
|||||||
table.item_code,
|
table.item_code,
|
||||||
table.batch_no,
|
table.batch_no,
|
||||||
table.warehouse,
|
table.warehouse,
|
||||||
|
batch.expiry_date,
|
||||||
Sum(table.actual_qty).as_("balance_qty"),
|
Sum(table.actual_qty).as_("balance_qty"),
|
||||||
)
|
)
|
||||||
.where(table.is_cancelled == 0)
|
.where(table.is_cancelled == 0)
|
||||||
@@ -118,10 +125,14 @@ def get_query_based_on_filters(query, batch, table, filters):
|
|||||||
if filters.batch_no:
|
if filters.batch_no:
|
||||||
query = query.where(batch.name == filters.batch_no)
|
query = query.where(batch.name == filters.batch_no)
|
||||||
|
|
||||||
if not filters.include_expired_batches:
|
if filters.to_date == today():
|
||||||
query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull()))
|
if not filters.include_expired_batches:
|
||||||
if filters.to_date == today():
|
query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull()))
|
||||||
query = query.where(batch.batch_qty > 0)
|
|
||||||
|
query = query.where(batch.batch_qty > 0)
|
||||||
|
|
||||||
|
else:
|
||||||
|
query = query.where(table.posting_date <= filters.to_date)
|
||||||
|
|
||||||
if filters.warehouse:
|
if filters.warehouse:
|
||||||
lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"])
|
lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"])
|
||||||
|
|||||||
Reference in New Issue
Block a user