fix(stock): pick list serial batch posting date (#56957)

* fix(stock): fall back to current date/time for serial and batch bundle posting datetime

Pick List has no posting_date/posting_time fields, so creating or updating a
Serial and Batch Bundle from a Pick List row crashed with
"TypeError: combine() argument 1 must be datetime.date, not None". Fall back
to today/now when the parent voucher doesn't carry its own posting date.

Fixes #56951

* fix(stock): accept a plain dict for add_serial_batch_ledgers' doc and child_row

The whitelisted add_serial_batch_ledgers only converted child_row into an
attribute-accessible frappe._dict when it arrived as a JSON string, and doc's
type hint only allowed Document | str. Frappe's JSON API delivers both as
plain dicts (see frappe.app.make_form_dict, which parses the request body
with orjson and only wraps the top-level dict, not nested values), so every
real request was rejected before the handler body ever ran: first with a
FrappeTypeError on doc, and once that's fixed, with an AttributeError on
child_row.serial_and_batch_bundle. parse_json already wraps a plain dict in
frappe._dict (and leaves a real Document instance untouched), so routing
child_row through it unconditionally fixes both.
This commit is contained in:
Pandiyan P
2026-07-09 17:28:03 +05:30
committed by GitHub
parent e2178a5f19
commit 8b3caeb578

View File

@@ -21,6 +21,7 @@ from frappe.utils import (
get_link_to_form, get_link_to_form,
getdate, getdate,
now, now,
nowtime,
parse_json, parse_json,
today, today,
) )
@@ -2184,12 +2185,11 @@ def get_reference_serial_and_batch_bundle(child_row):
def add_serial_batch_ledgers( def add_serial_batch_ledgers(
entries: list | str, entries: list | str,
child_row: PurchaseReceiptItem | dict | str, child_row: PurchaseReceiptItem | dict | str,
doc: Document | str, doc: Document | dict | str,
warehouse: str | None = None, warehouse: str | None = None,
do_not_save: bool = False, do_not_save: bool = False,
): ):
if isinstance(child_row, str): child_row = parse_json(child_row)
child_row = frappe._dict(parse_json(child_row))
if isinstance(entries, str): if isinstance(entries, str):
entries = parse_json(entries) entries = parse_json(entries)
@@ -2221,7 +2221,9 @@ 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")) posting_datetime = combine_datetime(
parent_doc.get("posting_date") or today(), parent_doc.get("posting_time") or nowtime()
)
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
@@ -2338,7 +2340,9 @@ 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_datetime = combine_datetime(parent_doc.get("posting_date"), parent_doc.get("posting_time")) doc.posting_datetime = combine_datetime(
parent_doc.get("posting_date") or today(), parent_doc.get("posting_time") or nowtime()
)
doc.warehouse = warehouse or doc.warehouse doc.warehouse = warehouse or doc.warehouse
doc.set("entries", []) doc.set("entries", [])