Merge pull request #50249 from frappe/version-15-hotfix

chore: release v15
This commit is contained in:
Diptanil Saha
2025-10-28 19:17:10 +05:30
committed by GitHub
27 changed files with 294 additions and 65 deletions

View File

@@ -123,8 +123,7 @@
"fieldname": "transaction_id",
"fieldtype": "Data",
"label": "Transaction ID",
"read_only": 1,
"unique": 1
"read_only": 1
},
{
"allow_on_submit": 1,
@@ -239,7 +238,7 @@
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2025-10-14 11:53:45.908169",
"modified": "2025-10-23 17:32:58.514807",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",

View File

@@ -99,7 +99,7 @@ class FiscalYear(Document):
)
overlap = False
if not self.get("companies") or not company_for_existing:
if not self.get("companies") and not company_for_existing:
overlap = True
for d in self.get("companies"):

View File

@@ -26,6 +26,27 @@ class TestFiscalYear(unittest.TestCase):
self.assertRaises(frappe.exceptions.InvalidDates, fy.insert)
def test_company_fiscal_year_overlap(self):
for name in ["_Test Global FY 2001", "_Test Company FY 2001"]:
if frappe.db.exists("Fiscal Year", name):
frappe.delete_doc("Fiscal Year", name)
global_fy = frappe.new_doc("Fiscal Year")
global_fy.year = "_Test Global FY 2001"
global_fy.year_start_date = "2001-04-01"
global_fy.year_end_date = "2002-03-31"
global_fy.insert()
company_fy = frappe.new_doc("Fiscal Year")
company_fy.year = "_Test Company FY 2001"
company_fy.year_start_date = "2001-01-01"
company_fy.year_end_date = "2001-12-31"
company_fy.append("companies", {"company": "_Test Company"})
company_fy.insert()
self.assertTrue(frappe.db.exists("Fiscal Year", global_fy.name))
self.assertTrue(frappe.db.exists("Fiscal Year", company_fy.name))
def test_record_generator():
test_records = [

View File

@@ -106,7 +106,6 @@
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
"read_only": 1
@@ -288,7 +287,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-10-20 17:46:47.344089",
"modified": "2025-10-27 13:48:32.805100",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -385,6 +385,16 @@ frappe.ui.form.on("Payment Reconciliation Allocation", {
// filter payment
let payment = frm.doc.payments.filter((x) => x.reference_name == row.reference_name);
let amount = payment[0].amount;
for (const d of frm.doc.allocation) {
if (row.reference_name == d.reference_name && amount) {
if (d.allocated_amount <= amount) {
d.amount = amount;
amount -= d.allocated_amount;
}
}
}
frm.call({
doc: frm.doc,
method: "calculate_difference_on_allocation_change",

View File

@@ -648,6 +648,7 @@ def make_reverse_gl_entries(
adv_adj=False,
update_outstanding="Yes",
partial_cancel=False,
posting_date=None,
):
"""
Get original gl entries of the voucher
@@ -745,6 +746,8 @@ def make_reverse_gl_entries(
if immutable_ledger_enabled:
new_gle["is_cancelled"] = 0
new_gle["posting_date"] = frappe.form_dict.get("posting_date") or getdate()
elif posting_date:
new_gle["posting_date"] = posting_date
if new_gle["debit"] or new_gle["credit"]:
make_entry(new_gle, adv_adj, "Yes")

View File

@@ -273,6 +273,7 @@ def get_asset_value_adjustment_map_by_category(filters):
AND a.company = %(company)s
AND a.purchase_date <= %(to_date)s
AND gle.account = aca.fixed_asset_account
AND gle.is_opening = 'No'
GROUP BY a.asset_category
""",
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
@@ -543,6 +544,7 @@ def get_asset_value_adjustment_map(filters):
AND a.company = %(company)s
AND a.purchase_date <= %(to_date)s
AND gle.account = aca.fixed_asset_account
AND gle.is_opening = 'No'
GROUP BY a.name
""",
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},

View File

@@ -231,6 +231,15 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
group_columns = group_wise_columns.get(scrub(filters.group_by))
# removing customer_name from group columns
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
if "customer_name" in group_columns and (
supplier_master_name == "Supplier Name" and customer_master_name == "Customer Name"
):
group_columns = [col for col in group_columns if col != "customer_name"]
for src in gross_profit_data.grouped_data:
total_base_amount += src.base_amount or 0.00
total_buying_amount += src.buying_amount or 0.00

View File

@@ -518,6 +518,7 @@
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:doc.docstatus > 0",
"fieldname": "additional_asset_cost",
"fieldtype": "Currency",
@@ -596,7 +597,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2025-05-20 00:44:06.229177",
"modified": "2025-10-23 22:43:33.634452",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
@@ -641,4 +642,4 @@
"states": [],
"title_field": "asset_name",
"track_changes": 1
}
}

View File

@@ -69,6 +69,7 @@ class Asset(AccountsController):
default_finance_book: DF.Link | None
department: DF.Link | None
depr_entry_posting_status: DF.Literal["", "Successful", "Failed"]
depreciation_completed: DF.Check
depreciation_method: DF.Literal["", "Straight Line", "Double Declining Balance", "Manual"]
disposal_date: DF.Date | None
finance_books: DF.Table[AssetFinanceBook]
@@ -152,7 +153,9 @@ class Asset(AccountsController):
)
self.validate_expected_value_after_useful_life()
self.set_total_booked_depreciations()
self.total_asset_cost = self.gross_purchase_amount
def before_save(self):
self.total_asset_cost = self.gross_purchase_amount + self.additional_asset_cost
self.status = self.get_status()
def on_submit(self):

View File

@@ -878,6 +878,7 @@ class BuyingController(SubcontractingController):
"asset_category": item_data.get("asset_category"),
"location": row.asset_location,
"company": self.company,
"status": "Draft",
"supplier": self.supplier,
"purchase_date": self.posting_date,
"calculate_depreciation": 0,

View File

@@ -724,6 +724,9 @@ class ProductionPlan(Document):
if not wo_list:
frappe.msgprint(_("No Work Orders were created"))
if not po_list:
frappe.msgprint(_("No Purchase Orders were created"))
def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
items_data = self.get_production_items()

View File

@@ -26,7 +26,6 @@ def get_exploded_items(bom, data, indent=0, qty=1):
)
for item in exploded_items:
print(item.bom_no, indent)
item["indent"] = indent
data.append(
{

View File

@@ -422,3 +422,4 @@ execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_fo
erpnext.patches.v15_0.add_company_payment_gateway_account
erpnext.patches.v15_0.update_uae_zero_rated_fetch
erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter
erpnext.patches.v15_0.set_asset_status_if_not_already_set

View File

@@ -0,0 +1,13 @@
import frappe
from frappe.query_builder import DocType
def execute():
Asset = DocType("Asset")
query = (
frappe.qb.update(Asset)
.set(Asset.status, "Draft")
.where((Asset.docstatus == 0) & ((Asset.status.isnull()) | (Asset.status == "")))
)
query.run()

View File

@@ -245,6 +245,7 @@
"fieldname": "act_start_date",
"fieldtype": "Date",
"label": "Actual Start Date (via Timesheet)",
"no_copy": 1,
"oldfieldname": "act_start_date",
"oldfieldtype": "Date",
"read_only": 1
@@ -253,6 +254,7 @@
"fieldname": "actual_time",
"fieldtype": "Float",
"label": "Actual Time in Hours (via Timesheet)",
"no_copy": 1,
"read_only": 1
},
{
@@ -263,6 +265,7 @@
"fieldname": "act_end_date",
"fieldtype": "Date",
"label": "Actual End Date (via Timesheet)",
"no_copy": 1,
"oldfieldname": "act_end_date",
"oldfieldtype": "Date",
"read_only": 1
@@ -277,6 +280,7 @@
"fieldname": "total_costing_amount",
"fieldtype": "Currency",
"label": "Total Costing Amount (via Timesheet)",
"no_copy": 1,
"oldfieldname": "actual_budget",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
@@ -290,6 +294,7 @@
"fieldname": "total_billing_amount",
"fieldtype": "Currency",
"label": "Total Billable Amount (via Timesheet)",
"no_copy": 1,
"read_only": 1
},
{
@@ -399,7 +404,7 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
"modified": "2024-01-08 16:00:41.296203",
"modified": "2025-10-16 08:39:12.214577",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
@@ -429,4 +434,4 @@
"timeline_field": "project",
"title_field": "subject",
"track_seen": 1
}
}

View File

@@ -3,6 +3,12 @@
frappe.query_reports["Delayed Tasks Summary"] = {
filters: [
{
fieldname: "project",
label: __("Project"),
fieldtype: "Link",
options: "Project",
},
{
fieldname: "from_date",
label: __("From Date"),

View File

@@ -23,6 +23,7 @@ def get_data(filters):
fields=[
"name",
"subject",
"project",
"exp_start_date",
"exp_end_date",
"status",
@@ -56,7 +57,7 @@ def get_data(filters):
def get_conditions(filters):
conditions = frappe._dict()
keys = ["priority", "status"]
keys = ["priority", "status", "project"]
for key in keys:
if filters.get(key):
conditions[key] = filters.get(key)
@@ -89,6 +90,13 @@ def get_columns():
columns = [
{"fieldname": "name", "fieldtype": "Link", "label": _("Task"), "options": "Task", "width": 150},
{"fieldname": "subject", "fieldtype": "Data", "label": _("Subject"), "width": 200},
{
"fieldname": "project",
"fieldtype": "Link",
"label": _("Project"),
"options": "Project",
"width": 150,
},
{"fieldname": "status", "fieldtype": "Data", "label": _("Status"), "width": 100},
{"fieldname": "priority", "fieldtype": "Data", "label": _("Priority"), "width": 80},
{"fieldname": "progress", "fieldtype": "Data", "label": _("Progress (%)"), "width": 120},

View File

@@ -237,10 +237,10 @@ erpnext.accounts.pos = {
frappe.ui.form.on(doctype, {
mode_of_payment: function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
get_payment_mode_account(frm, d.mode_of_payment, function(account){
frappe.model.set_value(cdt, cdn, 'account', account)
})
}
erpnext.accounts.pos.get_payment_mode_account(frm, d.mode_of_payment, function (account) {
frappe.model.set_value(cdt, cdn, "account", account);
});
},
});
},

View File

@@ -24,6 +24,7 @@ frappe.ui.form.on("Driver", {
transporter: function (frm, cdt, cdn) {
// this assumes that supplier's address has same title as supplier's name
if (!frm.doc.transporter) return;
frappe.db
.get_doc("Address", null, { address_title: frm.doc.transporter })
.then((r) => {

View File

@@ -539,7 +539,7 @@ frappe.ui.form.on("Stock Entry", {
const item = locals[cdt][cdn];
item.transfer_qty = flt(item.qty) * flt(item.conversion_factor);
const args = {
let args = {
item_code: item.item_code,
posting_date: frm.doc.posting_date,
posting_time: frm.doc.posting_time,
@@ -553,6 +553,10 @@ frappe.ui.form.on("Stock Entry", {
allow_zero_valuation: 1,
};
if (item.batch_no && frm.doc.purpose == "Material Receipt") {
args.qty = Math.abs(args.qty) * -1;
}
if (item.item_code || item.serial_no) {
frappe.call({
method: "erpnext.stock.utils.get_incoming_rate",

View File

@@ -7,7 +7,10 @@ frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on("Stock Reconciliation", {
setup(frm) {
frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"];
frm.barcode_scanner = new erpnext.utils.BarcodeScanner({ frm });
frm.barcode_scanner = new erpnext.utils.BarcodeScanner({
frm: frm,
uom_field: "stock_uom",
});
},
onload: function (frm) {

View File

@@ -24,24 +24,26 @@ frappe.query_reports["Incorrect Serial and Batch Bundle"] = {
},
onload(report) {
report.page.add_inner_button(__("Remove SABB Entry"), () => {
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
let selected_rows = indexes.map((i) => frappe.query_report.data[i]);
report.page
.add_inner_button(__("Fix SABB Entry"), () => {
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
let selected_rows = indexes.map((i) => frappe.query_report.data[i]);
if (!selected_rows.length) {
frappe.throw(__("Please select a row to create a Reposting Entry"));
} else {
frappe.call({
method: "erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle.remove_sabb_entry",
freeze: true,
args: {
selected_rows: selected_rows,
},
callback: function (r) {
frappe.query_report.refresh();
},
});
}
});
if (!selected_rows.length) {
frappe.throw(__("Please select at least one row to fix"));
} else {
frappe.call({
method: "erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle.fix_sabb_entries",
freeze: true,
args: {
selected_rows: selected_rows,
},
callback: function (r) {
frappe.query_report.refresh();
},
});
}
})
.addClass("btn-primary");
},
};

View File

@@ -13,7 +13,10 @@ def execute(filters: dict | None = None):
every time the report is refreshed or a filter is updated.
"""
columns = get_columns()
data = get_data(filters)
unlinked_bundles = get_unlinked_serial_batch_bundles(filters) or []
linked_cancelled_bundles = get_linked_cancelled_sabb(filters) or []
data = unlinked_bundles + linked_cancelled_bundles
return columns, data
@@ -50,14 +53,17 @@ def get_columns() -> list[dict]:
"fieldtype": "Data",
"width": 200,
},
{
"label": _("Is Cancelled"),
"fieldname": "is_cancelled",
"fieldtype": "Check",
"width": 200,
},
]
def get_data(filters) -> list[list]:
"""Return data for the report.
The report data is a list of rows, with each row being a list of cell values.
"""
def get_unlinked_serial_batch_bundles(filters) -> list[list]:
# SABB has not been linked to any SLE
SABB = frappe.qb.DocType("Serial and Batch Bundle")
SLE = frappe.qb.DocType("Stock Ledger Entry")
@@ -77,6 +83,7 @@ def get_data(filters) -> list[list]:
SABB.voucher_type,
SABB.voucher_no,
SABB.voucher_detail_no,
SABB.is_cancelled,
)
.where(
(SLE.serial_and_batch_bundle.isnull())
@@ -94,14 +101,63 @@ def get_data(filters) -> list[list]:
return data
def get_linked_cancelled_sabb(filters):
# SABB has cancelled but voucher is not cancelled
SABB = frappe.qb.DocType("Serial and Batch Bundle")
SLE = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(SABB)
.inner_join(SLE)
.on(SABB.name == SLE.serial_and_batch_bundle)
.select(
SABB.name,
SABB.voucher_type,
SABB.voucher_no,
SABB.voucher_detail_no,
SABB.is_cancelled,
)
.where(
(SLE.serial_and_batch_bundle.isnotnull())
& (SABB.docstatus == 2)
& (SABB.is_cancelled == 1)
& (SLE.is_cancelled == 0)
)
)
for field in filters:
query = query.where(SABB[field] == filters[field])
data = query.run(as_dict=1)
return data
@frappe.whitelist()
def remove_sabb_entry(selected_rows):
def fix_sabb_entries(selected_rows):
if isinstance(selected_rows, str):
selected_rows = frappe.parse_json(selected_rows)
for row in selected_rows:
doc = frappe.get_doc("Serial and Batch Bundle", row.get("name"))
doc.cancel()
doc.delete()
if doc.is_cancelled == 0 and not frappe.db.get_value(
"Stock Ledger Entry",
{"serial_and_batch_bundle": doc.name, "is_cancelled": 0},
"name",
):
doc.db_set({"is_cancelled": 1, "docstatus": 2})
frappe.msgprint(_("Selected Serial and Batch Bundle entries have been removed."))
for row in doc.entries:
row.db_set("docstatus", 2)
elif doc.is_cancelled == 1 and frappe.db.get_value(
"Stock Ledger Entry",
{"serial_and_batch_bundle": doc.name, "is_cancelled": 0},
"name",
):
doc.db_set({"is_cancelled": 0, "docstatus": 1})
for row in doc.entries:
row.db_set("docstatus", 1)
frappe.msgprint(_("Selected Serial and Batch Bundle entries have been fixed."))

View File

@@ -18,16 +18,41 @@ def execute(filters=None):
def get_data(filters):
data = get_stock_ledger_entries(filters)
serial_nos_data = prepare_serial_nos(data)
bundles = get_bundles(data)
serial_nos_data = prepare_serial_nos(data, bundles)
data = get_incorrect_serial_nos(serial_nos_data)
return data
def prepare_serial_nos(data):
def get_bundles(data):
bundles = [d.serial_and_batch_bundle for d in data if d.serial_and_batch_bundle]
bundle_dict = frappe._dict()
serial_nos_data = frappe.get_all(
"Serial and Batch Entry",
fields=["parent", "serial_no", "incoming_rate", "qty"],
filters={"parent": ("in", bundles), "serial_no": ("is", "set")},
)
for entry in serial_nos_data:
bundle_dict.setdefault(entry.parent, []).append(entry)
return bundle_dict
def prepare_serial_nos(data, bundles):
serial_no_wise_data = {}
for row in data:
if not row.serial_nos:
if not row.serial_nos and not row.serial_and_batch_bundle:
continue
if row.serial_and_batch_bundle:
for bundle in bundles.get(row.serial_and_batch_bundle, []):
sle = copy.deepcopy(row)
sle.serial_no = bundle.serial_no
sle.qty = bundle.qty
sle.valuation_rate = bundle.incoming_rate * (1 if sle.qty > 0 else -1)
serial_no_wise_data.setdefault(bundle.serial_no, []).append(sle)
continue
for serial_no in get_serial_nos(row.serial_nos):
@@ -54,6 +79,9 @@ def get_incorrect_serial_nos(serial_nos_data):
total_value.qty += total_dict.qty
total_value.valuation_rate += total_dict.valuation_rate
if total_dict.qty == 0 and abs(total_dict.valuation_rate) == 0:
continue
result.append(total_dict)
result.append({})
@@ -81,6 +109,7 @@ def get_stock_ledger_entries(report_filters):
"voucher_no",
"item_code",
"serial_no as serial_nos",
"serial_and_batch_bundle",
"actual_qty",
"posting_date",
"posting_time",
@@ -89,7 +118,8 @@ def get_stock_ledger_entries(report_filters):
"(stock_value_difference / actual_qty) as valuation_rate",
]
filters = {"serial_no": ("is", "set"), "is_cancelled": 0}
filters = {"is_cancelled": 0}
or_filters = {"serial_no": ("is", "set"), "serial_and_batch_bundle": ("is", "set")}
if report_filters.get("item_code"):
filters["item_code"] = report_filters.get("item_code")
@@ -104,6 +134,7 @@ def get_stock_ledger_entries(report_filters):
"Stock Ledger Entry",
fields=fields,
filters=filters,
or_filters=or_filters,
order_by="posting_date asc, posting_time asc, creation asc",
)

