refactor: posting datetime for SABB (backport #49710 + #50248) (#56072)

* refactor: posting datetime for SABB (backport #49710 + #50248)

Backport of #49710 "refactor: posting datetime for SABB" to
version-15-hotfix, including the regression fix #50248
"fix: missed refactoring code".

Replaces Serial and Batch Bundle's `posting_date`/`posting_time` with a
single `posting_datetime` field and updates all readers/writers; adds a
patch to populate `posting_datetime` from the linked Stock Ledger Entry
and drop the now-unused single-column indexes.

Adapted to version-15-hotfix where the surrounding stock code had already
moved to `posting_datetime` (SLE) and diverged from develop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: fix conflicts

* chore: fixed formatting

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rohitwaghchaure
2026-06-18 11:21:13 +05:30
committed by GitHub
parent 5c4220ee77
commit 98caefea88
18 changed files with 216 additions and 215 deletions

View File

@@ -11,7 +11,7 @@ from frappe.utils import cint, flt, format_datetime, get_datetime
import erpnext import erpnext
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
from erpnext.stock.utils import get_incoming_rate, get_valuation_method, getdate from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method, getdate
class StockOverReturnError(frappe.ValidationError): class StockOverReturnError(frappe.ValidationError):
@@ -1180,8 +1180,7 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f
"batches": data.get("batches"), "batches": data.get("batches"),
"serial_nos_valuation": data.get("serial_nos_valuation"), "serial_nos_valuation": data.get("serial_nos_valuation"),
"batches_valuation": data.get("batches_valuation"), "batches_valuation": data.get("batches_valuation"),
"posting_date": parent_doc.posting_date, "posting_datetime": get_combine_datetime(parent_doc.posting_date, parent_doc.posting_time),
"posting_time": parent_doc.posting_time,
"voucher_type": parent_doc.doctype, "voucher_type": parent_doc.doctype,
"voucher_no": parent_doc.name, "voucher_no": parent_doc.name,
"voucher_detail_no": child_doc.name, "voucher_detail_no": child_doc.name,

View File

@@ -12,7 +12,7 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return, i
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.item.item import set_item_default from erpnext.stock.doctype.item.item import set_item_default
from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor
from erpnext.stock.utils import get_incoming_rate, get_valuation_method from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method
class SellingController(StockController): class SellingController(StockController):
@@ -1084,8 +1084,7 @@ def get_serial_and_batch_bundle(child, parent, delivery_note_child=None):
"voucher_type": parent.doctype, "voucher_type": parent.doctype,
"voucher_no": parent.name if parent.docstatus < 2 else None, "voucher_no": parent.name if parent.docstatus < 2 else None,
"voucher_detail_no": delivery_note_child.name if delivery_note_child else child.name, "voucher_detail_no": delivery_note_child.name if delivery_note_child else child.name,
"posting_date": parent.posting_date, "posting_datetime": get_combine_datetime(parent.posting_date, parent.posting_time),
"posting_time": parent.posting_time,
"qty": child.qty, "qty": child.qty,
"type_of_transaction": "Outward" if child.qty > 0 and parent.docstatus < 2 else "Inward", "type_of_transaction": "Outward" if child.qty > 0 and parent.docstatus < 2 else "Inward",
"company": parent.company, "company": parent.company,

View File

@@ -26,6 +26,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
get_evaluated_inventory_dimension, get_evaluated_inventory_dimension,
) )
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
combine_datetime,
get_type_of_transaction, get_type_of_transaction,
) )
from erpnext.stock.stock_ledger import get_items_to_be_repost from erpnext.stock.stock_ledger import get_items_to_be_repost
@@ -282,8 +283,7 @@ class StockController(AccountsController):
): ):
bundle_details = { bundle_details = {
"item_code": row.get("rm_item_code") or row.item_code, "item_code": row.get("rm_item_code") or row.item_code,
"posting_date": self.posting_date, "posting_datetime": combine_datetime(self.posting_date, self.posting_time),
"posting_time": self.posting_time,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"voucher_no": self.name, "voucher_no": self.name,
"voucher_detail_no": row.name, "voucher_detail_no": row.name,

View File

@@ -13,6 +13,7 @@ from frappe.utils import cint, flt, get_link_to_form
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.batch.batch import get_batch_qty from erpnext.stock.doctype.batch.batch import get_batch_qty
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
combine_datetime,
get_auto_batch_nos, get_auto_batch_nos,
get_available_serial_nos, get_available_serial_nos,
get_voucher_wise_serial_batch_from_bundle, get_voucher_wise_serial_batch_from_bundle,
@@ -570,8 +571,7 @@ class SubcontractingController(StockController):
"qty": qty, "qty": qty,
"serial_nos": serial_nos, "serial_nos": serial_nos,
"batches": batches, "batches": batches,
"posting_date": self.posting_date, "posting_datetime": combine_datetime(self.posting_date, self.posting_time),
"posting_time": self.posting_time,
"voucher_type": "Subcontracting Receipt", "voucher_type": "Subcontracting Receipt",
"do_not_submit": True, "do_not_submit": True,
"type_of_transaction": "Outward" if qty > 0 else "Inward", "type_of_transaction": "Outward" if qty > 0 else "Inward",

View File

@@ -436,3 +436,4 @@ erpnext.patches.v16_0.depends_on_inv_dimensions
erpnext.patches.v16_0.clear_procedures_from_receivable_report erpnext.patches.v16_0.clear_procedures_from_receivable_report
erpnext.patches.v16_0.migrate_address_contact_custom_fields erpnext.patches.v16_0.migrate_address_contact_custom_fields
erpnext.patches.v15_0.set_main_item_code_in_material_request_plan_item erpnext.patches.v15_0.set_main_item_code_in_material_request_plan_item
erpnext.patches.v16_0.set_posting_datetime_for_sabb_and_drop_indexes

View File

@@ -0,0 +1,31 @@
import click
import frappe
def execute():
frappe.db.sql(
"""
UPDATE `tabSerial and Batch Bundle`
JOIN `tabStock Ledger Entry`
ON `tabSerial and Batch Bundle`.`name` = `tabStock Ledger Entry`.`serial_and_batch_bundle`
SET `tabSerial and Batch Bundle`.`posting_datetime` = `tabStock Ledger Entry`.`posting_datetime`
WHERE `tabStock Ledger Entry`.`is_cancelled` = 0
"""
)
drop_indexes()
def drop_indexes():
table = "tabSerial and Batch Bundle"
index_list = ["voucher_no_index", "item_code_index", "warehouse_index", "company_index"]
for index in index_list:
if not frappe.db.has_index(table, index):
continue
try:
frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`")
click.echo(f"✓ dropped {index} index from {table}")
except Exception:
frappe.log_error("Failed to drop index")

View File

@@ -36,6 +36,11 @@ class DeprecatedSerialNoValuation:
# get rate from serial nos within same company # get rate from serial nos within same company
incoming_values = 0.0 incoming_values = 0.0
posting_datetime = self.sle.posting_datetime
if not posting_datetime and self.sle.posting_date:
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
for serial_no in serial_nos: for serial_no in serial_nos:
sn_details = frappe.db.get_value("Serial No", serial_no, ["purchase_rate", "company"], as_dict=1) sn_details = frappe.db.get_value("Serial No", serial_no, ["purchase_rate", "company"], as_dict=1)
if ( if (
@@ -64,10 +69,7 @@ class DeprecatedSerialNoValuation:
& (table.serial_and_batch_bundle.isnull()) & (table.serial_and_batch_bundle.isnull())
& (table.actual_qty > 0) & (table.actual_qty > 0)
& (table.is_cancelled == 0) & (table.is_cancelled == 0)
& ( & (table.posting_datetime <= posting_datetime)
table.posting_datetime
<= get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
)
) )
.orderby(table.posting_datetime, order=Order.desc) .orderby(table.posting_datetime, order=Order.desc)
.limit(1) .limit(1)
@@ -98,11 +100,8 @@ class DeprecatedBatchNoValuation:
sle = frappe.qb.DocType("Stock Ledger Entry") sle = frappe.qb.DocType("Stock Ledger Entry")
timestamp_condition = None timestamp_condition = None
if self.sle.posting_date: if self.sle.posting_datetime:
if self.sle.posting_time is None: posting_datetime = self.sle.posting_datetime
self.sle.posting_time = nowtime()
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
if not self.sle.creation: if not self.sle.creation:
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
@@ -202,7 +201,11 @@ class DeprecatedBatchNoValuation:
sle = frappe.qb.DocType("Stock Ledger Entry") sle = frappe.qb.DocType("Stock Ledger Entry")
batch = frappe.qb.DocType("Batch") batch = frappe.qb.DocType("Batch")
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) posting_datetime = self.sle.posting_datetime
if not posting_datetime and self.sle.posting_date:
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
if not self.sle.creation: if not self.sle.creation:
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
@@ -266,7 +269,10 @@ class DeprecatedBatchNoValuation:
sle = frappe.qb.DocType("Stock Ledger Entry") sle = frappe.qb.DocType("Stock Ledger Entry")
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) posting_datetime = self.sle.posting_datetime
if not posting_datetime and self.sle.posting_date:
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
if not self.sle.creation: if not self.sle.creation:
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
@@ -308,19 +314,22 @@ class DeprecatedBatchNoValuation:
@deprecated @deprecated
def set_balance_value_from_bundle(self) -> None: def set_balance_value_from_bundle(self) -> None:
from erpnext.stock.utils import get_combine_datetime
bundle = frappe.qb.DocType("Serial and Batch Bundle") bundle = frappe.qb.DocType("Serial and Batch Bundle")
bundle_child = frappe.qb.DocType("Serial and Batch Entry") bundle_child = frappe.qb.DocType("Serial and Batch Entry")
batch = frappe.qb.DocType("Batch") batch = frappe.qb.DocType("Batch")
timestamp_condition = CombineDatetime(bundle.posting_date, bundle.posting_time) < CombineDatetime( posting_datetime = self.sle.posting_datetime
self.sle.posting_date, self.sle.posting_time if not posting_datetime and self.sle.posting_date:
) posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
timestamp_condition = bundle.posting_datetime < posting_datetime
if self.sle.creation: if self.sle.creation:
timestamp_condition |= ( timestamp_condition |= (bundle.posting_datetime == posting_datetime) & (
CombineDatetime(bundle.posting_date, bundle.posting_time) bundle.creation < self.sle.creation
== CombineDatetime(self.sle.posting_date, self.sle.posting_time) )
) & (bundle.creation < self.sle.creation)
query = ( query = (
frappe.qb.from_(bundle) frappe.qb.from_(bundle)

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import datetime
from collections import OrderedDict, defaultdict from collections import OrderedDict, defaultdict
import frappe import frappe
@@ -10,7 +11,7 @@ from frappe.model.document import Document
from frappe.model.naming import make_autoname, revert_series_if_last from frappe.model.naming import make_autoname, revert_series_if_last
from frappe.query_builder.functions import CurDate, Sum from frappe.query_builder.functions import CurDate, Sum
from frappe.utils import cint, flt, get_link_to_form from frappe.utils import cint, flt, get_link_to_form
from frappe.utils.data import add_days from frappe.utils.data import DateTimeLikeObject, add_days
class UnableToSelectBatchError(frappe.ValidationError): class UnableToSelectBatchError(frappe.ValidationError):
@@ -232,17 +233,18 @@ class Batch(Document):
@frappe.whitelist() @frappe.whitelist()
def get_batch_qty( def get_batch_qty(
batch_no=None, batch_no: str | None = None,
warehouse=None, warehouse: str | None = None,
item_code=None, item_code: str | None = None,
creation=None, creation: DateTimeLikeObject | None = None,
posting_date=None, posting_datetime: DateTimeLikeObject | None = None,
posting_time=None, posting_date: DateTimeLikeObject | None = None,
ignore_voucher_nos=None, posting_time: datetime.timedelta | None = None,
for_stock_levels=False, ignore_voucher_nos: list | None = None,
consider_negative_batches=False, for_stock_levels: bool = False,
do_not_check_future_batches=False, consider_negative_batches: bool = False,
ignore_reserved_stock=False, do_not_check_future_batches: bool = False,
ignore_reserved_stock: bool = False,
): ):
"""Returns batch actual qty if warehouse is passed, """Returns batch actual qty if warehouse is passed,
or returns dict of qty by warehouse if warehouse is None or returns dict of qty by warehouse if warehouse is None
@@ -255,6 +257,7 @@ def get_batch_qty(
:param for_stock_levels: True consider expired batches""" :param for_stock_levels: True consider expired batches"""
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
combine_datetime,
get_auto_batch_nos, get_auto_batch_nos,
) )
@@ -264,8 +267,6 @@ def get_batch_qty(
"item_code": item_code, "item_code": item_code,
"warehouse": warehouse, "warehouse": warehouse,
"creation": creation, "creation": creation,
"posting_date": posting_date,
"posting_time": posting_time,
"batch_no": batch_no, "batch_no": batch_no,
"based_on": frappe.get_single_value("Stock Settings", "pick_serial_and_batch_based_on"), "based_on": frappe.get_single_value("Stock Settings", "pick_serial_and_batch_based_on"),
"ignore_voucher_nos": ignore_voucher_nos, "ignore_voucher_nos": ignore_voucher_nos,
@@ -276,6 +277,10 @@ def get_batch_qty(
} }
) )
kwargs["posting_datetime"] = posting_datetime
if not kwargs.get("posting_datetime") and posting_date:
kwargs["posting_datetime"] = combine_datetime(posting_date, posting_time)
batches = get_auto_batch_nos(kwargs) batches = get_auto_batch_nos(kwargs)
if not (batch_no and warehouse): if not (batch_no and warehouse):
@@ -357,6 +362,7 @@ def make_batch_bundle(
): ):
from frappe.utils import nowtime, today from frappe.utils import nowtime, today
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import combine_datetime
from erpnext.stock.serial_batch_bundle import SerialBatchCreation from erpnext.stock.serial_batch_bundle import SerialBatchCreation
return ( return (
@@ -364,8 +370,7 @@ def make_batch_bundle(
{ {
"item_code": item_code, "item_code": item_code,
"warehouse": warehouse, "warehouse": warehouse,
"posting_date": today(), "posting_datetime": combine_datetime(today(), nowtime()),
"posting_time": nowtime(),
"voucher_type": "Stock Entry", "voucher_type": "Stock Entry",
"qty": qty, "qty": qty,
"type_of_transaction": type_of_transaction, "type_of_transaction": type_of_transaction,
@@ -476,9 +481,13 @@ def get_pos_reserved_batch_qty(filters):
def get_available_batches(kwargs): def get_available_batches(kwargs):
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
combine_datetime,
get_auto_batch_nos, get_auto_batch_nos,
) )
if kwargs.get("posting_date"):
kwargs["posting_datetime"] = combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
batchwise_qty = OrderedDict() batchwise_qty = OrderedDict()
batches = get_auto_batch_nos(kwargs) batches = get_auto_batch_nos(kwargs)

View File

@@ -29,8 +29,7 @@
"voucher_no", "voucher_no",
"voucher_detail_no", "voucher_detail_no",
"column_break_aouy", "column_break_aouy",
"posting_date", "posting_datetime",
"posting_time",
"returned_against", "returned_against",
"section_break_wzou", "section_break_wzou",
"is_cancelled", "is_cancelled",
@@ -49,8 +48,7 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"reqd": 1, "reqd": 1
"search_index": 1
}, },
{ {
"fetch_from": "item_code.item_group", "fetch_from": "item_code.item_group",
@@ -79,8 +77,7 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Item Code", "label": "Item Code",
"options": "Item", "options": "Item",
"reqd": 1, "reqd": 1
"search_index": 1
}, },
{ {
"fetch_from": "item_code.item_name", "fetch_from": "item_code.item_name",
@@ -117,8 +114,7 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Voucher No", "label": "Voucher No",
"no_copy": 1, "no_copy": 1,
"options": "voucher_type", "options": "voucher_type"
"search_index": 1
}, },
{ {
"default": "0", "default": "0",
@@ -188,8 +184,7 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Warehouse", "label": "Warehouse",
"mandatory_depends_on": "eval:doc.type_of_transaction != \"Maintenance\"", "mandatory_depends_on": "eval:doc.type_of_transaction != \"Maintenance\"",
"options": "Warehouse", "options": "Warehouse"
"search_index": 1
}, },
{ {
"fieldname": "type_of_transaction", "fieldname": "type_of_transaction",
@@ -211,18 +206,6 @@
"fieldname": "section_break_wzou", "fieldname": "section_break_wzou",
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
{
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date",
"no_copy": 1
},
{
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
"no_copy": 1
},
{ {
"fieldname": "voucher_detail_no", "fieldname": "voucher_detail_no",
"fieldtype": "Data", "fieldtype": "Data",
@@ -251,12 +234,17 @@
"label": "Naming Series", "label": "Naming Series",
"options": "\nSABB-.########", "options": "\nSABB-.########",
"set_only_once": 1 "set_only_once": 1
},
{
"fieldname": "posting_datetime",
"fieldtype": "Datetime",
"label": "Posting Datetime"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-09-15 14:37:26.441742", "modified": "2025-09-24 16:24:48.154853",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Serial and Batch Bundle", "name": "Serial and Batch Bundle",

View File

@@ -74,8 +74,7 @@ class SerialandBatchBundle(Document):
item_group: DF.Link | None item_group: DF.Link | None
item_name: DF.Data | None item_name: DF.Data | None
naming_series: DF.Literal["", "SABB-.########"] naming_series: DF.Literal["", "SABB-.########"]
posting_date: DF.Date | None posting_datetime: DF.Datetime | None
posting_time: DF.Time | None
returned_against: DF.Data | None returned_against: DF.Data | None
total_amount: DF.Float total_amount: DF.Float
total_qty: DF.Float total_qty: DF.Float
@@ -280,8 +279,7 @@ class SerialandBatchBundle(Document):
kwargs.update( kwargs.update(
{ {
"voucher_no": self.voucher_no, "voucher_no": self.voucher_no,
"posting_date": self.posting_date, "posting_datetime": self.posting_datetime,
"posting_time": self.posting_time,
} }
) )
@@ -332,8 +330,7 @@ class SerialandBatchBundle(Document):
kwargs = frappe._dict( kwargs = frappe._dict(
{ {
"item_code": self.item_code, "item_code": self.item_code,
"posting_date": self.posting_date, "posting_datetime": self.posting_datetime,
"posting_time": self.posting_time,
"serial_nos": serial_nos, "serial_nos": serial_nos,
"check_serial_nos": True, "check_serial_nos": True,
} }
@@ -669,8 +666,7 @@ class SerialandBatchBundle(Document):
def get_sle_for_outward_transaction(self): def get_sle_for_outward_transaction(self):
sle = frappe._dict( sle = frappe._dict(
{ {
"posting_date": self.posting_date, "posting_datetime": self.posting_datetime,
"posting_time": self.posting_time,
"item_code": self.item_code, "item_code": self.item_code,
"warehouse": self.warehouse, "warehouse": self.warehouse,
"serial_and_batch_bundle": self.name, "serial_and_batch_bundle": self.name,
@@ -829,11 +825,10 @@ class SerialandBatchBundle(Document):
if not self.voucher_detail_no or self.voucher_detail_no != row.name: if not self.voucher_detail_no or self.voucher_detail_no != row.name:
values_to_set["voucher_detail_no"] = row.name values_to_set["voucher_detail_no"] = row.name
if parent.get("posting_date") and (not self.posting_date or self.posting_date != parent.posting_date): if parent.get("posting_date") and parent.get("posting_time"):
values_to_set["posting_date"] = parent.posting_date or today() posting_datetime = combine_datetime(parent.posting_date, parent.posting_time)
if not self.posting_datetime or self.posting_datetime != posting_datetime:
if parent.get("posting_time") and (not self.posting_time or self.posting_time != parent.posting_time): values_to_set["posting_datetime"] = posting_datetime
values_to_set["posting_time"] = parent.posting_time
if row.get("doctype") == "Packed Item" and row.get("parent_detail_docname"): if row.get("doctype") == "Packed Item" and row.get("parent_detail_docname"):
values_to_set["voucher_detail_no"] = row.get("parent_detail_docname") values_to_set["voucher_detail_no"] = row.get("parent_detail_docname")
@@ -911,9 +906,7 @@ class SerialandBatchBundle(Document):
parent = frappe.qb.DocType("Serial and Batch Bundle") parent = frappe.qb.DocType("Serial and Batch Bundle")
child = frappe.qb.DocType("Serial and Batch Entry") child = frappe.qb.DocType("Serial and Batch Entry")
timestamp_condition = CombineDatetime(parent.posting_date, parent.posting_time) > CombineDatetime( timestamp_condition = parent.posting_datetime > self.posting_datetime
self.posting_date, self.posting_time
)
future_entries = ( future_entries = (
frappe.qb.from_(parent) frappe.qb.from_(parent)
@@ -2083,6 +2076,8 @@ def create_serial_batch_no_ledgers(
if parent_doc.get("doctype") == "Stock Entry": if parent_doc.get("doctype") == "Stock Entry":
warehouse = warehouse or child_row.s_warehouse or child_row.t_warehouse warehouse = warehouse or child_row.s_warehouse or child_row.t_warehouse
posting_datetime = combine_datetime(parent_doc.get("posting_date"), parent_doc.get("posting_time"))
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Serial and Batch Bundle", "doctype": "Serial and Batch Bundle",
@@ -2091,8 +2086,7 @@ def create_serial_batch_no_ledgers(
"warehouse": warehouse, "warehouse": warehouse,
"is_rejected": child_row.is_rejected, "is_rejected": child_row.is_rejected,
"type_of_transaction": type_of_transaction, "type_of_transaction": type_of_transaction,
"posting_date": parent_doc.get("posting_date"), "posting_datetime": posting_datetime,
"posting_time": parent_doc.get("posting_time"),
"company": parent_doc.get("company"), "company": parent_doc.get("company"),
} }
) )
@@ -2128,6 +2122,12 @@ def create_serial_batch_no_ledgers(
return doc return doc
def combine_datetime(date, time=None):
from erpnext.stock.utils import get_combine_datetime
return get_combine_datetime(date, time)
def get_batch(item_code): def get_batch(item_code):
from erpnext.stock.doctype.batch.batch import make_batch from erpnext.stock.doctype.batch.batch import make_batch
@@ -2180,8 +2180,8 @@ def update_serial_batch_no_ledgers(bundle, entries, child_row, parent_doc, wareh
) )
doc.voucher_detail_no = child_row.name doc.voucher_detail_no = child_row.name
doc.posting_date = parent_doc.posting_date doc.posting_datetime = combine_datetime(parent_doc.get("posting_date"), parent_doc.get("posting_time"))
doc.posting_time = parent_doc.posting_time
doc.warehouse = warehouse or doc.warehouse doc.warehouse = warehouse or doc.warehouse
doc.set("entries", []) doc.set("entries", [])
@@ -2269,6 +2269,9 @@ def get_available_serial_nos(kwargs):
elif kwargs.based_on == "Expiry": elif kwargs.based_on == "Expiry":
order_by = "amc_expiry_date" order_by = "amc_expiry_date"
if not kwargs.get("posting_datetime") and kwargs.get("posting_date"):
kwargs["posting_datetime"] = combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
filters = {"item_code": kwargs.item_code} filters = {"item_code": kwargs.item_code}
# ignore_warehouse is used for backdated stock transactions # ignore_warehouse is used for backdated stock transactions
@@ -2285,10 +2288,8 @@ def get_available_serial_nos(kwargs):
if kwargs.get("ignore_serial_nos"): if kwargs.get("ignore_serial_nos"):
ignore_serial_nos.extend(kwargs.get("ignore_serial_nos")) ignore_serial_nos.extend(kwargs.get("ignore_serial_nos"))
if kwargs.get("posting_date"): ignore_serial_nos = list(set(ignore_serial_nos))
if kwargs.get("posting_time") is None: if kwargs.get("posting_datetime"):
kwargs.posting_time = nowtime()
time_based_serial_nos = get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos) time_based_serial_nos = get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos)
if not time_based_serial_nos: if not time_based_serial_nos:
@@ -2692,6 +2693,9 @@ def get_reserved_batches_for_sre(kwargs) -> dict:
def get_auto_batch_nos(kwargs): def get_auto_batch_nos(kwargs):
if not kwargs.get("posting_datetime") and kwargs.get("posting_date"):
kwargs["posting_datetime"] = combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
available_batches = get_available_batches(kwargs) available_batches = get_available_batches(kwargs)
qty = flt(kwargs.qty) qty = flt(kwargs.qty)
@@ -2721,7 +2725,7 @@ def get_auto_batch_nos(kwargs):
if kwargs.based_on == "Expiry": if kwargs.based_on == "Expiry":
available_batches = sorted(available_batches, key=lambda x: x.expiry_date or getdate("9999-12-31")) available_batches = sorted(available_batches, key=lambda x: x.expiry_date or getdate("9999-12-31"))
if not kwargs.get("do_not_check_future_batches") and available_batches and kwargs.get("posting_date"): if not kwargs.get("do_not_check_future_batches") and available_batches and kwargs.get("posting_datetime"):
filter_zero_near_batches(available_batches, kwargs) filter_zero_near_batches(available_batches, kwargs)
if not kwargs.consider_negative_batches: if not kwargs.consider_negative_batches:
@@ -2737,8 +2741,7 @@ def get_auto_batch_nos(kwargs):
def filter_zero_near_batches(available_batches, kwargs): def filter_zero_near_batches(available_batches, kwargs):
kwargs.batch_no = [d.batch_no for d in available_batches] kwargs.batch_no = [d.batch_no for d in available_batches]
del kwargs["posting_date"] del kwargs["posting_datetime"]
del kwargs["posting_time"]
kwargs.do_not_check_future_batches = 1 kwargs.do_not_check_future_batches = 1
available_batches_in_future = get_auto_batch_nos(kwargs) available_batches_in_future = get_auto_batch_nos(kwargs)
@@ -2804,8 +2807,6 @@ def update_available_batches(available_batches, *reserved_batches) -> None:
def get_available_batches(kwargs): def get_available_batches(kwargs):
from erpnext.stock.utils import get_combine_datetime
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
batch_ledger = frappe.qb.DocType("Serial and Batch Entry") batch_ledger = frappe.qb.DocType("Serial and Batch Entry")
batch_table = frappe.qb.DocType("Batch") batch_table = frappe.qb.DocType("Batch")
@@ -2833,23 +2834,15 @@ def get_available_batches(kwargs):
if not kwargs.get("for_stock_levels"): if not kwargs.get("for_stock_levels"):
query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull())) query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
if kwargs.get("posting_date"): if kwargs.get("posting_datetime"):
if kwargs.get("posting_time") is None: timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime
kwargs.posting_time = nowtime()
timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime(
kwargs.posting_date, kwargs.posting_time
)
if kwargs.get("creation"): if kwargs.get("creation"):
timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime( timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime
kwargs.posting_date, kwargs.posting_time
)
timestamp_condition |= ( timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & (
stock_ledger_entry.posting_datetime stock_ledger_entry.creation < kwargs.creation
== get_combine_datetime(kwargs.posting_date, kwargs.posting_time) )
) & (stock_ledger_entry.creation < kwargs.creation)
query = query.where(timestamp_condition) query = query.where(timestamp_condition)
@@ -3035,15 +3028,14 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]:
serial_batch_table.incoming_rate, serial_batch_table.incoming_rate,
bundle_table.voucher_detail_no, bundle_table.voucher_detail_no,
bundle_table.voucher_no, bundle_table.voucher_no,
bundle_table.posting_date, bundle_table.posting_datetime,
bundle_table.posting_time,
) )
.where( .where(
(bundle_table.docstatus == 1) (bundle_table.docstatus == 1)
& (bundle_table.is_cancelled == 0) & (bundle_table.is_cancelled == 0)
& (bundle_table.type_of_transaction.isin(["Inward", "Outward"])) & (bundle_table.type_of_transaction.isin(["Inward", "Outward"]))
) )
.orderby(bundle_table.posting_date, bundle_table.posting_time) .orderby(bundle_table.posting_datetime)
) )
for key, val in kwargs.items(): for key, val in kwargs.items():
@@ -3061,7 +3053,7 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]:
query = query.where(bundle_table[key].isin(val)) query = query.where(bundle_table[key].isin(val))
else: else:
query = query.where(bundle_table[key] == val) query = query.where(bundle_table[key] == val)
elif key in ["posting_date", "posting_time"]: elif key in ["posting_datetime"]:
query = query.where(bundle_table[key] >= val) query = query.where(bundle_table[key] >= val)
else: else:
if isinstance(val, list): if isinstance(val, list):
@@ -3073,8 +3065,6 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]:
def get_stock_ledgers_for_serial_nos(kwargs): def get_stock_ledgers_for_serial_nos(kwargs):
from erpnext.stock.utils import get_combine_datetime
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
query = ( query = (
@@ -3090,23 +3080,15 @@ def get_stock_ledgers_for_serial_nos(kwargs):
.orderby(stock_ledger_entry.creation) .orderby(stock_ledger_entry.creation)
) )
if kwargs.get("posting_date"): if kwargs.get("posting_datetime"):
if kwargs.get("posting_time") is None: timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime
kwargs.posting_time = nowtime()
timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime(
kwargs.posting_date, kwargs.posting_time
)
if kwargs.get("creation"): if kwargs.get("creation"):
timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime( timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime
kwargs.posting_date, kwargs.posting_time
)
timestamp_condition |= ( timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & (
stock_ledger_entry.posting_datetime stock_ledger_entry.creation < kwargs.creation
== get_combine_datetime(kwargs.posting_date, kwargs.posting_time) )
) & (stock_ledger_entry.creation < kwargs.creation)
query = query.where(timestamp_condition) query = query.where(timestamp_condition)
@@ -3129,8 +3111,6 @@ def get_stock_ledgers_for_serial_nos(kwargs):
def get_stock_ledgers_batches(kwargs): def get_stock_ledgers_batches(kwargs):
from erpnext.stock.utils import get_combine_datetime
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
batch_table = frappe.qb.DocType("Batch") batch_table = frappe.qb.DocType("Batch")
@@ -3164,23 +3144,15 @@ def get_stock_ledgers_batches(kwargs):
if not kwargs.get("for_stock_levels"): if not kwargs.get("for_stock_levels"):
query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull())) query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
if kwargs.get("posting_date"): if kwargs.get("posting_datetime"):
if kwargs.get("posting_time") is None: timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime
kwargs.posting_time = nowtime()
timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime(
kwargs.posting_date, kwargs.posting_time
)
if kwargs.get("creation"): if kwargs.get("creation"):
timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime( timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime
kwargs.posting_date, kwargs.posting_time
)
timestamp_condition |= ( timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & (
stock_ledger_entry.posting_datetime stock_ledger_entry.creation < kwargs.creation
== get_combine_datetime(kwargs.posting_date, kwargs.posting_time) )
) & (stock_ledger_entry.creation < kwargs.creation)
query = query.where(timestamp_condition) query = query.where(timestamp_condition)
@@ -3268,3 +3240,7 @@ def get_stock_reco_details(voucher_detail_no):
], ],
as_dict=True, as_dict=True,
) )
def on_doctype_update():
frappe.db.add_index("Serial and Batch Bundle", ["item_code", "warehouse", "posting_datetime", "creation"])

View File

@@ -10,6 +10,7 @@ from frappe.utils import flt, nowtime, today
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
add_serial_batch_ledgers, add_serial_batch_ledgers,
combine_datetime,
make_batch_nos, make_batch_nos,
make_serial_nos, make_serial_nos,
) )
@@ -1271,14 +1272,17 @@ def make_serial_batch_bundle(kwargs):
if kwargs.get("type_of_transaction"): if kwargs.get("type_of_transaction"):
type_of_transaction = kwargs.get("type_of_transaction") type_of_transaction = kwargs.get("type_of_transaction")
posting_datetime = None
if kwargs.get("posting_date"):
posting_datetime = combine_datetime(kwargs.posting_date, kwargs.posting_time or nowtime())
sb = SerialBatchCreation( sb = SerialBatchCreation(
{ {
"item_code": kwargs.item_code, "item_code": kwargs.item_code,
"warehouse": kwargs.warehouse, "warehouse": kwargs.warehouse,
"voucher_type": kwargs.voucher_type, "voucher_type": kwargs.voucher_type,
"voucher_no": kwargs.voucher_no, "voucher_no": kwargs.voucher_no,
"posting_date": kwargs.posting_date, "posting_datetime": posting_datetime,
"posting_time": kwargs.posting_time,
"qty": kwargs.qty, "qty": kwargs.qty,
"avg_rate": kwargs.rate, "avg_rate": kwargs.rate,
"batches": kwargs.batches, "batches": kwargs.batches,

View File

@@ -52,7 +52,7 @@ from erpnext.stock.serial_batch_bundle import (
get_serial_or_batch_items, get_serial_or_batch_items,
) )
from erpnext.stock.stock_ledger import NegativeStockError, get_previous_sle, get_valuation_rate from erpnext.stock.stock_ledger import NegativeStockError, get_previous_sle, get_valuation_rate
from erpnext.stock.utils import get_bin, get_incoming_rate from erpnext.stock.utils import get_bin, get_combine_datetime, get_incoming_rate
class FinishedGoodError(frappe.ValidationError): class FinishedGoodError(frappe.ValidationError):
@@ -1527,8 +1527,7 @@ class StockEntry(StockController):
{ {
"item_code": row.item_code, "item_code": row.item_code,
"warehouse": row.s_warehouse, "warehouse": row.s_warehouse,
"posting_date": self.posting_date, "posting_datetime": get_combine_datetime(self.posting_date, self.posting_time),
"posting_time": self.posting_time,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"voucher_detail_no": row.name, "voucher_detail_no": row.name,
"qty": row.transfer_qty * -1, "qty": row.transfer_qty * -1,
@@ -4058,8 +4057,7 @@ def create_serial_and_batch_bundle(parent_doc, row, child, type_of_transaction=N
"item_code": child.item_code, "item_code": child.item_code,
"warehouse": child.warehouse, "warehouse": child.warehouse,
"type_of_transaction": type_of_transaction, "type_of_transaction": type_of_transaction,
"posting_date": parent_doc.posting_date, "posting_datetime": get_combine_datetime(parent_doc.posting_date, parent_doc.posting_time),
"posting_time": parent_doc.posting_time,
} }
) )

View File

@@ -8,6 +8,7 @@ import frappe
from frappe.utils import cint, flt from frappe.utils import cint, flt
import erpnext import erpnext
from erpnext.stock.utils import get_combine_datetime
if TYPE_CHECKING: if TYPE_CHECKING:
from erpnext.stock.doctype.stock_entry.stock_entry import StockEntry from erpnext.stock.doctype.stock_entry.stock_entry import StockEntry
@@ -140,6 +141,10 @@ def make_stock_entry(**args):
elif args.batches: elif args.batches:
batches = args.batches batches = args.batches
posting_datetime = None
if args.posting_date and args.posting_time:
posting_datetime = get_combine_datetime(args.posting_date, args.posting_time)
bundle_id = ( bundle_id = (
SerialBatchCreation( SerialBatchCreation(
{ {
@@ -151,8 +156,7 @@ def make_stock_entry(**args):
"serial_nos": args.serial_no, "serial_nos": args.serial_no,
"type_of_transaction": "Outward" if args.source else "Inward", "type_of_transaction": "Outward" if args.source else "Inward",
"company": s.company, "company": s.company,
"posting_date": s.posting_date, "posting_datetime": posting_datetime,
"posting_time": s.posting_time,
"rate": args.rate or args.basic_rate, "rate": args.rate or args.basic_rate,
"do_not_submit": True, "do_not_submit": True,
} }

View File

@@ -13,6 +13,7 @@ from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.batch.batch import get_available_batches, get_batch_qty from erpnext.stock.doctype.batch.batch import get_available_batches, get_batch_qty
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
combine_datetime,
get_available_serial_nos, get_available_serial_nos,
) )
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -136,8 +137,7 @@ class StockReconciliation(StockController):
{ {
"item_code": row.item_code, "item_code": row.item_code,
"warehouse": row.warehouse, "warehouse": row.warehouse,
"posting_date": self.posting_date, "posting_datetime": combine_datetime(self.posting_date, self.posting_time),
"posting_time": self.posting_time,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"voucher_no": self.name, "voucher_no": self.name,
"voucher_detail_no": row.name, "voucher_detail_no": row.name,
@@ -243,8 +243,7 @@ class StockReconciliation(StockController):
"doctype": "Serial and Batch Bundle", "doctype": "Serial and Batch Bundle",
"item_code": item.item_code, "item_code": item.item_code,
"warehouse": item.warehouse, "warehouse": item.warehouse,
"posting_date": self.posting_date, "posting_datetime": combine_datetime(self.posting_date, self.posting_time),
"posting_time": self.posting_time,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"type_of_transaction": "Outward", "type_of_transaction": "Outward",
} }
@@ -262,8 +261,7 @@ class StockReconciliation(StockController):
{ {
"item_code": item.item_code, "item_code": item.item_code,
"warehouse": item.warehouse, "warehouse": item.warehouse,
"posting_date": self.posting_date, "posting_datetime": combine_datetime(self.posting_date, self.posting_time),
"posting_time": self.posting_time,
"ignore_warehouse": 1, "ignore_warehouse": 1,
} }
) )

View File

@@ -19,7 +19,7 @@ def get_data(filters):
"Serial and Batch Bundle", "Serial and Batch Bundle",
fields=[ fields=[
"`tabSerial and Batch Bundle`.`voucher_type`", "`tabSerial and Batch Bundle`.`voucher_type`",
"`tabSerial and Batch Bundle`.`posting_date`", "`tabSerial and Batch Bundle`.`posting_datetime` as posting_date",
"`tabSerial and Batch Bundle`.`name`", "`tabSerial and Batch Bundle`.`name`",
"`tabSerial and Batch Bundle`.`company`", "`tabSerial and Batch Bundle`.`company`",
"`tabSerial and Batch Bundle`.`voucher_no`", "`tabSerial and Batch Bundle`.`voucher_no`",
@@ -33,7 +33,7 @@ def get_data(filters):
"`tabSerial and Batch Entry`.`qty`", "`tabSerial and Batch Entry`.`qty`",
], ],
filters=filter_conditions, filters=filter_conditions,
order_by="posting_date", order_by="posting_datetime",
) )
@@ -54,7 +54,7 @@ def get_filter_conditions(filters):
filter_conditions.append( filter_conditions.append(
[ [
"Serial and Batch Bundle", "Serial and Batch Bundle",
"posting_date", "posting_datetime",
"between", "between",
[filters.get("from_date"), filters.get("to_date")], [filters.get("from_date"), filters.get("to_date")],
] ]

View File

@@ -5,7 +5,7 @@ from frappe import _, bold
from frappe.model.naming import NamingSeries, make_autoname, parse_naming_series from frappe.model.naming import NamingSeries, make_autoname, parse_naming_series
from frappe.query_builder import Case from frappe.query_builder import Case
from frappe.query_builder.functions import CombineDatetime, Max, Sum, Timestamp from frappe.query_builder.functions import CombineDatetime, Max, Sum, Timestamp
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, now, nowtime, today from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, now, nowtime, today
from pypika import Order from pypika import Order
from pypika.terms import ExistsCriterion from pypika.terms import ExistsCriterion
@@ -140,8 +140,7 @@ class SerialBatchBundle:
{ {
"item_code": self.item_code, "item_code": self.item_code,
"warehouse": self.warehouse, "warehouse": self.warehouse,
"posting_date": self.sle.posting_date, "posting_datetime": self.sle.posting_datetime,
"posting_time": self.sle.posting_time,
"voucher_type": self.sle.voucher_type, "voucher_type": self.sle.voucher_type,
"voucher_no": self.sle.voucher_no, "voucher_no": self.sle.voucher_no,
"voucher_detail_no": self.sle.voucher_detail_no, "voucher_detail_no": self.sle.voucher_detail_no,
@@ -484,7 +483,7 @@ class SerialBatchBundle:
if status == "Delivered": if status == "Delivered":
warranty_period = frappe.get_cached_value("Item", sle.item_code, "warranty_period") warranty_period = frappe.get_cached_value("Item", sle.item_code, "warranty_period")
if warranty_period: if warranty_period:
warranty_expiry_date = add_days(sle.posting_date, cint(warranty_period)) warranty_expiry_date = add_days(getdate(sle.posting_datetime), cint(warranty_period))
query = query.set(sn_table.warranty_expiry_date, warranty_expiry_date) query = query.set(sn_table.warranty_expiry_date, warranty_expiry_date)
query = query.set(sn_table.warranty_period, warranty_period) query = query.set(sn_table.warranty_period, warranty_period)
else: else:
@@ -509,7 +508,7 @@ class SerialBatchBundle:
sle_doctype.voucher_no, sle_doctype.voucher_no,
sle_doctype.is_cancelled, sle_doctype.is_cancelled,
sle_doctype.item_code, sle_doctype.item_code,
sle_doctype.posting_date, sle_doctype.posting_datetime,
sle_doctype.company, sle_doctype.company,
) )
.where( .where(
@@ -663,7 +662,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
.on(bundle.name == bundle_child.parent) .on(bundle.name == bundle_child.parent)
.select( .select(
bundle_child.serial_no, bundle_child.serial_no,
Max(CombineDatetime(bundle.posting_date, bundle.posting_time)).as_("max_posting_dt"), Max(bundle.posting_datetime).as_("max_posting_dt"),
) )
.where( .where(
(bundle.is_cancelled == 0) (bundle.is_cancelled == 0)
@@ -681,13 +680,8 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
if self.sle.voucher_no: if self.sle.voucher_no:
latest_posting = latest_posting.where(bundle.voucher_no != self.sle.voucher_no) latest_posting = latest_posting.where(bundle.voucher_no != self.sle.voucher_no)
if self.sle.posting_date: if self.sle.posting_datetime:
if self.sle.posting_time is None: timestamp_condition = bundle.posting_datetime <= self.sle.posting_datetime
self.sle.posting_time = nowtime()
timestamp_condition = CombineDatetime(
bundle.posting_date, bundle.posting_time
) <= CombineDatetime(self.sle.posting_date, self.sle.posting_time)
latest_posting = latest_posting.where(timestamp_condition) latest_posting = latest_posting.where(timestamp_condition)
@@ -704,10 +698,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
.join(latest_posting) .join(latest_posting)
.on( .on(
(latest_posting.serial_no == bundle_child.serial_no) (latest_posting.serial_no == bundle_child.serial_no)
& ( & (latest_posting.max_posting_dt == bundle.posting_datetime)
latest_posting.max_posting_dt
== CombineDatetime(bundle.posting_date, bundle.posting_time)
)
) )
.select( .select(
bundle_child.serial_no, bundle_child.serial_no,
@@ -839,19 +830,13 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
child = frappe.qb.DocType("Serial and Batch Entry") child = frappe.qb.DocType("Serial and Batch Entry")
timestamp_condition = "" timestamp_condition = ""
if self.sle.posting_date: if self.sle.posting_datetime:
if self.sle.posting_time is None: timestamp_condition = parent.posting_datetime < self.sle.posting_datetime
self.sle.posting_time = nowtime()
timestamp_condition = CombineDatetime(parent.posting_date, parent.posting_time) < CombineDatetime(
self.sle.posting_date, self.sle.posting_time
)
if self.sle.creation: if self.sle.creation:
timestamp_condition |= ( timestamp_condition |= (parent.posting_datetime == self.sle.posting_datetime) & (
CombineDatetime(parent.posting_date, parent.posting_time) parent.creation < self.sle.creation
== CombineDatetime(self.sle.posting_date, self.sle.posting_time) )
) & (parent.creation < self.sle.creation)
query = ( query = (
frappe.qb.from_(parent) frappe.qb.from_(parent)
@@ -1074,9 +1059,9 @@ class SerialBatchCreation:
self.__dict__.update(item_details) self.__dict__.update(item_details)
def set_other_details(self): def set_other_details(self):
if not self.get("posting_date"): if not self.get("posting_datetime"):
self.posting_date = today() self.posting_datetime = now()
self.__dict__["posting_date"] = self.posting_date self.__dict__["posting_datetime"] = self.posting_datetime
if not self.get("actual_qty"): if not self.get("actual_qty"):
qty = self.get("qty") or self.get("total_qty") qty = self.get("qty") or self.get("total_qty")
@@ -1101,8 +1086,7 @@ class SerialBatchCreation:
new_package.docstatus = 0 new_package.docstatus = 0
new_package.warehouse = self.warehouse new_package.warehouse = self.warehouse
new_package.voucher_no = "" new_package.voucher_no = ""
new_package.posting_date = self.posting_date if hasattr(self, "posting_date") else today() new_package.posting_datetime = self.posting_datetime if hasattr(self, "posting_datetime") else now()
new_package.posting_time = self.posting_time if hasattr(self, "posting_time") else nowtime()
new_package.type_of_transaction = self.type_of_transaction new_package.type_of_transaction = self.type_of_transaction
new_package.returned_against = self.get("returned_against") new_package.returned_against = self.get("returned_against")
@@ -1242,9 +1226,8 @@ class SerialBatchCreation:
elif self.has_serial_no and not self.get("serial_nos"): elif self.has_serial_no and not self.get("serial_nos"):
self.serial_nos = get_serial_nos_for_outward(kwargs) self.serial_nos = get_serial_nos_for_outward(kwargs)
elif not self.has_serial_no and self.has_batch_no and not self.get("batches"): elif not self.has_serial_no and self.has_batch_no and not self.get("batches"):
if self.get("posting_date"): if self.get("posting_datetime"):
kwargs["posting_date"] = self.get("posting_date") kwargs["posting_datetime"] = self.get("posting_datetime")
kwargs["posting_time"] = self.get("posting_time")
self.batches = get_available_batches(kwargs) self.batches = get_available_batches(kwargs)

View File

@@ -1975,9 +1975,6 @@ def get_valuation_rate(
# Get moving average rate of a specific batch number # Get moving average rate of a specific batch number
if warehouse and serial_and_batch_bundle: if warehouse and serial_and_batch_bundle:
sabb = frappe.db.get_value(
"Serial and Batch Bundle", serial_and_batch_bundle, ["posting_date", "posting_time"], as_dict=True
)
batch_obj = BatchNoValuation( batch_obj = BatchNoValuation(
sle=frappe._dict( sle=frappe._dict(
{ {
@@ -1985,8 +1982,9 @@ def get_valuation_rate(
"warehouse": warehouse, "warehouse": warehouse,
"actual_qty": -1, "actual_qty": -1,
"serial_and_batch_bundle": serial_and_batch_bundle, "serial_and_batch_bundle": serial_and_batch_bundle,
"posting_date": sabb.posting_date, "posting_datetime": frappe.get_value(
"posting_time": sabb.posting_time, "Serial and Batch Bundle", serial_and_batch_bundle, "posting_datetime"
),
} }
) )
) )

View File

@@ -2,12 +2,14 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import datetime
import json import json
import frappe import frappe
from frappe import _ from frappe import _
from frappe.query_builder.functions import CombineDatetime, IfNull, Sum from frappe.query_builder.functions import CombineDatetime, IfNull, Sum
from frappe.utils import cstr, flt, get_link_to_form, get_time, getdate, nowdate, nowtime from frappe.utils import cstr, flt, get_link_to_form, get_time, getdate, nowdate, nowtime
from frappe.utils.data import DateTimeLikeObject
import erpnext import erpnext
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
@@ -94,13 +96,13 @@ def get_stock_value_on(
@frappe.whitelist() @frappe.whitelist()
def get_stock_balance( def get_stock_balance(
item_code, item_code: str,
warehouse, warehouse: str | None,
posting_date=None, posting_date: DateTimeLikeObject | None = None,
posting_time=None, posting_time: DateTimeLikeObject | datetime.timedelta | None = None,
with_valuation_rate=False, with_valuation_rate: bool = False,
with_serial_no=False, with_serial_no: bool = False,
inventory_dimensions_dict=None, inventory_dimensions_dict: dict | None = None,
): ):
"""Returns stock balance quantity at given warehouse on given posting date or current date. """Returns stock balance quantity at given warehouse on given posting date or current date.
@@ -146,8 +148,7 @@ def get_stock_balance(
{ {
"item_code": item_code, "item_code": item_code,
"warehouse": warehouse, "warehouse": warehouse,
"posting_date": posting_date, "posting_datetime": get_combine_datetime(posting_date, posting_time),
"posting_time": posting_time,
"ignore_warehouse": 1, "ignore_warehouse": 1,
} }
) )
@@ -247,13 +248,16 @@ def _create_bin(item_code, warehouse):
@frappe.whitelist() @frappe.whitelist()
def get_incoming_rate(args, raise_error_if_no_rate=True, fallbacks: bool = True): def get_incoming_rate(args: dict | str, raise_error_if_no_rate: bool = True, fallbacks: bool = True):
"""Get Incoming Rate based on valuation method""" """Get Incoming Rate based on valuation method"""
from erpnext.stock.stock_ledger import get_previous_sle, get_valuation_rate from erpnext.stock.stock_ledger import get_previous_sle, get_valuation_rate
if isinstance(args, str): if isinstance(args, str):
args = json.loads(args) args = json.loads(args)
if not args.get("posting_datetime") and args.get("posting_date"):
args["posting_datetime"] = get_combine_datetime(args.get("posting_date"), args.get("posting_time"))
in_rate = None in_rate = None
item_details = frappe.get_cached_value( item_details = frappe.get_cached_value(