Merge pull request #48979 from frappe/version-15-hotfix

chore: release v15
This commit is contained in:
ruthra kumar
2025-08-06 08:06:33 +05:30
committed by GitHub
64 changed files with 444 additions and 172 deletions

View File

@@ -1,7 +1,6 @@
# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
name: Skipped Patch Test
permissions: none
on:
pull_request:
@@ -12,6 +11,9 @@ on:
- "**.html"
- "**.csv"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Entire Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
fetch-depth: 0
persist-credentials: false

View File

@@ -1,7 +1,6 @@
# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
name: Skipped Tests
permissions: {}
on:
pull_request:
@@ -11,6 +10,9 @@ on:
- "**.md"
- "**.html"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest

View File

@@ -3,22 +3,21 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @khushi8112 @deepeshgarg007
erpnext/regional @deepeshgarg007 @ruthra-kumar
erpnext/selling @deepeshgarg007 @ruthra-kumar
erpnext/support/ @deepeshgarg007
pos*
erpnext/accounts/ @ruthra-kumar
erpnext/assets/ @khushi8112
erpnext/regional @ruthra-kumar
erpnext/selling @ruthra-kumar
erpnext/support/ @ruthra-kumar
erpnext/buying/ @rohitwaghchaure
erpnext/buying/ @rohitwaghchaure @mihir-kandoi
erpnext/maintenance/ @rohitwaghchaure
erpnext/manufacturing/ @rohitwaghchaure
erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi
erpnext/quality_management/ @rohitwaghchaure
erpnext/stock/ @rohitwaghchaure
erpnext/subcontracting @rohitwaghchaure
erpnext/stock/ @rohitwaghchaure @mihir-kandoi
erpnext/subcontracting @mihir-kandoi
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure
erpnext/patches/ @deepeshgarg007
erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
erpnext/patches/ @ruthra-kumar
.github/ @deepeshgarg007
.github/ @ruthra-kumar
pyproject.toml @akhilnarang

View File

@@ -304,7 +304,9 @@ class Account(NestedSet):
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
self.currency_explicitly_specified = False
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
gl_currency = frappe.db.get_value(
"GL Entry", {"account": self.name, "is_cancelled": 0}, "account_currency"
)
if gl_currency and self.account_currency != gl_currency:
if frappe.db.get_value("GL Entry", {"account": self.name}):

View File

@@ -111,17 +111,15 @@ class AccountingDimension(Document):
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:
doclist = get_doctypes_with_dimensions()
doc_count = len(get_accounting_dimensions())
count = 0
repostable_doctypes = get_allowed_types_from_settings()
repostable_doctypes = get_allowed_types_from_settings(child_doc=True)
for doctype in doclist:
if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break"
else:
insert_after_field = "accounting_dimensions_section"
df = {
"fieldname": doc.fieldname,
"label": doc.label,

View File

@@ -109,6 +109,7 @@ class BankAccount(Document):
"party_type": self.party_type,
"party": self.party,
"is_company_account": self.is_company_account,
"company": self.company,
"is_default": 1,
"disabled": 0,
},

View File

@@ -9,7 +9,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Sum
from frappe.utils import cint, flt
from frappe.utils import cint, create_batch, flt
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
@@ -377,16 +377,17 @@ def auto_reconcile_vouchers(
bank_transactions = get_bank_transactions(bank_account)
if len(bank_transactions) > 10:
frappe.enqueue(
method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile",
queue="long",
bank_transactions=bank_transactions,
from_date=from_date,
to_date=to_date,
filter_by_reference_date=filter_by_reference_date,
from_reference_date=from_reference_date,
to_reference_date=to_reference_date,
)
for bank_transaction_batch in create_batch(bank_transactions, 1000):
frappe.enqueue(
method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile",
queue="long",
bank_transactions=bank_transaction_batch,
from_date=from_date,
to_date=to_date,
filter_by_reference_date=filter_by_reference_date,
from_reference_date=from_reference_date,
to_reference_date=to_reference_date,
)
frappe.msgprint(_("Auto Reconciliation has started in the background"))
else:
start_auto_reconcile(

View File

@@ -4,6 +4,8 @@
import unittest
import frappe
from frappe.query_builder.functions import Sum
from frappe.tests.utils import change_settings
from frappe.utils import add_days, today
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
@@ -190,6 +192,31 @@ class TestCostCenterAllocation(unittest.TestCase):
coa2.cancel()
jv.cancel()
@change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
def test_debit_credit_on_cost_center_allocation_for_commercial_rounding(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
cca = create_cost_center_allocation(
"_Test Company",
"Main Cost Center 1 - _TC",
{"Sub Cost Center 2 - _TC": 50, "Sub Cost Center 3 - _TC": 50},
)
si = create_sales_invoice(rate=145.65, cost_center="Main Cost Center 1 - _TC")
gl_entry = frappe.qb.DocType("GL Entry")
gl_entries = (
frappe.qb.from_(gl_entry)
.select(Sum(gl_entry.credit).as_("cr"), Sum(gl_entry.debit).as_("dr"))
.where(gl_entry.voucher_type == "Sales Invoice")
.where(gl_entry.voucher_no == si.name)
).run(as_dict=1)
self.assertEqual(gl_entries[0].cr, gl_entries[0].dr)
si.cancel()
cca.cancel()
def create_cost_center_allocation(
company,

View File

@@ -8,4 +8,14 @@ frappe.ui.form.on("Payment Gateway Account", {
frm.set_df_property("payment_gateway", "read_only", 1);
}
},
setup(frm) {
frm.set_query("payment_account", function () {
return {
filters: {
company: frm.doc.company,
},
};
});
},
});

View File

@@ -6,6 +6,7 @@
"field_order": [
"payment_gateway",
"payment_channel",
"company",
"is_default",
"column_break_4",
"payment_account",
@@ -70,11 +71,21 @@
"fieldtype": "Select",
"label": "Payment Channel",
"options": "\nEmail\nPhone"
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"print_hide": 1,
"remember_last_selected_value": 1,
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-09-20 13:30:27.722852",
"modified": "2025-07-14 16:49:55.210352",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Gateway Account",
@@ -95,4 +106,4 @@
],
"sort_field": "modified",
"sort_order": "DESC"
}
}