View File

@@ -731,13 +731,55 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
for ledger in entries:
self.stock_value_differece[ledger.batch_no] += flt(ledger.incoming_rate)
self.available_qty[ledger.batch_no] += flt(ledger.qty)
self.total_qty[ledger.batch_no] += flt(ledger.total_qty)
entries = self.get_batch_wise_total_available_qty()
for row in entries:
self.total_qty[row.batch_no] += flt(row.total_qty)
self.calculate_avg_rate_from_deprecarated_ledgers()
self.calculate_avg_rate_for_non_batchwise_valuation()
self.set_stock_value_difference()
def get_batch_wise_total_available_qty(self) -> list[dict]:
# Get total qty of each batch no from Serial and Batch Bundle without checking time condition
if not self.batchwise_valuation_batches:
return []
parent = frappe.qb.DocType("Serial and Batch Bundle")
child = frappe.qb.DocType("Serial and Batch Entry")
query = (
frappe.qb.from_(parent)
.inner_join(child)
.on(parent.name == child.parent)
.select(
child.batch_no,
Sum(child.qty).as_("total_qty"),
)
.where(
(parent.warehouse == self.sle.warehouse)
& (parent.item_code == self.sle.item_code)
& (child.batch_no.isin(self.batchwise_valuation_batches))
& (parent.docstatus == 1)
& (parent.is_cancelled == 0)
& (parent.type_of_transaction.isin(["Inward", "Outward"]))
)
.for_update()
.groupby(child.batch_no)
)
# Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference
if self.sle.voucher_detail_no:
query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no)
elif self.sle.voucher_no:
query = query.where(parent.voucher_no != self.sle.voucher_no)
query = query.where(parent.voucher_type != "Pick List")
return query.run(as_dict=True)
def get_batch_no_ledgers(self) -> list[dict]:
# Get batch wise stock value difference from Serial and Batch Bundle considering time condition
if not self.batchwise_valuation_batches:
return []
@@ -765,11 +807,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
.on(parent.name == child.parent)
.select(
child.batch_no,
Sum(Case().when(timestamp_condition, child.stock_value_difference).else_(0)).as_(
"incoming_rate"
),
Sum(Case().when(timestamp_condition, child.qty).else_(0)).as_("qty"),
Sum(child.qty).as_("total_qty"),
Sum(child.stock_value_difference).as_("incoming_rate"),
Sum(child.qty).as_("qty"),
)
.where(
(parent.warehouse == self.sle.warehouse)
@@ -790,6 +829,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
query = query.where(parent.voucher_no != self.sle.voucher_no)
query = query.where(parent.voucher_type != "Pick List")
if timestamp_condition:
query = query.where(timestamp_condition)
return query.run(as_dict=True)

View File

@@ -893,7 +893,11 @@ class update_entries_after:
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
sle.stock_value_difference = stock_value_difference
if sle.is_adjustment_entry and flt(sle.qty_after_transaction, self.flt_precision) == 0:
if (
sle.is_adjustment_entry
and flt(sle.qty_after_transaction, self.flt_precision) == 0
and flt(sle.stock_value, self.currency_precision) != 0
):
sle.stock_value_difference = (
get_stock_value_difference(
sle.item_code,
@@ -901,6 +905,7 @@ class update_entries_after:
sle.posting_date,
sle.posting_time,
voucher_detail_no=sle.voucher_detail_no,
creation=sle.creation,
)
* -1
)
@@ -2358,7 +2363,7 @@ def is_internal_transfer(sle):
def get_stock_value_difference(
item_code, warehouse, posting_date, posting_time, voucher_no=None, voucher_detail_no=None
item_code, warehouse, posting_date, posting_time, voucher_no=None, voucher_detail_no=None, creation=None
):
table = frappe.qb.DocType("Stock Ledger Entry")
posting_datetime = get_combine_datetime(posting_date, posting_time)
@@ -2366,12 +2371,7 @@ def get_stock_value_difference(
query = (
frappe.qb.from_(table)
.select(Sum(table.stock_value_difference).as_("value"))
.where(
(table.is_cancelled == 0)
& (table.item_code == item_code)
& (table.warehouse == warehouse)
& (table.posting_datetime <= posting_datetime)
)
.where((table.is_cancelled == 0) & (table.item_code == item_code) & (table.warehouse == warehouse))
)
if voucher_detail_no:
@@ -2380,6 +2380,14 @@ def get_stock_value_difference(
elif voucher_no:
query = query.where(table.voucher_no != voucher_no)
if creation:
query = query.where(
(table.posting_datetime < posting_datetime)
| ((table.posting_datetime == posting_datetime) & (table.creation < creation))
)
else:
query = query.where(table.posting_datetime <= posting_datetime)
difference_amount = query.run()
return flt(difference_amount[0][0]) if difference_amount else 0