Merge pull request #42648 from frappe/version-14-hotfix

chore: release v14
This commit is contained in:
ruthra kumar
2024-08-07 14:56:41 +05:30
committed by GitHub
18 changed files with 227 additions and 31 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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)"

View File

@@ -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
) )

View File

@@ -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",
},
], ],
}; };

View File

@@ -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")

View File

@@ -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)

View File

@@ -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"))

View File

@@ -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"),

View File

@@ -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"))

View File

@@ -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
) )

View File

@@ -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"):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,
}; };
} }

View File

@@ -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>")

View File

@@ -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))

View File

@@ -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"])