View File

@@ -15,6 +15,7 @@ class PaymentGatewayAccount(Document):
if TYPE_CHECKING:
from frappe.types import DF
company: DF.Link
currency: DF.ReadOnly | None
is_default: DF.Check
message: DF.SmallText | None
@@ -24,7 +25,8 @@ class PaymentGatewayAccount(Document):
# end: auto-generated types
def autoname(self):
self.name = self.payment_gateway + " - " + self.currency
abbr = frappe.db.get_value("Company", self.company, "abbr")
self.name = self.payment_gateway + " - " + self.currency + " - " + abbr
def validate(self):
self.currency = frappe.get_cached_value("Account", self.payment_account, "account_currency")
@@ -34,13 +36,15 @@ class PaymentGatewayAccount(Document):
def update_default_payment_gateway(self):
if self.is_default:
frappe.db.sql(
"""update `tabPayment Gateway Account` set is_default = 0
where is_default = 1 """
frappe.db.set_value(
"Payment Gateway Account",
{"is_default": 1, "name": ["!=", self.name], "company": self.company},
"is_default",
0,
)
def set_as_default_if_not_set(self):
if not frappe.db.get_value(
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name"
if not frappe.db.exists(
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name), "company": self.company}
):
self.is_default = 1

View File

@@ -9,6 +9,14 @@ frappe.ui.form.on("Payment Request", {
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
};
});
frm.set_query("payment_gateway_account", function () {
return {
filters: {
company: frm.doc.company,
},
};
});
},
});

View File

@@ -539,7 +539,9 @@ def make_payment_request(**args):
if args.dt not in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST:
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt)))
ref_doc = frappe.get_doc(args.dt, args.dn)
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
if not args.get("company"):
args.company = ref_doc.company
gateway_account = get_gateway_details(args) or frappe._dict()
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
@@ -782,7 +784,7 @@ def get_gateway_details(args): # nosemgrep
"""
Return gateway and payment account of default payment gateway
"""
gateway_account = args.get("payment_gateway_account", {"is_default": 1})
gateway_account = args.get("payment_gateway_account", {"is_default": 1, "company": args.company})
if gateway_account:
return get_payment_gateway_account(gateway_account)

View File

@@ -28,12 +28,14 @@ payment_method = [
"payment_gateway": "_Test Gateway",
"payment_account": "_Test Bank - _TC",
"currency": "INR",
"company": "_Test Company",
},
{
"doctype": "Payment Gateway Account",
"payment_gateway": "_Test Gateway",
"payment_account": "_Test Bank USD - _TC",
"currency": "USD",
"company": "_Test Company",
},
]
@@ -46,7 +48,11 @@ class TestPaymentRequest(FrappeTestCase):
for method in payment_method:
if not frappe.db.get_value(
"Payment Gateway Account",
{"payment_gateway": method["payment_gateway"], "currency": method["currency"]},
{
"payment_gateway": method["payment_gateway"],
"currency": method["currency"],
"company": method["company"],
},
"name",
):
frappe.get_doc(method).insert(ignore_permissions=True)
@@ -60,7 +66,7 @@ class TestPaymentRequest(FrappeTestCase):
dt="Sales Order",
dn=so_inr.name,
recipient_id="saurabh@erpnext.com",
payment_gateway_account="_Test Gateway - INR",
payment_gateway_account="_Test Gateway - INR - _TC",
)
self.assertEqual(pr.reference_doctype, "Sales Order")
@@ -74,7 +80,7 @@ class TestPaymentRequest(FrappeTestCase):
dt="Sales Invoice",
dn=si_usd.name,
recipient_id="saurabh@erpnext.com",
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
)
self.assertEqual(pr.reference_doctype, "Sales Invoice")
@@ -95,7 +101,7 @@ class TestPaymentRequest(FrappeTestCase):
party="_Test Supplier USD",
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
submit_doc=1,
return_doc=1,
)
@@ -119,7 +125,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=purchase_invoice.name,
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
return_doc=1,
)
@@ -138,7 +144,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=purchase_invoice.name,
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
return_doc=1,
)
@@ -162,7 +168,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=so_inr.name,
recipient_id="saurabh@erpnext.com",
mute_email=1,
payment_gateway_account="_Test Gateway - INR",
payment_gateway_account="_Test Gateway - INR - _TC",
submit_doc=1,
return_doc=1,
)
@@ -184,7 +190,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=si_usd.name,
recipient_id="saurabh@erpnext.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
submit_doc=1,
return_doc=1,
)
@@ -228,7 +234,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=si_usd.name,
recipient_id="saurabh@erpnext.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
submit_doc=1,
return_doc=1,
)

