Compare commits

...

9 Commits

Author SHA1 Message Date
Mihir Kandoi
5c716b0547 Merge pull request #56430 from frappe/mergify/bp/version-16-hotfix/pr-55191
refactor(sales_person_wise_transaction_summary): Replace SQL with que… (backport #55191)
2026-06-24 16:43:29 +05:30
Loic Oberle
b3871a212c refactor(sales_person_wise_transaction_summary): Replace SQL with que… (#55191)
(cherry picked from commit df3d0859a1)
2026-06-24 10:44:33 +00:00
Mihir Kandoi
18400b58ce Merge pull request #56423 from frappe/mergify/bp/version-16-hotfix/pr-56421
fix: exclude virtual child doctypes from deletion in transaction dele… (backport #56421)
2026-06-24 15:37:18 +05:30
ruthra kumar
eee826dfb6 Merge pull request #56419 from frappe/mergify/bp/version-16-hotfix/pr-56417
refactor: configurable timeout on process pcv (backport #56417)
2026-06-24 15:30:14 +05:30
Mihir Kandoi
8a665709d2 fix: exclude virtual child doctypes from deletion in transaction deletion record
(cherry picked from commit 8bd8b28207)
2026-06-24 09:48:25 +00:00
ruthra kumar
267086153b chore: resolve conflicts 2026-06-24 15:08:04 +05:30
ruthra kumar
66b28cf456 refactor: patch, display depends on and json changes
(cherry picked from commit 3da7eefebb)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.py
#	erpnext/patches.txt
2026-06-24 07:58:49 +00:00
ruthra kumar
d389014e57 feat(accounts): add configurable job timeout for Process Period Closing Voucher
Adds a `pcv_job_timeout` Int field (default 3600s) to Accounts Settings
so admins can tune the enqueue timeout for PCV background jobs without
a code change. All three `frappe.enqueue` calls in
`process_period_closing_voucher.py` now read this value at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 13b6c4a165)
2026-06-24 07:58:48 +00:00
mergify[bot]
54c45d7b22 fix: job card timer issue (backport #56405) (#56406)
fix: job card timer issue (#56405)

(cherry picked from commit 21541e3ad3)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2026-06-24 09:21:02 +05:30
7 changed files with 87 additions and 50 deletions

View File

@@ -86,6 +86,7 @@
"period_closing_settings_section",
"ignore_account_closing_balance",
"use_legacy_controller_for_pcv",
"pcv_job_timeout",
"column_break_25",
"reports_tab",
"remarks_section",
@@ -611,6 +612,14 @@
"fieldtype": "Check",
"label": "Use legacy controller for Period Closing Voucher"
},
{
"default": "3600",
"depends_on": "eval: !doc.use_legacy_controller_for_pcv",
"description": "Timeout (in seconds) for each background job enqueued by Process Period Closing Voucher",
"fieldname": "pcv_job_timeout",
"fieldtype": "Int",
"label": "PCV Job Timeout (seconds)"
},
{
"description": "Users with this role will be notified if the asset depreciation gets failed",
"fieldname": "role_to_notify_on_depreciation_failure",
@@ -748,7 +757,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-06-15 18:26:50.778723",
"modified": "2026-06-24 12:59:41.868865",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -90,6 +90,7 @@ class AccountsSettings(Document):
make_payment_via_journal_entry: DF.Check
merge_similar_account_heads: DF.Check
over_billing_allowance: DF.Currency
pcv_job_timeout: DF.Int
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
receivable_payable_remarks_length: DF.Int
reconciliation_queue_size: DF.Int

View File

@@ -95,6 +95,8 @@ def start_pcv_processing(docname: str):
frappe.has_permission("Process Payment Reconciliation", "write", doc=docname, throw=True)
frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Running")
timeout = frappe.db.get_single_value("Accounts Settings", "pcv_job_timeout") or 3600
ppcvd = qb.DocType("Process Period Closing Voucher Detail")
if normal_balances := (
qb.from_(ppcvd)
@@ -121,7 +123,7 @@ def start_pcv_processing(docname: str):
frappe.enqueue(
method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date",
queue="long",
timeout="3600",
timeout=timeout,
is_async=True,
enqueue_after_commit=True,
docname=docname,
@@ -247,6 +249,8 @@ def get_gle_for_closing_account(pcv, dimension_balance, dimensions):
@frappe.whitelist()
def schedule_next_date(docname: str):
timeout = frappe.db.get_single_value("Accounts Settings", "pcv_job_timeout") or 3600
ppcvd = qb.DocType("Process Period Closing Voucher Detail")
if to_process := (
qb.from_(ppcvd)
@@ -272,7 +276,7 @@ def schedule_next_date(docname: str):
frappe.enqueue(
method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date",
queue="long",
timeout="3600",
timeout=timeout,
is_async=True,
enqueue_after_commit=True,
docname=docname,
@@ -302,7 +306,7 @@ def schedule_next_date(docname: str):
frappe.enqueue(
method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.summarize_and_post_ledger_entries",
queue="long",
timeout="3600",
timeout=timeout,
is_async=True,
job_name=job_name,
enqueue_after_commit=True,

View File

@@ -694,10 +694,11 @@ frappe.ui.form.on("Job Card", {
// ── Wire up button click handlers ─────────────────────────────────
if (show_start) {
wrapper.find(".jcd-btn-start").on("click", () => {
const from_time = frappe.datetime.now_datetime();
const has_no_employee = !frm.doc.employee || !frm.doc.employee.length;
if (has_no_employee) {
// Capture the start time only when the employee dialog is submitted, not on click,
// so the time spent selecting the operator is not counted as worked time.
frappe.prompt(
{
fieldtype: "Table MultiSelect",
@@ -707,11 +708,11 @@ frappe.ui.form.on("Job Card", {
reqd: 1,
filters: { status: "Active" },
},
(d) => frm.events.start_timer(frm, from_time, d.employees),
(d) => frm.events.start_timer(frm, frappe.datetime.now_datetime(), d.employees),
__("Assign Job to Employee")
);
} else {
frm.events.start_timer(frm, from_time, frm.doc.employee);
frm.events.start_timer(frm, frappe.datetime.now_datetime(), frm.doc.employee);
}
});
}

View File

@@ -484,3 +484,4 @@ erpnext.patches.v16_0.set_not_applicable_on_german_item_tax_templates
erpnext.patches.v16_0.clear_procedures_from_receivable_report
erpnext.patches.v16_0.migrate_address_contact_custom_fields
erpnext.patches.v16_0.drop_redundant_serial_no_index_from_sabb
execute:frappe.db.set_single_value("Accounts Settings", "pcv_job_timeout", 3600)

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import _, msgprint, qb
from frappe.query_builder import Criterion
from frappe.query_builder import Case, Criterion
from erpnext import get_company_currency
@@ -155,50 +155,60 @@ def get_columns(filters):
def get_entries(filters):
date_field = filters["doc_type"] == "Sales Order" and "transaction_date" or "posting_date"
if filters["doc_type"] == "Sales Order":
qty_field = "delivered_qty"
else:
qty_field = "qty"
conditions, values = get_conditions(filters, date_field)
doc_type = filters["doc_type"]
entries = frappe.db.sql(
"""
SELECT
dt.name, dt.customer, dt.territory, dt.{} as posting_date, dt_item.item_code,
st.sales_person, st.allocated_percentage, dt_item.warehouse,
CASE
WHEN dt.status = "Closed" THEN dt_item.{} * dt_item.conversion_factor
ELSE dt_item.stock_qty
END as stock_qty,
CASE
WHEN dt.status = "Closed" THEN (dt_item.base_net_rate * dt_item.{} * dt_item.conversion_factor)
ELSE dt_item.base_net_amount
END as base_net_amount,
CASE
WHEN dt.status = "Closed" THEN ((dt_item.base_net_rate * dt_item.{} * dt_item.conversion_factor) * st.allocated_percentage/100)
ELSE dt_item.base_net_amount * st.allocated_percentage/100
END as contribution_amt
FROM
`tab{}` dt, `tab{} Item` dt_item, `tabSales Team` st
WHERE
st.parent = dt.name and dt.name = dt_item.parent and st.parenttype = {}
and dt.docstatus = 1 {} order by st.sales_person, dt.name desc
""".format(
date_field,
qty_field,
qty_field,
qty_field,
filters["doc_type"],
filters["doc_type"],
"%s",
conditions,
),
tuple([filters["doc_type"], *values]),
as_dict=1,
date_field = "transaction_date" if doc_type == "Sales Order" else "posting_date"
qty_field = "delivered_qty" if doc_type == "Sales Order" else "qty"
dt = frappe.qb.DocType(doc_type)
dt_item = frappe.qb.DocType(f"{doc_type} Item")
st = frappe.qb.DocType("Sales Team")
calc_qty = dt_item[qty_field] * dt_item.conversion_factor
calc_net_amount = dt_item.base_net_rate * calc_qty
stock_qty_case = Case().when(dt.status == "Closed", calc_qty).else_(dt_item.stock_qty).as_("stock_qty")
base_net_amount_case = (
Case()
.when(dt.status == "Closed", calc_net_amount)
.else_(dt_item.base_net_amount)
.as_("base_net_amount")
)
return entries
contribution_amt_case = (
Case()
.when(dt.status == "Closed", (calc_net_amount * st.allocated_percentage / 100))
.else_(dt_item.base_net_amount * st.allocated_percentage / 100)
.as_("contribution_amt")
)
query = (
frappe.get_query(dt, filters=filters, ignore_permissions=False)
.join(dt_item)
.on(dt.name == dt_item.parent)
.join(st)
.on(dt.name == st.parent)
.select(
dt.name,
dt.customer,
dt.territory,
dt[date_field].as_("posting_date"),
dt_item.item_code,
st.sales_person,
st.allocated_percentage,
dt_item.warehouse,
stock_qty_case,
base_net_amount_case,
contribution_amt_case,
)
.where(st.parenttype == doc_type)
.where(dt.docstatus == 1)
)
query = query.orderby(st.sales_person).orderby(dt.name, order=frappe.qb.desc)
return query.run(as_dict=True)
def get_conditions(filters, date_field):

View File

@@ -318,12 +318,23 @@ class TransactionDeletionRecord(Document):
Returns:
list: List of child table DocType names (Table field options)
"""
return frappe.get_all(
child_tables = frappe.get_all(
"DocField",
filters={"parent": doctype_name, "fieldtype": ["in", ["Table", "Table MultiSelect"]]},
pluck="options",
)
if not child_tables:
return []
child_tables = frappe.get_all(
"DocType",
filters={"name": ["in", child_tables], "is_virtual": 0},
pluck="name",
)
return child_tables
def _get_to_delete_row_infos(self, doctype_name, company_field=None, company=None):
"""Get child tables and document count for a To Delete list row