mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-03 04:09:11 +00:00
Merge pull request #51538 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -93,6 +93,7 @@
|
|||||||
"receivable_payable_remarks_length",
|
"receivable_payable_remarks_length",
|
||||||
"accounts_receivable_payable_tuning_section",
|
"accounts_receivable_payable_tuning_section",
|
||||||
"receivable_payable_fetch_method",
|
"receivable_payable_fetch_method",
|
||||||
|
"default_ageing_range",
|
||||||
"column_break_ntmi",
|
"column_break_ntmi",
|
||||||
"drop_ar_procedures",
|
"drop_ar_procedures",
|
||||||
"legacy_section",
|
"legacy_section",
|
||||||
@@ -657,6 +658,12 @@
|
|||||||
"fieldname": "show_party_balance",
|
"fieldname": "show_party_balance",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Party Balance"
|
"label": "Show Party Balance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "30, 60, 90, 120",
|
||||||
|
"fieldname": "default_ageing_range",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Default Ageing Range"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -664,7 +671,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-11-06 17:48:07.682837",
|
"modified": "2025-12-26 19:46:55.093717",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
@@ -694,4 +701,4 @@
|
|||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class AccountsSettings(Document):
|
|||||||
check_supplier_invoice_uniqueness: DF.Check
|
check_supplier_invoice_uniqueness: DF.Check
|
||||||
create_pr_in_draft_status: DF.Check
|
create_pr_in_draft_status: DF.Check
|
||||||
credit_controller: DF.Link | None
|
credit_controller: DF.Link | None
|
||||||
|
default_ageing_range: DF.Data | None
|
||||||
delete_linked_ledger_entries: DF.Check
|
delete_linked_ledger_entries: DF.Check
|
||||||
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
|
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
|
||||||
enable_common_party_accounting: DF.Check
|
enable_common_party_accounting: DF.Check
|
||||||
|
|||||||
@@ -20,6 +20,23 @@ frappe.ui.form.on("Journal Entry", {
|
|||||||
"Unreconcile Payment Entries",
|
"Unreconcile Payment Entries",
|
||||||
"Bank Transaction",
|
"Bank Transaction",
|
||||||
];
|
];
|
||||||
|
frm.trigger("set_queries");
|
||||||
|
},
|
||||||
|
|
||||||
|
set_queries(frm) {
|
||||||
|
frm.set_query("project", "accounts", function (doc, cdt, cdn) {
|
||||||
|
let row = frappe.get_doc(cdt, cdn);
|
||||||
|
let filters = {
|
||||||
|
company: doc.company,
|
||||||
|
};
|
||||||
|
if (row.party_type == "Customer") {
|
||||||
|
filters.customer = row.party;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.get_project_name",
|
||||||
|
filters,
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import json
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint, scrub
|
from frappe import _, msgprint, scrub
|
||||||
|
from frappe.core.doctype.submission_queue.submission_queue import queue_submission
|
||||||
from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
|
from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -172,15 +173,13 @@ class JournalEntry(AccountsController):
|
|||||||
|
|
||||||
def submit(self):
|
def submit(self):
|
||||||
if len(self.accounts) > 100:
|
if len(self.accounts) > 100:
|
||||||
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
queue_submission(self, "_submit")
|
||||||
self.queue_action("submit", timeout=4600)
|
|
||||||
else:
|
else:
|
||||||
return self._submit()
|
return self._submit()
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
if len(self.accounts) > 100:
|
if len(self.accounts) > 100:
|
||||||
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
queue_submission(self, "_cancel")
|
||||||
self.queue_action("cancel", timeout=4600)
|
|
||||||
else:
|
else:
|
||||||
return self._cancel()
|
return self._cancel()
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ frappe.ui.form.on("Period Closing Voucher", {
|
|||||||
return {
|
return {
|
||||||
filters: [
|
filters: [
|
||||||
["Account", "company", "=", frm.doc.company],
|
["Account", "company", "=", frm.doc.company],
|
||||||
["Account", "is_group", "=", "0"],
|
["Account", "is_group", "=", 0],
|
||||||
["Account", "freeze_account", "=", "No"],
|
["Account", "freeze_account", "=", "No"],
|
||||||
["Account", "root_type", "in", "Liability, Equity"],
|
["Account", "root_type", "in", ["Liability", "Equity"]],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -165,6 +165,10 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route("query-report", "Accounts Payable Summary", { company: filters.company });
|
frappe.set_route("query-report", "Accounts Payable Summary", { company: filters.company });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (frappe.boot.sysdefaults.default_ageing_range) {
|
||||||
|
report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,10 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
|||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route("query-report", "Accounts Payable", { company: filters.company });
|
frappe.set_route("query-report", "Accounts Payable", { company: filters.company });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (frappe.boot.sysdefaults.default_ageing_range) {
|
||||||
|
report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -192,6 +192,10 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route("query-report", "Accounts Receivable Summary", { company: filters.company });
|
frappe.set_route("query-report", "Accounts Receivable Summary", { company: filters.company });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (frappe.boot.sysdefaults.default_ageing_range) {
|
||||||
|
report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
|||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route("query-report", "Accounts Receivable", { company: filters.company });
|
frappe.set_route("query-report", "Accounts Receivable", { company: filters.company });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (frappe.boot.sysdefaults.default_ageing_range) {
|
||||||
|
report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -81,5 +81,11 @@ frappe.query_reports["Trial Balance for Party"] = {
|
|||||||
label: __("Show zero values"),
|
label: __("Show zero values"),
|
||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: "exclude_zero_balance_parties",
|
||||||
|
label: __("Exclude Zero Balance Parties"),
|
||||||
|
fieldtype: "Check",
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,20 +75,20 @@ def get_data(filters, show_party_name):
|
|||||||
closing_debit, closing_credit = toggle_debit_credit(opening_debit + debit, opening_credit + credit)
|
closing_debit, closing_credit = toggle_debit_credit(opening_debit + debit, opening_credit + credit)
|
||||||
row.update({"closing_debit": closing_debit, "closing_credit": closing_credit})
|
row.update({"closing_debit": closing_debit, "closing_credit": closing_credit})
|
||||||
|
|
||||||
# totals
|
|
||||||
for col in total_row:
|
|
||||||
total_row[col] += row.get(col)
|
|
||||||
|
|
||||||
row.update({"currency": company_currency})
|
row.update({"currency": company_currency})
|
||||||
|
|
||||||
has_value = False
|
has_value = False
|
||||||
if opening_debit or opening_credit or debit or credit or closing_debit or closing_credit:
|
if opening_debit or opening_credit or debit or credit or closing_debit or closing_credit:
|
||||||
has_value = True
|
has_value = True
|
||||||
|
# Exclude zero balance parties if filter is set
|
||||||
|
if filters.get("exclude_zero_balance_parties") and not closing_debit and not closing_credit:
|
||||||
|
continue
|
||||||
|
|
||||||
if cint(filters.show_zero_values) or has_value:
|
if cint(filters.show_zero_values) or has_value:
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
# totals
|
||||||
# Add total row
|
for col in total_row:
|
||||||
|
total_row[col] += row.get(col)
|
||||||
|
|
||||||
total_row.update({"party": "'" + _("Totals") + "'", "currency": company_currency})
|
total_row.update({"party": "'" + _("Totals") + "'", "currency": company_currency})
|
||||||
data.append(total_row)
|
data.append(total_row)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2017-10-23 11:38:54.004355",
|
"creation": "2017-10-23 11:38:54.004355",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@@ -250,7 +251,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-11-17 18:35:54.575265",
|
"modified": "2026-01-06 15:48:13.862505",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Repair",
|
"name": "Asset Repair",
|
||||||
@@ -264,6 +265,7 @@
|
|||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
|
"import": 1,
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
@@ -279,6 +281,7 @@
|
|||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
|
"import": 1,
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
@@ -295,4 +298,4 @@
|
|||||||
"title_field": "asset_name",
|
"title_field": "asset_name",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ def boot_session(bootinfo):
|
|||||||
bootinfo.party_account_types = frappe._dict(party_account_types)
|
bootinfo.party_account_types = frappe._dict(party_account_types)
|
||||||
|
|
||||||
bootinfo.sysdefaults.demo_company = frappe.db.get_single_value("Global Defaults", "demo_company")
|
bootinfo.sysdefaults.demo_company = frappe.db.get_single_value("Global Defaults", "demo_company")
|
||||||
|
bootinfo.sysdefaults.default_ageing_range = frappe.db.get_single_value(
|
||||||
|
"Accounts Settings", "default_ageing_range"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_page_info(bootinfo):
|
def update_page_info(bootinfo):
|
||||||
|
|||||||
@@ -140,6 +140,24 @@ frappe.ui.form.on("Stock Entry", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("project", "items", function (doc) {
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.get_project_name",
|
||||||
|
filters: {
|
||||||
|
company: doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("project", function (doc) {
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.get_project_name",
|
||||||
|
filters: {
|
||||||
|
company: doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.add_fetch("bom_no", "inspection_required", "inspection_required");
|
frm.add_fetch("bom_no", "inspection_required", "inspection_required");
|
||||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
|
|
||||||
@@ -914,6 +932,9 @@ frappe.ui.form.on("Stock Entry Detail", {
|
|||||||
|
|
||||||
item_code(frm, cdt, cdn) {
|
item_code(frm, cdt, cdn) {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
|
// since some items may not have image, so empty the image field to avoid setting the image of previous item
|
||||||
|
d.image = "";
|
||||||
|
|
||||||
if (d.item_code) {
|
if (d.item_code) {
|
||||||
var args = {
|
var args = {
|
||||||
item_code: d.item_code,
|
item_code: d.item_code,
|
||||||
|
|||||||
@@ -879,7 +879,7 @@ class StockReconciliation(StockController):
|
|||||||
if row.get(dimension.get("fieldname")):
|
if row.get(dimension.get("fieldname")):
|
||||||
has_dimensions = True
|
has_dimensions = True
|
||||||
|
|
||||||
if self.docstatus == 2 and (not row.batch_no or not row.serial_and_batch_bundle):
|
if self.docstatus == 2:
|
||||||
if row.current_qty and current_bundle:
|
if row.current_qty and current_bundle:
|
||||||
data.actual_qty = -1 * row.current_qty
|
data.actual_qty = -1 * row.current_qty
|
||||||
data.qty_after_transaction = flt(row.current_qty)
|
data.qty_after_transaction = flt(row.current_qty)
|
||||||
|
|||||||
@@ -1645,6 +1645,59 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
|||||||
batch_qty = get_batch_qty(batch_no, warehouse, item_code)
|
batch_qty = get_batch_qty(batch_no, warehouse, item_code)
|
||||||
self.assertEqual(batch_qty, 4)
|
self.assertEqual(batch_qty, 4)
|
||||||
|
|
||||||
|
def test_sabb_cancel_on_stock_reco_cancellation(self):
|
||||||
|
item_code = self.make_item(
|
||||||
|
"Test Item for SABB Cancel on Stock Reco Cancellation",
|
||||||
|
{
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TEST-BATCH-SABBCANC-.###",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
sr = create_stock_reconciliation(
|
||||||
|
item_code=item_code,
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
sr.reload()
|
||||||
|
|
||||||
|
batch_no = get_batch_from_bundle(sr.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
sr1 = create_stock_reconciliation(
|
||||||
|
item_code=item_code,
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=20,
|
||||||
|
rate=100,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
batch_no=batch_no,
|
||||||
|
)
|
||||||
|
|
||||||
|
sr1.reload()
|
||||||
|
|
||||||
|
current_serial_and_batch_bundle = sr1.items[0].current_serial_and_batch_bundle
|
||||||
|
serial_and_batch_bundle = sr1.items[0].serial_and_batch_bundle
|
||||||
|
|
||||||
|
self.assertTrue(current_serial_and_batch_bundle)
|
||||||
|
self.assertTrue(serial_and_batch_bundle)
|
||||||
|
|
||||||
|
sr1.cancel()
|
||||||
|
|
||||||
|
for sabb in [serial_and_batch_bundle, current_serial_and_batch_bundle]:
|
||||||
|
docstatus = frappe.db.get_value(
|
||||||
|
"Serial and Batch Bundle",
|
||||||
|
sabb,
|
||||||
|
"docstatus",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(docstatus, 2)
|
||||||
|
|
||||||
|
|
||||||
def create_batch_item_with_batch(item_name, batch_id):
|
def create_batch_item_with_batch(item_name, batch_id):
|
||||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||||
|
|||||||
@@ -445,6 +445,7 @@ class StockReservationEntry(Document):
|
|||||||
voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor)
|
voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor)
|
||||||
|
|
||||||
allowed_qty = min(self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty))
|
allowed_qty = min(self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty))
|
||||||
|
allowed_qty = flt(allowed_qty, self.precision("reserved_qty"))
|
||||||
qty_to_be_reserved = flt(qty_to_be_reserved, self.precision("reserved_qty"))
|
qty_to_be_reserved = flt(qty_to_be_reserved, self.precision("reserved_qty"))
|
||||||
|
|
||||||
if self.get("_action") != "submit" and self.voucher_type == "Sales Order" and allowed_qty <= 0:
|
if self.get("_action") != "submit" and self.voucher_type == "Sales Order" and allowed_qty <= 0:
|
||||||
@@ -537,6 +538,7 @@ def get_available_qty_to_reserve(
|
|||||||
& (sre.reserved_qty >= sre.delivered_qty)
|
& (sre.reserved_qty >= sre.delivered_qty)
|
||||||
& (sre.status.notin(["Delivered", "Cancelled"]))
|
& (sre.status.notin(["Delivered", "Cancelled"]))
|
||||||
)
|
)
|
||||||
|
.for_update()
|
||||||
)
|
)
|
||||||
|
|
||||||
if ignore_sre:
|
if ignore_sre:
|
||||||
|
|||||||
Reference in New Issue
Block a user