View File

@@ -301,6 +301,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1573,7 +1574,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2025-07-17 16:51:40.886083",
"modified": "2025-08-04 22:22:31.471752",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@@ -78,18 +78,18 @@
"reqd": 1
},
{
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
"depends_on": "eval:(!doc.enable_auto_email && doc.report == 'General Ledger');",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"mandatory_depends_on": "eval:doc.frequency == '';"
"mandatory_depends_on": "eval:(!doc.enable_auto_email && doc.report == \"General Ledger\") "
},
{
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
"depends_on": "eval:(!doc.enable_auto_email && doc.report == 'General Ledger');",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"mandatory_depends_on": "eval:doc.frequency == '';"
"mandatory_depends_on": "eval:(!doc.enable_auto_email && doc.report == \"General Ledger\") "
},
{
"fieldname": "cost_center",
@@ -330,7 +330,8 @@
"depends_on": "eval:(doc.report == 'Accounts Receivable');",
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date"
"label": "Posting Date",
"mandatory_depends_on": "eval:(doc.report == 'Accounts Receivable');"
},
{
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
@@ -400,7 +401,7 @@
}
],
"links": [],
"modified": "2025-07-08 16:52:12.602384",
"modified": "2025-08-04 18:21:12.603623",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",

View File

@@ -3,7 +3,7 @@
import frappe
from frappe.model.document import Document
from frappe.utils import getdate
from frappe.utils import create_batch, getdate
from erpnext.accounts.doctype.subscription.subscription import DateTimeLikeObject, process_all
@@ -23,7 +23,23 @@ class ProcessSubscription(Document):
# end: auto-generated types
def on_submit(self):
process_all(subscription=self.subscription, posting_date=self.posting_date)
self.process_all_subscription()
def process_all_subscription(self):
filters = {"status": ("!=", "Cancelled")}
if self.subscription:
filters["name"] = self.subscription
subscriptions = frappe.get_all("Subscription", filters, pluck="name")
for subscription in create_batch(subscriptions, 500):
frappe.enqueue(
method="erpnext.accounts.doctype.subscription.subscription.process_all",
queue="long",
subscription=subscription,
posting_date=self.posting_date,
)
def create_subscription_process(

View File

@@ -319,6 +319,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1650,7 +1651,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2025-04-09 16:49:22.175081",
"modified": "2025-08-04 19:19:11.380664",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -5,6 +5,7 @@ import inspect
import frappe
from frappe import _, qb
from frappe.desk.form.linked_with import get_child_tables_of_doctypes
from frappe.model.document import Document
from frappe.utils.data import comma_and
@@ -208,13 +209,29 @@ def start_repost(account_repost_doc=str) -> None:
doc.make_gl_entries()
def get_allowed_types_from_settings():
return [
def get_allowed_types_from_settings(child_doc: bool = False):
repost_docs = [
x.document_type
for x in frappe.db.get_all(
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
)
]
result = repost_docs
if repost_docs and child_doc:
result.extend(get_child_docs(repost_docs))
return result
def get_child_docs(doc: list) -> list:
child_doc = []
doc = get_child_tables_of_doctypes(doc)
for child_list in doc.values():
for child in child_list:
if child.get("child_table"):
child_doc.append(child["child_table"])
return child_doc
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):

View File

@@ -1,9 +1,14 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
import frappe
from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import get_child_docs
class RepostAccountingLedgerSettings(Document):
# begin: auto-generated types
@@ -17,6 +22,24 @@ class RepostAccountingLedgerSettings(Document):
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes
allowed_types: DF.Table[RepostAllowedTypes]
# end: auto-generated types
pass
# end: auto-generated types
def validate(self):
self.update_property_for_accounting_dimension()
def update_property_for_accounting_dimension(self):
doctypes = [entry.document_type for entry in self.allowed_types if entry.allowed]
if not doctypes:
return
doctypes += get_child_docs(doctypes)
set_allow_on_submit_for_dimension_fields(doctypes)
def set_allow_on_submit_for_dimension_fields(doctypes):
for dt in doctypes:
meta = frappe.get_meta(dt)
for dimension in get_accounting_dimensions():
df = meta.get_field(dimension)
if df and not df.allow_on_submit:
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)

