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

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Entire Repository - name: Checkout Entire Repository
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false 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. # Tests are skipped for these files but github doesn't allow "passing" hence this is required.
name: Skipped Tests name: Skipped Tests
permissions: {}
on: on:
pull_request: pull_request:
@@ -11,6 +10,9 @@ on:
- "**.md" - "**.md"
- "**.html" - "**.html"
permissions:
contents: read
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -3,22 +3,21 @@
# These owners will be the default owners for everything in # These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence, # the repo. Unless a later match takes precedence,
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar erpnext/accounts/ @ruthra-kumar
erpnext/assets/ @khushi8112 @deepeshgarg007 erpnext/assets/ @khushi8112
erpnext/regional @deepeshgarg007 @ruthra-kumar erpnext/regional @ruthra-kumar
erpnext/selling @deepeshgarg007 @ruthra-kumar erpnext/selling @ruthra-kumar
erpnext/support/ @deepeshgarg007 erpnext/support/ @ruthra-kumar
pos*
erpnext/buying/ @rohitwaghchaure erpnext/buying/ @rohitwaghchaure @mihir-kandoi
erpnext/maintenance/ @rohitwaghchaure erpnext/maintenance/ @rohitwaghchaure
erpnext/manufacturing/ @rohitwaghchaure erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi
erpnext/quality_management/ @rohitwaghchaure erpnext/quality_management/ @rohitwaghchaure
erpnext/stock/ @rohitwaghchaure erpnext/stock/ @rohitwaghchaure @mihir-kandoi
erpnext/subcontracting @rohitwaghchaure erpnext/subcontracting @mihir-kandoi
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
erpnext/patches/ @deepeshgarg007 erpnext/patches/ @ruthra-kumar
.github/ @deepeshgarg007 .github/ @ruthra-kumar
pyproject.toml @akhilnarang 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.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
self.currency_explicitly_specified = False 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 gl_currency and self.account_currency != gl_currency:
if frappe.db.get_value("GL Entry", {"account": self.name}): 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): def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist: if not doclist:
doclist = get_doctypes_with_dimensions() doclist = get_doctypes_with_dimensions()
doc_count = len(get_accounting_dimensions()) doc_count = len(get_accounting_dimensions())
count = 0 count = 0
repostable_doctypes = get_allowed_types_from_settings() repostable_doctypes = get_allowed_types_from_settings(child_doc=True)
for doctype in doclist: for doctype in doclist:
if (doc_count + 1) % 2 == 0: if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break" insert_after_field = "dimension_col_break"
else: else:
insert_after_field = "accounting_dimensions_section" insert_after_field = "accounting_dimensions_section"
df = { df = {
"fieldname": doc.fieldname, "fieldname": doc.fieldname,
"label": doc.label, "label": doc.label,

View File

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

View File

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

View File

@@ -4,6 +4,8 @@
import unittest import unittest
import frappe import frappe
from frappe.query_builder.functions import Sum
from frappe.tests.utils import change_settings
from frappe.utils import add_days, today from frappe.utils import add_days, today
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
@@ -190,6 +192,31 @@ class TestCostCenterAllocation(unittest.TestCase):
coa2.cancel() coa2.cancel()
jv.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( def create_cost_center_allocation(
company, company,

View File

@@ -8,4 +8,14 @@ frappe.ui.form.on("Payment Gateway Account", {
frm.set_df_property("payment_gateway", "read_only", 1); 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": [ "field_order": [
"payment_gateway", "payment_gateway",
"payment_channel", "payment_channel",
"company",
"is_default", "is_default",
"column_break_4", "column_break_4",
"payment_account", "payment_account",
@@ -70,11 +71,21 @@
"fieldtype": "Select", "fieldtype": "Select",
"label": "Payment Channel", "label": "Payment Channel",
"options": "\nEmail\nPhone" "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, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-09-20 13:30:27.722852", "modified": "2025-07-14 16:49:55.210352",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Gateway Account", "name": "Payment Gateway Account",
@@ -95,4 +106,4 @@
], ],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC"
} }

View File

@@ -15,6 +15,7 @@ class PaymentGatewayAccount(Document):
if TYPE_CHECKING: if TYPE_CHECKING:
from frappe.types import DF from frappe.types import DF
company: DF.Link
currency: DF.ReadOnly | None currency: DF.ReadOnly | None
is_default: DF.Check is_default: DF.Check
message: DF.SmallText | None message: DF.SmallText | None
@@ -24,7 +25,8 @@ class PaymentGatewayAccount(Document):
# end: auto-generated types # end: auto-generated types
def autoname(self): 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): def validate(self):
self.currency = frappe.get_cached_value("Account", self.payment_account, "account_currency") 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): def update_default_payment_gateway(self):
if self.is_default: if self.is_default:
frappe.db.sql( frappe.db.set_value(
"""update `tabPayment Gateway Account` set is_default = 0 "Payment Gateway Account",
where is_default = 1 """ {"is_default": 1, "name": ["!=", self.name], "company": self.company},
"is_default",
0,
) )
def set_as_default_if_not_set(self): def set_as_default_if_not_set(self):
if not frappe.db.get_value( if not frappe.db.exists(
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name" "Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name), "company": self.company}
): ):
self.is_default = 1 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", 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: if args.dt not in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST:
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt))) 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() gateway_account = get_gateway_details(args) or frappe._dict()
grand_total = get_amount(ref_doc, gateway_account.get("payment_account")) 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 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: if gateway_account:
return get_payment_gateway_account(gateway_account) return get_payment_gateway_account(gateway_account)

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
import frappe import frappe
from frappe.model.document import Document 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 from erpnext.accounts.doctype.subscription.subscription import DateTimeLikeObject, process_all
@@ -23,7 +23,23 @@ class ProcessSubscription(Document):
# end: auto-generated types # end: auto-generated types
def on_submit(self): 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( def create_subscription_process(

View File

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

View File

@@ -5,6 +5,7 @@ import inspect
import frappe import frappe
from frappe import _, qb from frappe import _, qb
from frappe.desk.form.linked_with import get_child_tables_of_doctypes
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils.data import comma_and from frappe.utils.data import comma_and
@@ -208,13 +209,29 @@ def start_repost(account_repost_doc=str) -> None:
doc.make_gl_entries() doc.make_gl_entries()
def get_allowed_types_from_settings(): def get_allowed_types_from_settings(child_doc: bool = False):
return [ repost_docs = [
x.document_type x.document_type
for x in frappe.db.get_all( for x in frappe.db.get_all(
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"] "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): 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 # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
# import frappe import frappe
from frappe.model.document import Document 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): class RepostAccountingLedgerSettings(Document):
# begin: auto-generated types # begin: auto-generated types
@@ -17,6 +22,24 @@ class RepostAccountingLedgerSettings(Document):
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes
allowed_types: DF.Table[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 "search_index": 1
}, },
{ {
"default": "Now",
"fieldname": "posting_time", "fieldname": "posting_time",
"fieldtype": "Time", "fieldtype": "Time",
"hide_days": 1, "hide_days": 1,
@@ -2189,7 +2190,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2025-06-26 14:06:56.773552", "modified": "2025-08-04 19:20:28.732039",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -1370,8 +1370,9 @@ class SalesInvoice(SellingController):
) )
asset.db_set("disposal_date", None) asset.db_set("disposal_date", None)
add_asset_activity(asset.name, _("Asset returned")) 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 = ( posting_date = (
frappe.db.get_value("Sales Invoice", self.return_against, "posting_date") frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
if self.is_return if self.is_return

View File

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

View File

@@ -75,7 +75,7 @@
}, },
{ {
"default": "0", "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", "fieldname": "consider_party_ledger_amount",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Consider Entire Party Ledger Amount", "label": "Consider Entire Party Ledger Amount",
@@ -102,10 +102,11 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-07-27 21:47:34.396071", "modified": "2025-07-30 07:13:51.785735",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Tax Withholding Category", "name": "Tax Withholding Category",
"naming_rule": "Set by user",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@@ -148,4 +149,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1 "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): 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 = [] new_gl_map = []
for d in gl_map: for d in gl_map:
cost_center = d.get("cost_center") 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) new_gl_map.append(d)
continue 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: for sub_cost_center, percentage in cost_center_allocation:
gle = copy.deepcopy(d) gle = copy.deepcopy(d)
gle.cost_center = sub_cost_center gle.cost_center = sub_cost_center

View File

@@ -197,6 +197,11 @@ frappe.query_reports["General Ledger"] = {
label: __("Show Net Values in Party Account"), label: __("Show Net Values in Party Account"),
fieldtype: "Check", fieldtype: "Check",
}, },
{
fieldname: "show_amount_in_company_currency",
label: __("Show Credit / Debit in Company Currency"),
fieldtype: "Check",
},
{ {
fieldname: "add_values_in_transaction_currency", fieldname: "add_values_in_transaction_currency",
label: __("Add Columns 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() company = filters.get("company") or get_default_company()
filters["presentation_currency"] = currency = get_company_currency(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 = [ columns = [
{ {
"label": _("GL Entry"), "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.item_name,
child_doctype.description, child_doctype.description,
project_field, project_field,
doctype.company,
) )
.where( .where(
(doctype.docstatus == 1) (doctype.docstatus == 1)

View File

@@ -46,6 +46,7 @@ class PaymentLedger:
against_voucher_no=ple.against_voucher_no, against_voucher_no=ple.against_voucher_no,
amount=ple.amount, amount=ple.amount,
currency=ple.account_currency, currency=ple.account_currency,
company=ple.company,
) )
if self.filters.include_account_currency: if self.filters.include_account_currency:
@@ -77,6 +78,7 @@ class PaymentLedger:
against_voucher_no="Outstanding:", against_voucher_no="Outstanding:",
amount=total, amount=total,
currency=voucher_data[0].currency, currency=voucher_data[0].currency,
company=voucher_data[0].company,
) )
if self.filters.include_account_currency: if self.filters.include_account_currency:
@@ -85,7 +87,12 @@ class PaymentLedger:
voucher_data.append(entry) voucher_data.append(entry)
# empty row # 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) self.data.extend(voucher_data)
def build_conditions(self): def build_conditions(self):
@@ -130,7 +137,6 @@ class PaymentLedger:
) )
def get_columns(self): def get_columns(self):
company_currency = frappe.get_cached_value("Company", self.filters.get("company"), "default_currency")
options = None options = None
self.columns.append( self.columns.append(
dict( dict(
@@ -195,7 +201,7 @@ class PaymentLedger:
label=_("Amount"), label=_("Amount"),
fieldname="amount", fieldname="amount",
fieldtype="Currency", fieldtype="Currency",
options=company_currency, options="Company:company:default_currency",
width="100", 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) gle_map = get_gle_map(tds_docs)
out = [] out = []
entries = {}
for name, details in gle_map.items(): for name, details in gle_map.items():
for entry in details: for entry in details:
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0 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, "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"])) out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
return out return out

View File

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

View File

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

View File

@@ -1097,7 +1097,7 @@ def make_journal_entry(asset_name):
je.voucher_type = "Depreciation Entry" je.voucher_type = "Depreciation Entry"
je.naming_series = depreciation_series je.naming_series = depreciation_series
je.company = asset.company 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( je.append(
"accounts", "accounts",

View File

@@ -277,7 +277,9 @@ def _make_journal_entry_for_depreciation(
je.posting_date = depr_schedule.schedule_date je.posting_date = depr_schedule.schedule_date
je.company = asset.company je.company = asset.company
je.finance_book = asset_depr_schedule_doc.finance_book 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 = { credit_entry = {
"account": credit_account, "account": credit_account,

View File

@@ -346,6 +346,33 @@ class TestAsset(AssetSetup):
si.cancel() si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") 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): def test_gle_made_by_asset_sale_for_existing_asset(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice 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): def on_submit(self):
self.validate_asset()
self.db_set("status", "Active") 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: if not self.flags.should_not_cancel_depreciation_entries:
self.cancel_depreciation_entries() self.cancel_depreciation_entries()
def cancel_depreciation_entries(self): def cancel_depreciation_entries(self):
for d in self.get("depreciation_schedule"): for d in self.get("depreciation_schedule"):
if d.journal_entry: 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() frappe.get_doc("Journal Entry", d.journal_entry).cancel()
def on_cancel(self):
self.db_set("status", "Cancelled")
def update_shift_depr_schedule(self): def update_shift_depr_schedule(self):
if not self.shift_based or self.docstatus != 0: if not self.shift_based or self.docstatus != 0:
return return

View File

@@ -3,13 +3,13 @@ frappe.listview_settings["Asset Maintenance Log"] = {
has_indicator_for_draft: 1, has_indicator_for_draft: 1,
get_indicator: function (doc) { get_indicator: function (doc) {
if (doc.maintenance_status == "Planned") { 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") { } 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") { } 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") { } 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): def set_missing_values(source, target):
target.run_method("set_missing_values") target.run_method("set_missing_values")
target.run_method("calculate_taxes_and_totals") target.run_method("calculate_taxes_and_totals")
target.run_method("set_use_serial_batch_fields")
@frappe.whitelist() @frappe.whitelist()

View File

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

View File

@@ -153,7 +153,12 @@ class OpportunitySummaryBySalesStage:
}[self.filters.get("based_on")] }[self.filters.get("based_on")]
if self.filters.get("based_on") == "Opportunity Owner": 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"] assignments = ["Not Assigned"]
else: else:
assignments = json.loads(d.get(based_on)) assignments = json.loads(d.get(based_on))

View File

@@ -60,7 +60,7 @@ def import_genericode():
"doctype": "File", "doctype": "File",
"attached_to_doctype": "Code List", "attached_to_doctype": "Code List",
"attached_to_name": code_list.name, "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_name": frappe.local.uploaded_filename,
"file_url": frappe.local.uploaded_file_url, "file_url": frappe.local.uploaded_file_url,
"is_private": 1, "is_private": 1,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,8 @@
"fieldname": "time_in_mins", "fieldname": "time_in_mins",
"fieldtype": "Float", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
"label": "Operation Time" "label": "Operation Time",
"non_negative": 1
}, },
{ {
"fieldname": "column_break_5", "fieldname": "column_break_5",
@@ -39,7 +40,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-07-15 16:39:41.635362", "modified": "2025-08-04 16:15:11.425349",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Sub Operation", "name": "Sub Operation",
@@ -49,4 +50,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1 "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 erpnext.patches.v14_0.update_zero_asset_quantity_field
execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
erpnext.patches.v14_0.update_total_asset_cost_field 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.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.v14_0.update_flag_for_return_invoices #2024-03-22
erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16 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.rename_price_list_to_buying_price_list
erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request 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.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) 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(): 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(): for dimension in get_accounting_dimensions():
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1) frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)

View File

@@ -2,24 +2,31 @@ import frappe
def execute(): 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( docs = frappe.get_all(
"GL Entry", "GL Entry",
fields=["name", "voucher_no"],
filters={"voucher_type": "Subcontracting Receipt", "account": ["is", "not set"], "is_cancelled": 0}, 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: if docs:
item.db_set("expense_account", account) cancel_incorrect_gl_entries([d.name for d in docs])
if not item.cost_center: recreate_gl_entries([d.voucher_no for d in docs])
item.db_set("cost_center", cost_center)
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.docstatus = 1
doc.make_gl_entries()

View File

@@ -14,5 +14,6 @@ def get_data():
{"label": _("Material"), "items": ["Material Request", "BOM", "Stock Entry"]}, {"label": _("Material"), "items": ["Material Request", "BOM", "Stock Entry"]},
{"label": _("Sales"), "items": ["Sales Order", "Delivery Note", "Sales Invoice"]}, {"label": _("Sales"), "items": ["Sales Order", "Delivery Note", "Sales Invoice"]},
{"label": _("Purchase"), "items": ["Purchase Order", "Purchase Receipt", "Purchase 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(); 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 me = this;
var payment_status = true; var payment_status = true;
if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) { 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) { $.each(this.frm.doc['payments'] || [], function(index, data) {
if(data.default && payment_status && total_amount_to_pay > 0) { if(data.default && payment_status && total_amount_to_pay > 0) {

View File

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

View File

@@ -1,8 +1,9 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
erpnext.PointOfSale.Payment = class { erpnext.PointOfSale.Payment = class {
constructor({ events, wrapper }) { constructor({ events, settings, wrapper }) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.events = events; this.events = events;
this.disable_grand_total_to_default_mop = settings.disable_grand_total_to_default_mop;
this.init_component(); this.init_component();
} }
@@ -341,10 +342,11 @@ erpnext.PointOfSale.Payment = class {
} }
render_payment_section() { render_payment_section() {
this.remove_grand_total_from_default_mop();
this.render_payment_mode_dom(); this.render_payment_mode_dom();
this.make_invoice_fields_control(); this.make_invoice_fields_control();
this.update_totals_section(); this.update_totals_section();
this.unset_grand_total_to_default_mop(); this.focus_on_default_mop();
} }
after_render() { after_render() {
@@ -446,7 +448,19 @@ erpnext.PointOfSale.Payment = class {
this.attach_cash_shortcuts(doc); 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() { focus_on_default_mop() {
if (this.disable_grand_total_to_default_mop) return;
const doc = this.events.get_frm().doc; const doc = this.events.get_frm().doc;
const payments = doc.payments; const payments = doc.payments;
payments.forEach((p) => { payments.forEach((p) => {
@@ -629,19 +643,6 @@ erpnext.PointOfSale.Payment = class {
.toLowerCase(); .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() { validate_reqd_invoice_fields() {
const doc = this.events.get_frm().doc; const doc = this.events.get_frm().doc;
let validation_flag = true; let validation_flag = true;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1287,12 +1287,12 @@ def get_conversion_factor(item_code, uom):
if variant_of: if variant_of:
filters["parent"] = ("in", (item_code, 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: if not conversion_factor:
stock_uom = frappe.db.get_value("Item", item_code, "stock_uom") 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() @frappe.whitelist()