fix(stock): make Repost Item Valuation dedup Postgres-valid (TIMESTAMP→CombineDatetime)

deduplicate_similar_repost used a raw UPDATE with the MySQL-only two-arg
TIMESTAMP(posting_date, posting_time) constructor, which is invalid on Postgres.

Convert the UPDATE to frappe.qb and replace TIMESTAMP() with CombineDatetime
on the column (portable, and preserves the original NULL semantics so rows with
a NULL posting_time stay excluded); the right-hand side is this document's own
always-set posting datetime, computed in Python via get_combine_datetime to
avoid wrapping literals in a SQL datetime function.

Surgical re-apply: develop's recalculate_valuation_rate field /
_recalculate_valuation_rate method / repost() branch are left intact.

The existing test_repost_item_valuation.test_deduplication directly exercises
this UPDATE; it errors on develop's Postgres and now passes on MariaDB and
Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-06-21 06:06:16 +05:30
parent 4255059846
commit be213d9d3d

View File

@@ -22,6 +22,7 @@ from erpnext.stock.stock_ledger import (
get_items_to_be_repost,
repost_future_sle,
)
from erpnext.stock.utils import get_combine_datetime
RecoverableErrors = (JobTimeoutException, QueryDeadlockError, QueryTimeoutError)
@@ -321,28 +322,26 @@ class RepostItemValuation(Document):
if self.based_on != "Item and Warehouse":
return
filters = {
"item_code": self.item_code,
"warehouse": self.warehouse,
"name": self.name,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
}
frappe.db.sql(
"""
update `tabRepost Item Valuation`
set status = 'Skipped'
WHERE item_code = %(item_code)s
and warehouse = %(warehouse)s
and name != %(name)s
and TIMESTAMP(posting_date, posting_time) > TIMESTAMP(%(posting_date)s, %(posting_time)s)
and docstatus = 1
and status = 'Queued'
and based_on = 'Item and Warehouse'
""",
filters,
)
riv = frappe.qb.DocType("Repost Item Valuation")
(
frappe.qb.update(riv)
.set(riv.status, "Skipped")
.where(
(riv.item_code == self.item_code)
& (riv.warehouse == self.warehouse)
& (riv.name != self.name)
# CombineDatetime on the column is portable (TIMESTAMP() is MySQL-only) and keeps the
# original NULL semantics (rows with NULL posting_time stay excluded); the RHS is this
# doc's own (always-set) posting datetime, computed in Python.
& (
CombineDatetime(riv.posting_date, riv.posting_time)
> get_combine_datetime(self.posting_date, self.posting_time)
)
& (riv.docstatus == 1)
& (riv.status == "Queued")
& (riv.based_on == "Item and Warehouse")
)
).run()
def _recalculate_valuation_rate(self):
doc = frappe.get_doc(self.voucher_type, self.voucher_no)