View File

@@ -379,6 +379,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"hide_days": 1,
@@ -2189,7 +2190,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-06-26 14:06:56.773552",
"modified": "2025-08-04 19:20:28.732039",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -1370,8 +1370,9 @@ class SalesInvoice(SellingController):
)
asset.db_set("disposal_date", None)
add_asset_activity(asset.name, _("Asset returned"))
asset_status = asset.get_status()
if asset.calculate_depreciation:
if asset.calculate_depreciation and not asset_status == "Fully Depreciated":
posting_date = (
frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
if self.is_return

View File

@@ -756,18 +756,14 @@ def get_prorata_factor(
return diff / plan_days
def process_all(subscription: str | None = None, posting_date: DateTimeLikeObject | None = None) -> None:
def process_all(subscription: list, posting_date: DateTimeLikeObject | None = None) -> None:
"""
Task to updates the status of all `Subscription` apart from those that are cancelled
"""
filters = {"status": ("!=", "Cancelled")}
if subscription:
filters["name"] = subscription
for subscription in frappe.get_all("Subscription", filters, pluck="name"):
for subscription_name in subscription:
try:
subscription = frappe.get_doc("Subscription", subscription)
subscription = frappe.get_doc("Subscription", subscription_name)
subscription.process(posting_date)
frappe.db.commit()
except frappe.ValidationError:

View File

@@ -75,7 +75,7 @@
},
{
"default": "0",
"description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
"description": "Only payment entries with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
"fieldname": "consider_party_ledger_amount",
"fieldtype": "Check",
"label": "Consider Entire Party Ledger Amount",
@@ -102,10 +102,11 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-07-27 21:47:34.396071",
"modified": "2025-07-30 07:13:51.785735",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withholding Category",
"naming_rule": "Set by user",
"owner": "Administrator",
"permissions": [
{
@@ -148,4 +149,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -186,6 +186,15 @@ def process_gl_map(gl_map, merge_entries=True, precision=None, from_repost=False
def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_repost=False):
round_off_account, default_currency = frappe.get_cached_value(
"Company", gl_map[0].company, ["round_off_account", "default_currency"]
)
if not precision:
precision = get_field_precision(
frappe.get_meta("GL Entry").get_field("debit"),
currency=default_currency,
)
new_gl_map = []
for d in gl_map:
cost_center = d.get("cost_center")
@@ -203,6 +212,11 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_r
new_gl_map.append(d)
continue
if d.account == round_off_account:
d.cost_center = cost_center_allocation[0][0]
new_gl_map.append(d)
continue
for sub_cost_center, percentage in cost_center_allocation:
gle = copy.deepcopy(d)
gle.cost_center = sub_cost_center

View File

@@ -197,6 +197,11 @@ frappe.query_reports["General Ledger"] = {
label: __("Show Net Values in Party Account"),
fieldtype: "Check",
},
{
fieldname: "show_amount_in_company_currency",
label: __("Show Credit / Debit in Company Currency"),
fieldtype: "Check",
},
{
fieldname: "add_values_in_transaction_currency",
label: __("Add Columns in Transaction Currency"),

View File

@@ -605,6 +605,18 @@ def get_columns(filters):
company = filters.get("company") or get_default_company()
filters["presentation_currency"] = currency = get_company_currency(company)
company_currency = get_company_currency(filters.get("company") or get_default_company())
if (
filters.get("show_amount_in_company_currency")
and filters["presentation_currency"] != company_currency
):
frappe.throw(
_(
f'Presentation Currency cannot be {frappe.bold(filters["presentation_currency"])} , When {frappe.bold("Show Credit / Debit in Company Currency")} is enabled.'
)
)
columns = [
{
"label": _("GL Entry"),

View File

@@ -46,6 +46,7 @@ def get_ordered_to_be_billed_data(args, filters=None):
child_doctype.item_name,
child_doctype.description,
project_field,
doctype.company,
)
.where(
(doctype.docstatus == 1)

View File

@@ -46,6 +46,7 @@ class PaymentLedger:
against_voucher_no=ple.against_voucher_no,
amount=ple.amount,
currency=ple.account_currency,
company=ple.company,
)
if self.filters.include_account_currency:
@@ -77,6 +78,7 @@ class PaymentLedger:
against_voucher_no="Outstanding:",
amount=total,
currency=voucher_data[0].currency,
company=voucher_data[0].company,
)
if self.filters.include_account_currency:
@@ -85,7 +87,12 @@ class PaymentLedger:
voucher_data.append(entry)
# empty row
voucher_data.append(frappe._dict())
voucher_data.append(
frappe._dict(
currency=voucher_data[0].currency,
company=voucher_data[0].company,
)
)
self.data.extend(voucher_data)
def build_conditions(self):
@@ -130,7 +137,6 @@ class PaymentLedger:
)
def get_columns(self):
company_currency = frappe.get_cached_value("Company", self.filters.get("company"), "default_currency")
options = None
self.columns.append(
dict(
@@ -195,7 +201,7 @@ class PaymentLedger:
label=_("Amount"),
fieldname="amount",
fieldtype="Currency",
options=company_currency,
options="Company:company:default_currency",
width="100",
)
)

View File

@@ -45,6 +45,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
gle_map = get_gle_map(tds_docs)
out = []
entries = {}
for name, details in gle_map.items():
for entry in details:
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
@@ -119,8 +120,13 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
"supplier_invoice_date": bill_date,
}
)
out.append(row)
key = entry.voucher_no
if key in entries:
entries[key]["tax_amount"] += tax_amount
else:
entries[key] = row
out = list(entries.values())
out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
return out

View File

@@ -118,7 +118,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, filters=None):
len(account_currencies) == 1
and account_currency == presentation_currency
and not exchange_gain_or_loss
):
) and not filters.get("show_amount_in_company_currency"):
entry["debit"] = debit_in_account_currency
entry["credit"] = credit_in_account_currency
else:

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
from collections import defaultdict
from json import loads
from typing import TYPE_CHECKING, Optional
@@ -1339,6 +1340,7 @@ def create_payment_gateway_account(gateway, payment_channel="Email"):
"payment_account": bank_account.name,
"currency": bank_account.account_currency,
"payment_channel": payment_channel,
"company": company,
}
).insert(ignore_permissions=True, ignore_if_duplicate=True)
@@ -2419,25 +2421,37 @@ def sync_auto_reconcile_config(auto_reconciliation_job_trigger: int = 15):
).save()
def get_link_fields_grouped_by_option(doctype):
meta = frappe.get_meta(doctype)
link_fields_map = defaultdict(list)
for df in meta.fields:
if df.fieldtype == "Link" and df.options and not df.ignore_user_permissions:
link_fields_map[df.options].append(df.fieldname)
return link_fields_map
def build_qb_match_conditions(doctype, user=None) -> list:
match_filters = build_match_conditions(doctype, user, False)
link_fields_map = get_link_fields_grouped_by_option(doctype)
criterion = []
apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions")
if match_filters:
from frappe import qb
_dt = qb.DocType(doctype)
for filter in match_filters:
for d, names in filter.items():
fieldname = d.lower().replace(" ", "_")
field = _dt[fieldname]
for link_option, allowed_values in filter.items():
fieldnames = link_fields_map.get(link_option, [])
cond = field.isin(names)
if not apply_strict_user_permissions:
cond = (Coalesce(field, "") == "") | field.isin(names)
for fieldname in fieldnames:
field = _dt[fieldname]
cond = field.isin(allowed_values)
criterion.append(cond)
if not apply_strict_user_permissions:
cond = (Coalesce(field, "") == "") | cond
criterion.append(cond)
return criterion

View File

@@ -1097,7 +1097,7 @@ def make_journal_entry(asset_name):
je.voucher_type = "Depreciation Entry"
je.naming_series = depreciation_series
je.company = asset.company
je.remark = f"Depreciation Entry against asset {asset_name}"
je.remark = _("Depreciation Entry against asset {0}").format(asset_name)
je.append(
"accounts",

View File

@@ -277,7 +277,9 @@ def _make_journal_entry_for_depreciation(
je.posting_date = depr_schedule.schedule_date
je.company = asset.company
je.finance_book = asset_depr_schedule_doc.finance_book
je.remark = f"Depreciation Entry against {asset.name} worth {depr_schedule.depreciation_amount}"
je.remark = _("Depreciation Entry against {0} worth {1}").format(
asset.name, depr_schedule.depreciation_amount
)
credit_entry = {
"account": credit_account,

View File

@@ -346,6 +346,33 @@ class TestAsset(AssetSetup):
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_status_after_sales_invoice_cancel(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-04-01",
purchase_date="2020-04-01",
expected_value_after_useful_life=0,
total_number_of_depreciations=5,
opening_number_of_booked_depreciations=2,
frequency_of_depreciation=12,
depreciation_start_date="2023-03-31",
opening_accumulated_depreciation=24000,
gross_purchase_amount=60000,
submit=1,
)
si = create_sales_invoice(
item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23")
)
asset.load_from_db()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
si.cancel()
asset.load_from_db()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_gle_made_by_asset_sale_for_existing_asset(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice

View File

@@ -98,20 +98,41 @@ class AssetDepreciationSchedule(Document):
)
def on_submit(self):
self.validate_asset()
self.db_set("status", "Active")
def before_cancel(self):
def validate_asset(self):
asset = frappe.get_doc("Asset", self.asset)
if not asset.calculate_depreciation:
frappe.throw(
_("Asset {0} is not set to calculate depreciation.").format(
get_link_to_form("Asset", self.asset)
)
)
if asset.docstatus != 1:
frappe.throw(
_("Asset {0} is not submitted. Please submit the asset before proceeding.").format(
get_link_to_form("Asset", self.asset)
)
)
def on_cancel(self):
self.db_set("status", "Cancelled")
if not self.flags.should_not_cancel_depreciation_entries:
self.cancel_depreciation_entries()
def cancel_depreciation_entries(self):
for d in self.get("depreciation_schedule"):
if d.journal_entry:
je_status = frappe.db.get_value("Journal Entry", d.journal_entry, "docstatus")
if je_status == 0:
frappe.throw(
_(
"Cannot cancel Asset Depreciation Schedule {0} as it has a draft journal entry {1}."
).format(self.name, d.journal_entry)
)
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
def on_cancel(self):
self.db_set("status", "Cancelled")
def update_shift_depr_schedule(self):
if not self.shift_based or self.docstatus != 0:
return

View File

@@ -3,13 +3,13 @@ frappe.listview_settings["Asset Maintenance Log"] = {
has_indicator_for_draft: 1,
get_indicator: function (doc) {
if (doc.maintenance_status == "Planned") {
return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status];
return [__(doc.maintenance_status), "orange", "maintenance_status,=," + doc.maintenance_status];
} else if (doc.maintenance_status == "Completed") {
return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status];
return [__(doc.maintenance_status), "green", "maintenance_status,=," + doc.maintenance_status];
} else if (doc.maintenance_status == "Cancelled") {
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
return [__(doc.maintenance_status), "red", "maintenance_status,=," + doc.maintenance_status];
} else if (doc.maintenance_status == "Overdue") {
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
return [__(doc.maintenance_status), "red", "maintenance_status,=," + doc.maintenance_status];
}
},
};

View File

@@ -720,6 +720,7 @@ def close_or_unclose_purchase_orders(names, status):
def set_missing_values(source, target):
target.run_method("set_missing_values")
target.run_method("calculate_taxes_and_totals")
target.run_method("set_use_serial_batch_fields")
@frappe.whitelist()

View File

@@ -51,7 +51,7 @@ def get_columns(filters):
},
{
"label": _("Requestor"),
"options": "Employee",
"options": "User",
"fieldname": "requestor",
"fieldtype": "Link",
"width": 140,

View File

@@ -153,7 +153,12 @@ class OpportunitySummaryBySalesStage:
}[self.filters.get("based_on")]
if self.filters.get("based_on") == "Opportunity Owner":
if d.get(based_on) == "[]" or d.get(based_on) is None or d.get(based_on) == "Not Assigned":
if (
d.get(based_on) == "[]"
or d.get(based_on) is None
or d.get(based_on) == "Not Assigned"
or d.get(based_on) == ""
):
assignments = ["Not Assigned"]
else:
assignments = json.loads(d.get(based_on))

View File

@@ -60,7 +60,7 @@ def import_genericode():
"doctype": "File",
"attached_to_doctype": "Code List",
"attached_to_name": code_list.name,
"folder": "Home/Attachments",
"folder": frappe.db.get_value("File", {"is_attachments_folder": 1}),
"file_name": frappe.local.uploaded_filename,
"file_url": frappe.local.uploaded_file_url,
"is_private": 1,

View File

@@ -65,6 +65,7 @@
"fieldname": "hour_rate",
"fieldtype": "Currency",
"label": "Hour Rate",
"non_negative": 1,
"oldfieldname": "hour_rate",
"oldfieldtype": "Currency",
"options": "currency",
@@ -78,6 +79,7 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "Operation Time",
"non_negative": 1,
"oldfieldname": "time_in_mins",
"oldfieldtype": "Currency",
"reqd": 1
@@ -194,7 +196,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-11-04 17:17:16.986941",
"modified": "2025-07-31 16:17:47.287117",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",

View File

@@ -42,6 +42,7 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "Qty",
"non_negative": 1,
"reqd": 1
},
{
@@ -49,6 +50,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate",
"non_negative": 1,
"options": "currency"
},
{
@@ -92,7 +94,7 @@
],
"istable": 1,
"links": [],
"modified": "2023-01-03 14:19:28.460965",
"modified": "2025-07-31 16:21:44.047007",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Scrap Item",
@@ -103,4 +105,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -160,6 +160,7 @@
"fieldname": "total_completed_qty",
"fieldtype": "Float",
"label": "Total Completed Qty",
"non_negative": 1,
"read_only": 1
},
{
@@ -510,7 +511,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2025-03-17 15:55:11.143456",
"modified": "2025-08-04 15:47:54.514290",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
@@ -568,4 +569,4 @@
"states": [],
"title_field": "operation",
"track_changes": 1
}
}

View File

@@ -70,7 +70,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-14 01:20:48.588052",
"modified": "2025-07-29 13:09:57.323835",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card Scrap Item",
@@ -80,4 +80,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -42,7 +42,8 @@
"fieldname": "completed_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Completed Qty"
"label": "Completed Qty",
"non_negative": 1
},
{
"fieldname": "employee",
@@ -63,7 +64,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-05-21 12:40:55.765860",
"modified": "2025-08-04 15:47:11.748937",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card Time Log",

View File

@@ -70,7 +70,8 @@
"fieldname": "batch_size",
"fieldtype": "Int",
"label": "Batch Size",
"mandatory_depends_on": "create_job_card_based_on_batch_size"
"mandatory_depends_on": "create_job_card_based_on_batch_size",
"non_negative": 1
},
{
"default": "0",
@@ -104,7 +105,7 @@
"icon": "fa fa-wrench",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-11-24 19:15:24.357187",
"modified": "2025-08-04 16:14:57.659318",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Operation",
@@ -137,4 +138,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -24,7 +24,8 @@
"fieldname": "time_in_mins",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Operation Time"
"label": "Operation Time",
"non_negative": 1
},
{
"fieldname": "column_break_5",
@@ -39,7 +40,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-07-15 16:39:41.635362",
"modified": "2025-08-04 16:15:11.425349",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Sub Operation",
@@ -49,4 +50,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -356,8 +356,8 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
erpnext.patches.v14_0.update_zero_asset_quantity_field
execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
erpnext.patches.v14_0.update_total_asset_cost_field
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes #2025-06-19
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22
erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16
@@ -416,5 +416,6 @@ erpnext.patches.v15_0.update_payment_ledger_entries_against_advance_doctypes
erpnext.patches.v15_0.rename_price_list_to_buying_price_list
erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request
erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice
erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-07-31
erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-08-04
execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_for_internal_transaction", 1)
erpnext.patches.v15_0.add_company_payment_gateway_account

View File

@@ -0,0 +1,7 @@
import frappe
def execute():
for gateway_account in frappe.get_list("Payment Gateway Account", fields=["name", "payment_account"]):
company = frappe.db.get_value("Account", gateway_account.payment_account, "company")
frappe.db.set_value("Payment Gateway Account", gateway_account.name, "company", company)

View File

@@ -9,6 +9,6 @@ from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger
def execute():
for dt in get_allowed_types_from_settings():
for dt in get_allowed_types_from_settings(child_doc=True):
for dimension in get_accounting_dimensions():
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)

View File

@@ -2,24 +2,31 @@ import frappe
def execute():
def cancel_incorrect_gl_entries(gl_entries):
table = frappe.qb.DocType("GL Entry")
frappe.qb.update(table).set(table.is_cancelled, 1).where(table.name.isin(gl_entries)).run()
def recreate_gl_entries(voucher_nos):
for doc in voucher_nos:
doc = frappe.get_doc("Subcontracting Receipt", doc)
for item in doc.supplied_items:
account, cost_center = frappe.db.get_values(
"Subcontracting Receipt Item", item.reference_name, ["expense_account", "cost_center"]
)[0]
if not item.expense_account:
item.db_set("expense_account", account)
if not item.cost_center:
item.db_set("cost_center", cost_center)
doc.make_gl_entries()
docs = frappe.get_all(
"GL Entry",
fields=["name", "voucher_no"],
filters={"voucher_type": "Subcontracting Receipt", "account": ["is", "not set"], "is_cancelled": 0},
pluck="voucher_no",
)
for doc in docs:
doc = frappe.get_doc("Subcontracting Receipt", doc)
for item in doc.supplied_items:
account, cost_center = frappe.db.get_values(
"Subcontracting Receipt Item", item.reference_name, ["expense_account", "cost_center"]
)[0]
if not item.expense_account:
item.db_set("expense_account", account)
if not item.cost_center:
item.db_set("cost_center", cost_center)
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.docstatus = 1
doc.make_gl_entries()
if docs:
cancel_incorrect_gl_entries([d.name for d in docs])
recreate_gl_entries([d.voucher_no for d in docs])

View File

@@ -14,5 +14,6 @@ def get_data():
{"label": _("Material"), "items": ["Material Request", "BOM", "Stock Entry"]},
{"label": _("Sales"), "items": ["Sales Order", "Delivery Note", "Sales Invoice"]},
{"label": _("Purchase"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]},
{"label": _("Manufacture"), "items": ["Work Order"]},
],
}

View File

@@ -928,15 +928,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.frm.refresh_fields();
}
async set_default_payment(total_amount_to_pay, update_paid_amount) {
set_default_payment(total_amount_to_pay, update_paid_amount) {
var me = this;
var payment_status = true;
if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) {
let r = await frappe.db.get_value("POS Profile", this.frm.doc.pos_profile, "disable_grand_total_to_default_mop");
if (r.message.disable_grand_total_to_default_mop) {
return;
}
$.each(this.frm.doc['payments'] || [], function(index, data) {
if(data.default && payment_status && total_amount_to_pay > 0) {

View File

@@ -421,6 +421,7 @@ erpnext.PointOfSale.Controller = class {
init_payments() {
this.payment = new erpnext.PointOfSale.Payment({
wrapper: this.$components_wrapper,
settings: this.settings,
events: {
get_frm: () => this.frm || {},

View File

@@ -1,8 +1,9 @@
/* eslint-disable no-unused-vars */
erpnext.PointOfSale.Payment = class {
constructor({ events, wrapper }) {
constructor({ events, settings, wrapper }) {
this.wrapper = wrapper;
this.events = events;
this.disable_grand_total_to_default_mop = settings.disable_grand_total_to_default_mop;
this.init_component();
}
@@ -341,10 +342,11 @@ erpnext.PointOfSale.Payment = class {
}
render_payment_section() {
this.remove_grand_total_from_default_mop();
this.render_payment_mode_dom();
this.make_invoice_fields_control();
this.update_totals_section();
this.unset_grand_total_to_default_mop();
this.focus_on_default_mop();
}
after_render() {
@@ -446,7 +448,19 @@ erpnext.PointOfSale.Payment = class {
this.attach_cash_shortcuts(doc);
}
remove_grand_total_from_default_mop() {
if (!this.disable_grand_total_to_default_mop) return;
const doc = this.events.get_frm().doc;
const payments = doc.payments;
payments.forEach((p) => {
if (p.default) {
frappe.model.set_value(p.doctype, p.name, "amount", 0);
}
});
}
focus_on_default_mop() {
if (this.disable_grand_total_to_default_mop) return;
const doc = this.events.get_frm().doc;
const payments = doc.payments;
payments.forEach((p) => {
@@ -629,19 +643,6 @@ erpnext.PointOfSale.Payment = class {
.toLowerCase();
}
async unset_grand_total_to_default_mop() {
const doc = this.events.get_frm().doc;
let r = await frappe.db.get_value(
"POS Profile",
doc.pos_profile,
"disable_grand_total_to_default_mop"
);
if (!r.message.disable_grand_total_to_default_mop) {
this.focus_on_default_mop();
}
}
validate_reqd_invoice_fields() {
const doc = this.events.get_frm().doc;
let validation_flag = true;

View File

@@ -258,6 +258,7 @@
"width": "100px"
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1395,7 +1396,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2024-11-26 12:44:28.258215",
"modified": "2025-08-04 19:20:47.724218",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
@@ -1486,6 +1487,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "status,customer,customer_name, territory,base_grand_total",
"show_name_in_global_search": 1,
"sort_field": "modified",
@@ -1495,4 +1497,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -96,7 +96,6 @@ class DeliveryNote(SellingController):
per_billed: DF.Percent
per_installed: DF.Percent
per_returned: DF.Percent
pick_list: DF.Link | None
plc_conversion_rate: DF.Float
po_date: DF.Date | None
po_no: DF.SmallText | None

View File

@@ -243,6 +243,7 @@
"width": "100px"
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1290,7 +1291,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
"modified": "2025-04-09 16:52:19.323878",
"modified": "2025-08-04 19:18:47.754957",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
@@ -1360,4 +1361,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -145,7 +145,7 @@ class SerialandBatchBundle(Document):
)
elif not frappe.db.exists("Stock Ledger Entry", {"voucher_detail_no": self.voucher_detail_no}):
if self.voucher_type == "Delivery Note" and frappe.db.exists(
if self.voucher_type in ["Delivery Note", "Sales Invoice"] and frappe.db.exists(
"Packed Item", self.voucher_detail_no
):
return

View File

@@ -218,6 +218,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -697,7 +698,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-08-13 19:05:42.386955",
"modified": "2025-08-04 19:21:03.338958",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",
@@ -763,6 +764,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "posting_date, from_warehouse, to_warehouse, purpose, remarks",
"show_name_in_global_search": 1,
"sort_field": "modified",
@@ -770,4 +772,4 @@
"states": [],
"title_field": "stock_entry_type",
"track_changes": 1
}
}

View File

@@ -72,6 +72,7 @@
"reqd": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"in_list_view": 1,
@@ -183,7 +184,7 @@
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-05-11 09:10:26.327652",
"modified": "2025-08-04 19:21:20.179658",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",
@@ -203,9 +204,10 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "posting_date",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -1287,12 +1287,12 @@ def get_conversion_factor(item_code, uom):
if variant_of:
filters["parent"] = ("in", (item_code, variant_of))
conversion_factor = frappe.db.get_value("UOM Conversion Detail", filters, "conversion_factor")
conversion_factor = frappe.get_all("UOM Conversion Detail", filters, pluck="conversion_factor")
if not conversion_factor:
stock_uom = frappe.db.get_value("Item", item_code, "stock_uom")
conversion_factor = get_uom_conv_factor(uom, stock_uom)
conversion_factor = [get_uom_conv_factor(uom, stock_uom) or 1]
return {"conversion_factor": conversion_factor or 1.0}
return {"conversion_factor": conversion_factor[-1]}
@frappe.whitelist()