Merge branch 'develop' into feat/employee-creation-and-lifecycle

This commit is contained in:
Krishna Pramod Shirsath
2026-03-09 11:16:55 +05:30
committed by Krishna Shirsath
137 changed files with 5589 additions and 3456 deletions

View File

@@ -1,7 +1,7 @@
# Security Policy
The ERPNext team and community take security issues seriously. To report a security issue, fill out the form at [https://erpnext.com/security/report](https://erpnext.com/security/report).
The ERPNext team and community take security issues seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).
You can help us make ERPNext and all it's users more secure by following the [Reporting guidelines](https://erpnext.com/security).
You can help us make ERPNext and all its users more secure by following the [Reporting guidelines](https://frappe.io/security).
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.

View File

@@ -69,37 +69,34 @@ class AccountingDimensionFilter(Document):
def get_dimension_filter_map():
if not frappe.flags.get("dimension_filter_map"):
filters = frappe.db.sql(
"""
SELECT
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
p.allow_or_restrict, p.fieldname, a.is_mandatory
FROM
`tabApplicable On Account` a,
`tabAccounting Dimension Filter` p
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
WHERE
p.name = a.parent
AND p.disabled = 0
""",
as_dict=1,
filters = frappe.db.sql(
"""
SELECT
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
p.allow_or_restrict, p.fieldname, a.is_mandatory
FROM
`tabApplicable On Account` a,
`tabAccounting Dimension Filter` p
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
WHERE
p.name = a.parent
AND p.disabled = 0
""",
as_dict=1,
)
dimension_filter_map = {}
for f in filters:
build_map(
dimension_filter_map,
f.fieldname,
f.applicable_on_account,
f.dimension_value,
f.allow_or_restrict,
f.is_mandatory,
)
dimension_filter_map = {}
for f in filters:
build_map(
dimension_filter_map,
f.fieldname,
f.applicable_on_account,
f.dimension_value,
f.allow_or_restrict,
f.is_mandatory,
)
frappe.flags.dimension_filter_map = dimension_filter_map
return frappe.flags.dimension_filter_map
return dimension_filter_map
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):

View File

@@ -205,7 +205,7 @@
"description": "Payment Terms from orders will be fetched into the invoices as is",
"fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check",
"label": "Automatically Fetch Payment Terms from Order"
"label": "Automatically Fetch Payment Terms from Order/Quotation"
},
{
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
@@ -697,7 +697,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-02-04 17:15:38.609327",
"modified": "2026-02-27 01:04:09.415288",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -1154,7 +1154,7 @@ def get_irequests_of_payment_request(doc: str | None = None) -> list:
@frappe.whitelist()
def get_available_payment_schedules(reference_doctype, reference_name):
def get_available_payment_schedules(reference_doctype: str, reference_name: str):
ref_doc = frappe.get_doc(reference_doctype, reference_name)
if not hasattr(ref_doc, "payment_schedule") or not ref_doc.payment_schedule:

View File

@@ -4,19 +4,6 @@
frappe.ui.form.on("POS Closing Entry", {
onload: async function (frm) {
frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log", "Sales Invoice"];
frm.set_query("pos_profile", function (doc) {
return {
filters: { user: doc.user },
};
});
frm.set_query("user", function (doc) {
return {
query: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_cashiers",
filters: { parent: doc.pos_profile },
};
});
frm.set_query("pos_opening_entry", function (doc) {
return { filters: { status: "Open", docstatus: 1 } };
});

View File

@@ -346,8 +346,7 @@ def apply_pricing_rule(args: str | dict, doc: str | dict | Document | None = Non
args = frappe._dict(args)
if not args.transaction_type:
set_transaction_type(args)
set_transaction_type(args)
# list of dictionaries
out = []
@@ -688,23 +687,23 @@ def remove_pricing_rules(item_list: str | list):
return out
def set_transaction_type(args):
if args.transaction_type:
def set_transaction_type(pricing_ctx: frappe._dict) -> None:
if pricing_ctx.transaction_type in ["buying", "selling"]:
return
if args.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
args.transaction_type = "selling"
elif args.doctype in (
if pricing_ctx.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
pricing_ctx.transaction_type = "selling"
elif pricing_ctx.doctype in (
"Material Request",
"Supplier Quotation",
"Purchase Order",
"Purchase Receipt",
"Purchase Invoice",
):
args.transaction_type = "buying"
elif args.customer:
args.transaction_type = "selling"
pricing_ctx.transaction_type = "buying"
elif pricing_ctx.customer:
pricing_ctx.transaction_type = "selling"
else:
args.transaction_type = "buying"
pricing_ctx.transaction_type = "buying"
@frappe.whitelist()

View File

@@ -800,8 +800,7 @@
"hide_seconds": 1,
"label": "Time Sheets",
"options": "Sales Invoice Timesheet",
"print_hide": 1,
"read_only": 1
"print_hide": 1
},
{
"default": "0",
@@ -2331,7 +2330,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2026-02-25 12:41:57.043459",
"modified": "2026-02-28 17:58:56.453076",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -1452,6 +1452,9 @@ class SalesInvoice(SellingController):
return asset_qty_map
def process_asset_depreciation(self):
if self.is_internal_transfer():
return
if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1):
self.depreciate_asset_on_sale()
else:

View File

@@ -8,6 +8,8 @@ import frappe
from frappe import _
from frappe.contacts.doctype.address.address import get_default_address
from frappe.model.document import Document
from frappe.query_builder import DocType
from frappe.query_builder.functions import IfNull
from frappe.utils import cstr
from frappe.utils.nestedset import get_root_of
@@ -83,6 +85,8 @@ class TaxRule(Document):
frappe.throw(_("Tax Template is mandatory."))
def validate_filters(self):
TaxRule = DocType("Tax Rule")
filters = {
"tax_type": self.tax_type,
"customer": self.customer,
@@ -105,33 +109,34 @@ class TaxRule(Document):
"company": self.company,
}
conds = ""
for d in filters:
if conds:
conds += " and "
conds += f"""ifnull({d}, '') = {frappe.db.escape(cstr(filters[d]))}"""
if self.from_date and self.to_date:
conds += f""" and ((from_date > '{self.from_date}' and from_date < '{self.to_date}') or
(to_date > '{self.from_date}' and to_date < '{self.to_date}') or
('{self.from_date}' > from_date and '{self.from_date}' < to_date) or
('{self.from_date}' = from_date and '{self.to_date}' = to_date))"""
elif self.from_date and not self.to_date:
conds += f""" and to_date > '{self.from_date}'"""
elif self.to_date and not self.from_date:
conds += f""" and from_date < '{self.to_date}'"""
tax_rule = frappe.db.sql(
f"select name, priority \
from `tabTax Rule` where {conds} and name != '{self.name}'",
as_dict=1,
query = (
frappe.qb.from_(TaxRule).select(TaxRule.name, TaxRule.priority).where(TaxRule.name != self.name)
)
if tax_rule:
if tax_rule[0].priority == self.priority:
frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
for field, value in filters.items():
query = query.where(IfNull(TaxRule[field], "") == cstr(value))
if self.from_date and self.to_date:
query = query.where(
((TaxRule.from_date > self.from_date) & (TaxRule.from_date < self.to_date))
| ((TaxRule.to_date > self.from_date) & (TaxRule.to_date < self.to_date))
| ((self.from_date > TaxRule.from_date) & (self.from_date < TaxRule.to_date))
| ((TaxRule.from_date == self.from_date) & (TaxRule.to_date == self.to_date))
)
elif self.from_date:
query = query.where(TaxRule.to_date > self.from_date)
elif self.to_date:
query = query.where(TaxRule.from_date < self.to_date)
tax_rule = query.run(as_dict=True)
if tax_rule and tax_rule[0].priority == self.priority:
frappe.throw(
_("Tax Rule Conflicts with {0}").format(tax_rule[0].name),
ConflictingTaxRule,
)
@frappe.whitelist()

View File

@@ -1,6 +1,6 @@
import frappe
from frappe.tests import IntegrationTestCase
from frappe.utils import today
from frappe.utils import add_days, today
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.report.accounts_payable.accounts_payable import execute
@@ -57,3 +57,66 @@ class TestAccountsPayable(AccountsTestMixin, IntegrationTestCase):
if not do_not_submit:
pi = pi.submit()
return pi
def test_payment_terms_template_filters(self):
from erpnext.controllers.accounts_controller import get_payment_terms
payment_term1 = frappe.get_doc(
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 15 Days"}
).insert()
payment_term2 = frappe.get_doc(
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 30 Days"}
).insert()
template = frappe.get_doc(
{
"doctype": "Payment Terms Template",
"template_name": "_Test 50-50",
"terms": [
{
"doctype": "Payment Terms Template Detail",
"due_date_based_on": "Day(s) after invoice date",
"payment_term": payment_term1.name,
"description": "_Test 50-50",
"invoice_portion": 50,
"credit_days": 15,
},
{
"doctype": "Payment Terms Template Detail",
"due_date_based_on": "Day(s) after invoice date",
"payment_term": payment_term2.name,
"description": "_Test 50-50",
"invoice_portion": 50,
"credit_days": 30,
},
],
}
)
template.insert()
filters = {
"company": self.company,
"report_date": today(),
"range": "30, 60, 90, 120",
"based_on_payment_terms": 1,
"payment_terms_template": template.name,
"ageing_based_on": "Posting Date",
}
pi = self.create_purchase_invoice(do_not_submit=True)
pi.payment_terms_template = template.name
schedule = get_payment_terms(template.name)
pi.set("payment_schedule", [])
for row in schedule:
row["due_date"] = add_days(pi.posting_date, row.get("credit_days", 0))
pi.append("payment_schedule", row)
pi.save()
pi.submit()
report = execute(filters)
row = report[1][0]
self.assertEqual(len(report[1]), 2)
self.assertEqual([pi.name, payment_term1.payment_term_name], [row.voucher_no, row.payment_term])

View File

@@ -1035,9 +1035,8 @@ class ReceivablePayableReport:
self,
):
self.customer = qb.DocType("Customer")
if self.filters.get("customer_group"):
groups = get_customer_group_with_children(self.filters.customer_group)
groups = get_party_group_with_children("Customer", self.filters.customer_group)
customers = (
qb.from_(self.customer)
.select(self.customer.name)
@@ -1049,14 +1048,18 @@ class ReceivablePayableReport:
self.get_hierarchical_filters("Territory", "territory")
if self.filters.get("payment_terms_template"):
self.qb_selection_filter.append(
self.ple.party.isin(
qb.from_(self.customer)
.select(self.customer.name)
.where(self.customer.payment_terms == self.filters.get("payment_terms_template"))
)
customer_ptt = self.ple.party.isin(
qb.from_(self.customer)
.select(self.customer.name)
.where(self.customer.payment_terms == self.filters.get("payment_terms_template"))
)
si_ptt = self.add_payment_term_template_filters("Sales Invoice")
sales_ptt = self.ple.against_voucher_no.isin(si_ptt)
self.qb_selection_filter.append(Criterion.any([customer_ptt, sales_ptt]))
if self.filters.get("sales_partner"):
self.qb_selection_filter.append(
self.ple.party.isin(
@@ -1081,14 +1084,53 @@ class ReceivablePayableReport:
)
if self.filters.get("payment_terms_template"):
self.qb_selection_filter.append(
self.ple.party.isin(
qb.from_(supplier)
.select(supplier.name)
.where(supplier.payment_terms == self.filters.get("supplier_group"))
)
supplier_ptt = self.ple.party.isin(
qb.from_(supplier)
.select(supplier.name)
.where(supplier.payment_terms == self.filters.get("payment_terms_template"))
)
pi_ptt = self.add_payment_term_template_filters("Purchase Invoice")
purchase_ptt = self.ple.against_voucher_no.isin(pi_ptt)
self.qb_selection_filter.append(Criterion.any([supplier_ptt, purchase_ptt]))
def add_payment_term_template_filters(self, dtype):
voucher_type = qb.DocType(dtype)
ptt = (
qb.from_(voucher_type)
.select(voucher_type.name)
.where(voucher_type.payment_terms_template == self.filters.get("payment_terms_template"))
.where(voucher_type.company == self.filters.company)
)
if dtype == "Purchase Invoice":
party = "Supplier"
party_group_type = "supplier_group"
acc_type = "credit_to"
else:
party = "Customer"
party_group_type = "customer_group"
acc_type = "debit_to"
if self.filters.get(party_group_type):
party_groups = get_party_group_with_children(party, self.filters.get(party_group_type))
ptt = ptt.where((voucher_type[party_group_type]).isin(party_groups))
if self.filters.party:
ptt = ptt.where((voucher_type[party.lower()]).isin(self.filters.party))
if self.filters.cost_center:
cost_centers = get_cost_centers_with_children(self.filters.cost_center)
ptt = ptt.where(voucher_type.cost_center.isin(cost_centers))
if self.filters.party_account:
ptt = ptt.where(voucher_type[acc_type] == self.filters.party_account)
return ptt
def get_hierarchical_filters(self, doctype, key):
lft, rgt = frappe.db.get_value(doctype, self.filters.get(key), ["lft", "rgt"])
@@ -1330,20 +1372,26 @@ class ReceivablePayableReport:
self.err_journals = [x[0] for x in results] if results else []
def get_customer_group_with_children(customer_groups):
if not isinstance(customer_groups, list):
customer_groups = [d.strip() for d in customer_groups.strip().split(",") if d]
def get_party_group_with_children(party, party_groups):
if party not in ("Customer", "Supplier"):
return []
all_customer_groups = []
for d in customer_groups:
if frappe.db.exists("Customer Group", d):
lft, rgt = frappe.db.get_value("Customer Group", d, ["lft", "rgt"])
children = frappe.get_all("Customer Group", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
all_customer_groups += [c.name for c in children]
group_dtype = f"{party} Group"
if not isinstance(party_groups, list):
party_groups = [d.strip() for d in party_groups.strip().split(",") if d]
all_party_groups = []
for d in party_groups:
if frappe.db.exists(group_dtype, d):
lft, rgt = frappe.db.get_value(group_dtype, d, ["lft", "rgt"])
children = frappe.get_all(
group_dtype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, pluck="name"
)
all_party_groups += children
else:
frappe.throw(_("Customer Group: {0} does not exist").format(d))
frappe.throw(_("{0}: {1} does not exist").format(group_dtype, d))
return list(set(all_customer_groups))
return list(set(all_party_groups))
class InitSQLProceduresForAR:

View File

@@ -1139,3 +1139,66 @@ class TestAccountsReceivable(AccountsTestMixin, IntegrationTestCase):
self.assertEqual(len(report[1]), 1)
row = report[1][0]
self.assertEqual(expected_data_after_payment, [row.voucher_no, row.cost_center, row.outstanding])
def test_payment_terms_template_filters(self):
from erpnext.controllers.accounts_controller import get_payment_terms
payment_term1 = frappe.get_doc(
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 15 Days"}
).insert()
payment_term2 = frappe.get_doc(
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 30 Days"}
).insert()
template = frappe.get_doc(
{
"doctype": "Payment Terms Template",
"template_name": "_Test 50-50",
"terms": [
{
"doctype": "Payment Terms Template Detail",
"due_date_based_on": "Day(s) after invoice date",
"payment_term": payment_term1.name,
"description": "_Test 50-50",
"invoice_portion": 50,
"credit_days": 15,
},
{
"doctype": "Payment Terms Template Detail",
"due_date_based_on": "Day(s) after invoice date",
"payment_term": payment_term2.name,
"description": "_Test 50-50",
"invoice_portion": 50,
"credit_days": 30,
},
],
}
)
template.insert()
filters = {
"company": self.company,
"report_date": today(),
"range": "30, 60, 90, 120",
"based_on_payment_terms": 1,
"payment_terms_template": template.name,
"ageing_based_on": "Posting Date",
}
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si.payment_terms_template = template.name
schedule = get_payment_terms(template.name)
si.set("payment_schedule", [])
for row in schedule:
row["due_date"] = add_days(si.posting_date, row.get("credit_days", 0))
si.append("payment_schedule", row)
si.save()
si.submit()
report = execute(filters)
row = report[1][0]
self.assertEqual(len(report[1]), 2)
self.assertEqual([si.name, payment_term1.payment_term_name], [row.voucher_no, row.payment_term])

View File

@@ -5,6 +5,7 @@ import frappe
from frappe import _
from frappe.utils import add_months, flt, formatdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.utils import get_fiscal_year
from erpnext.controllers.trends import get_period_date_ranges
@@ -13,6 +14,8 @@ def execute(filters=None):
if not filters:
filters = {}
validate_filters(filters)
columns = get_columns(filters)
if filters.get("budget_against_filter"):
dimensions = filters.get("budget_against_filter")
@@ -31,6 +34,10 @@ def execute(filters=None):
return columns, data, None, chart_data
def validate_filters(filters):
validate_budget_dimensions(filters)
def get_budget_records(filters, dimensions):
budget_against_field = frappe.scrub(filters["budget_against"])
@@ -51,7 +58,7 @@ def get_budget_records(filters, dimensions):
b.company = %s
AND b.docstatus = 1
AND b.budget_against = %s
AND b.{budget_against_field} IN ({', '.join(['%s'] * len(dimensions))})
AND b.{budget_against_field} IN ({", ".join(["%s"] * len(dimensions))})
AND (
b.from_fiscal_year <= %s
AND b.to_fiscal_year >= %s
@@ -404,6 +411,17 @@ def get_budget_dimensions(filters):
) # nosec
def validate_budget_dimensions(filters):
dimensions = [d.get("document_type") for d in get_dimensions(with_cost_center_and_project=True)[0]]
if filters.get("budget_against") and filters.get("budget_against") not in dimensions:
frappe.throw(
title=_("Invalid Accounting Dimension"),
msg=_("{0} is not a valid Accounting Dimension.").format(
frappe.bold(filters.get("budget_against"))
),
)
def build_comparison_chart_data(filters, columns, data):
if not data:
return None

View File

@@ -656,7 +656,11 @@ def set_gl_entries_by_account(
query = query.where(Criterion.all(additional_conditions))
gl_entries = query.run(as_dict=True)
if filters and filters.get("presentation_currency") != d.default_currency:
if (
filters
and filters.get("presentation_currency")
and filters.get("presentation_currency") != d.default_currency
):
currency_info["company"] = d.name
currency_info["company_currency"] = d.default_currency
convert_to_presentation_currency(gl_entries, currency_info)

View File

@@ -37,6 +37,20 @@ function get_filters() {
});
},
},
{
fieldname: "party_type",
label: __("Party Type"),
fieldtype: "Link",
options: "Party Type",
width: 100,
},
{
fieldname: "party",
label: __("Party"),
fieldtype: "Dynamic Link",
options: "party_type",
width: 100,
},
{
fieldname: "voucher_no",
label: __("Voucher No"),

View File

@@ -68,6 +68,12 @@ class General_Payment_Ledger_Comparison:
if self.filters.period_end_date:
filter_criterion.append(gle.posting_date.lte(self.filters.period_end_date))
if self.filters.party_type:
filter_criterion.append(gle.party_type.eq(self.filters.party_type))
if self.filters.party:
filter_criterion.append(gle.party.eq(self.filters.party))
if acc_type == "receivable":
outstanding = (Sum(gle.debit) - Sum(gle.credit)).as_("outstanding")
else:
@@ -111,6 +117,12 @@ class General_Payment_Ledger_Comparison:
if self.filters.period_end_date:
filter_criterion.append(ple.posting_date.lte(self.filters.period_end_date))
if self.filters.party_type:
filter_criterion.append(ple.party_type.eq(self.filters.party_type))
if self.filters.party:
filter_criterion.append(ple.party.eq(self.filters.party))
self.account_types[acc_type].ple = (
qb.from_(ple)
.select(

View File

@@ -7,6 +7,7 @@ import math
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import (
cint,
@@ -986,7 +987,7 @@ class Asset(AccountsController):
return False
@frappe.whitelist()
def get_depreciation_rate(self, args, on_validate=False):
def get_depreciation_rate(self, args: str | dict | Document, on_validate: bool = False):
if isinstance(args, str):
args = json.loads(args)

View File

@@ -16,6 +16,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
refresh() {
this.show_general_ledger();
erpnext.toggle_serial_batch_fields(this.frm);
if (this.frm.doc.stock_items && this.frm.doc.stock_items.length) {
this.show_stock_ledger();

View File

@@ -90,7 +90,7 @@ def update_asset_maintenance_log_status():
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters):
def get_maintenance_tasks(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
asset_maintenance_tasks = frappe.db.get_values(
"Asset Maintenance Task", {"parent": filters.get("asset_maintenance")}, "maintenance_task"
)

View File

@@ -18,6 +18,7 @@
"order_confirmation_date",
"column_break_7",
"transaction_date",
"transaction_time",
"schedule_date",
"column_break1",
"is_subcontracted",
@@ -1311,6 +1312,14 @@
{
"fieldname": "section_break_tnkm",
"fieldtype": "Section Break"
},
{
"default": "Now",
"depends_on": "is_internal_supplier",
"fieldname": "transaction_time",
"fieldtype": "Time",
"label": "Time",
"mandatory_depends_on": "is_internal_supplier"
}
],
"grid_page_length": 50,
@@ -1318,7 +1327,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2026-02-23 13:22:33.323946",
"modified": "2026-03-02 00:40:47.119584",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -167,6 +167,7 @@ class PurchaseOrder(BuyingController):
total_qty: DF.Float
total_taxes_and_charges: DF.Currency
transaction_date: DF.Date
transaction_time: DF.Time | None
# end: auto-generated types
def __init__(self, *args, **kwargs):

View File

@@ -250,10 +250,17 @@ frappe.ui.form.on("Request for Quotation", {
"subject",
])
.then((r) => {
frm.set_value(
"message_for_supplier",
r.message.use_html ? r.message.response_html : r.message.response
);
if (r.message.use_html) {
frm.set_value({
mfs_html: r.message.response_html,
use_html: 1,
});
} else {
frm.set_value({
message_for_supplier: r.message.response,
use_html: 0,
});
}
frm.set_value("subject", r.message.subject);
});
}

View File

@@ -31,7 +31,9 @@
"send_document_print",
"sec_break_email_2",
"subject",
"use_html",
"message_for_supplier",
"mfs_html",
"terms_section_break",
"incoterm",
"named_place",
@@ -142,12 +144,13 @@
{
"allow_on_submit": 1,
"default": "Please supply the specified items at the best possible rates",
"depends_on": "eval:doc.use_html == 0",
"fieldname": "message_for_supplier",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Message for Supplier",
"print_hide": 1,
"reqd": 1
"mandatory_depends_on": "eval:doc.use_html == 0",
"print_hide": 1
},
{
"collapsible": 1,
@@ -324,6 +327,22 @@
"label": "Subject",
"not_nullable": 1,
"reqd": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.use_html == 1",
"fieldname": "mfs_html",
"fieldtype": "Code",
"label": "Message for Supplier",
"mandatory_depends_on": "eval:doc.use_html == 1",
"print_hide": 1
},
{
"default": "0",
"fieldname": "use_html",
"fieldtype": "Check",
"hidden": 1,
"label": "Use HTML"
}
],
"grid_page_length": 50,
@@ -331,7 +350,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2026-01-06 10:31:08.747043",
"modified": "2026-03-01 23:38:48.079274",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",

View File

@@ -48,7 +48,8 @@ class RequestforQuotation(BuyingController):
incoterm: DF.Link | None
items: DF.Table[RequestforQuotationItem]
letter_head: DF.Link | None
message_for_supplier: DF.TextEditor
message_for_supplier: DF.TextEditor | None
mfs_html: DF.Code | None
named_place: DF.Data | None
naming_series: DF.Literal["PUR-RFQ-.YYYY.-"]
opportunity: DF.Link | None
@@ -62,6 +63,7 @@ class RequestforQuotation(BuyingController):
tc_name: DF.Link | None
terms: DF.TextEditor | None
transaction_date: DF.Date
use_html: DF.Check
vendor: DF.Link | None
# end: auto-generated types
@@ -101,8 +103,16 @@ class RequestforQuotation(BuyingController):
["use_html", "response", "response_html", "subject"],
as_dict=True,
)
if not self.message_for_supplier:
self.message_for_supplier = data.response_html if data.use_html else data.response
self.use_html = data.use_html
if data.use_html:
if not self.mfs_html:
self.mfs_html = data.response_html
else:
if not self.message_for_supplier:
self.message_for_supplier = data.response
if not self.subject:
self.subject = data.subject
@@ -305,7 +315,10 @@ class RequestforQuotation(BuyingController):
else:
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
rendered_message = frappe.render_template(self.message_for_supplier, doc_args)
message_template = self.mfs_html if self.use_html else self.message_for_supplier
# nosemgrep: frappe-semgrep-rules.rules.security.frappe-ssti
rendered_message = frappe.render_template(message_template, doc_args)
subject_source = (
self.subject
or frappe.get_value("Email Template", self.email_template, "subject")

View File

@@ -34,7 +34,7 @@ class TestPurchaseOrder(IntegrationTestCase):
self.assertEqual(sq.get("items")[1].rate, 300)
self.assertEqual(sq.get("items")[1].description, "test")
def test_update_supplier_quotation_child_rate_disallow(self):
def test_update_supplier_quotation_child_rate(self):
sq = frappe.copy_doc(self.globalTestRecords["Supplier Quotation"][0])
sq.submit()
trans_item = json.dumps(
@@ -47,6 +47,22 @@ class TestPurchaseOrder(IntegrationTestCase):
},
]
)
update_child_qty_rate("Supplier Quotation", trans_item, sq.name)
sq.reload()
self.assertEqual(sq.get("items")[0].rate, 300)
po = make_purchase_order(sq.name)
po.schedule_date = add_days(today(), 1)
po.submit()
trans_item = json.dumps(
[
{
"item_code": sq.items[0].item_code,
"rate": 20,
"qty": sq.items[0].qty,
"docname": sq.items[0].name,
},
]
)
self.assertRaises(
frappe.ValidationError, update_child_qty_rate, "Supplier Quotation", trans_item, sq.name
)

View File

@@ -165,7 +165,7 @@ def get_data(filters):
"cost_center": po.cost_center,
"project": po.project,
"requesting_site": po.warehouse,
"requestor": po.owner,
"requestor": mr_record.get("owner", po.owner),
"material_request_no": po.material_request,
"item_code": po.item_code,
"quantity": flt(po.qty),

View File

@@ -2526,13 +2526,14 @@ class AccountsController(TransactionBase):
grand_total = flt(self.get("rounded_total") or self.grand_total)
automatically_fetch_payment_terms = 0
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
grand_total = grand_total - flt(self.write_off_amount)
if self.doctype in ("Sales Invoice", "Purchase Invoice", "Sales Order"):
po_or_so, doctype, fieldname = self.get_order_details()
automatically_fetch_payment_terms = cint(
frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
)
if self.doctype != "Sales Order":
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
grand_total = grand_total - flt(self.write_off_amount)
if self.get("total_advance"):
if party_account_currency == self.company_currency:
@@ -2548,7 +2549,7 @@ class AccountsController(TransactionBase):
if not self.get("payment_schedule"):
if (
self.doctype in ["Sales Invoice", "Purchase Invoice"]
self.doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order"]
and automatically_fetch_payment_terms
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
):
@@ -2606,16 +2607,18 @@ class AccountsController(TransactionBase):
if not self.get("items"):
return None, None, None
if self.doctype == "Sales Invoice":
po_or_so = self.get("items")[0].get("sales_order")
po_or_so_doctype = "Sales Order"
po_or_so_doctype_name = "sales_order"
prev_doc = self.get("items")[0].get("sales_order")
prev_doctype = "Sales Order"
prev_doctype_name = "sales_order"
elif self.doctype == "Purchase Invoice":
prev_doc = self.get("items")[0].get("purchase_order")
prev_doctype = "Purchase Order"
prev_doctype_name = "purchase_order"
else:
po_or_so = self.get("items")[0].get("purchase_order")
po_or_so_doctype = "Purchase Order"
po_or_so_doctype_name = "purchase_order"
return po_or_so, po_or_so_doctype, po_or_so_doctype_name
prev_doc = self.get("items")[0].get("prevdoc_docname")
prev_doctype = "Quotation"
prev_doctype_name = "prevdoc_docname"
return prev_doc, prev_doctype, prev_doctype_name
def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
@@ -3886,20 +3889,28 @@ def update_child_qty_rate(
return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
return False
def validate_quantity(child_item, new_data):
def validate_quantity_and_rate(child_item, new_data):
if not flt(new_data.get("qty")) and not is_allowed_zero_qty():
frappe.throw(
_("Row #{0}: Quantity for Item {1} cannot be zero.").format(
_("Row #{0}:Quantity for Item {1} cannot be zero.").format(
new_data.get("idx"), frappe.bold(new_data.get("item_code"))
),
title=_("Invalid Qty"),
)
if parent_doctype == "Sales Order" and flt(new_data.get("qty")) < flt(child_item.delivered_qty):
frappe.throw(_("Cannot set quantity less than delivered quantity"))
qty_limits = {
"Sales Order": ("delivered_qty", _("Cannot set quantity less than delivered quantity")),
"Purchase Order": ("received_qty", _("Cannot set quantity less than received quantity")),
}
if parent_doctype == "Purchase Order" and flt(new_data.get("qty")) < flt(child_item.received_qty):
frappe.throw(_("Cannot set quantity less than received quantity"))
if parent_doctype in qty_limits:
qty_field, error_message = qty_limits[parent_doctype]
if flt(new_data.get("qty")) < flt(child_item.get(qty_field)):
frappe.throw(
_("Row #{0}:").format(new_data.get("idx"))
+ error_message.format(frappe.bold(new_data.get("item_code"))),
title=_("Invalid Qty"),
)
if parent_doctype in ["Quotation", "Supplier Quotation"]:
if (parent_doctype == "Quotation" and not ordered_items) or (
@@ -3912,7 +3923,15 @@ def update_child_qty_rate(
if parent_doctype == "Quotation"
else purchased_items.get(child_item.name)
)
if qty_to_check:
if not rate_unchanged:
frappe.throw(
_(
"Cannot update rate as item {0} is already ordered or purchased against this quotation"
).format(frappe.bold(new_data.get("item_code")))
)
if flt(new_data.get("qty")) < qty_to_check:
frappe.throw(_("Cannot reduce quantity than ordered or purchased quantity"))
@@ -4031,10 +4050,7 @@ def update_child_qty_rate(
):
continue
validate_quantity(child_item, d)
if parent_doctype in ["Quotation", "Supplier Quotation"]:
if not rate_unchanged:
frappe.throw(_("Rates cannot be modified for quoted items"))
validate_quantity_and_rate(child_item, d)
if flt(child_item.get("qty")) != flt(d.get("qty")):
any_qty_changed = True

View File

@@ -1012,7 +1012,14 @@ def get_serial_batches_based_on_bundle(doctype, field, _bundle_ids):
if doctype == "Packed Item":
if key is None:
key = frappe.get_cached_value("Packed Item", row.voucher_detail_no, field)
key = frappe.get_cached_value(
"Packed Item",
{"parent_detail_docname": row.voucher_detail_no, "item_code": row.item_code},
field,
)
if key is None:
key = frappe.get_cached_value("Packed Item", row.voucher_detail_no, field)
if row.voucher_type == "Delivery Note":
key = frappe.get_cached_value("Delivery Note Item", key, "dn_detail")
elif row.voucher_type == "Sales Invoice":

View File

@@ -333,9 +333,10 @@ class SellingController(StockController):
if is_internal_customer or not is_stock_item:
continue
if item.get("incoming_rate") and item.base_net_rate < (
rate_field = "valuation_rate" if self.doctype in ["Sales Order", "Quotation"] else "incoming_rate"
if item.get(rate_field) and item.base_net_rate < (
valuation_rate := flt(
item.incoming_rate * (item.conversion_factor or 1), item.precision("base_net_rate")
item.get(rate_field) * (item.conversion_factor or 1), item.precision("base_net_rate")
)
):
throw_message(

View File

@@ -63,6 +63,8 @@ class StockController(AccountsController):
if not self.get("is_return"):
self.validate_inspection()
self.validate_warehouse_of_sabb()
self.validate_serialized_batch()
self.clean_serial_nos()
self.validate_customer_provided_item()
@@ -75,6 +77,45 @@ class StockController(AccountsController):
super().on_update()
self.check_zero_rate()
def validate_warehouse_of_sabb(self):
if self.is_internal_transfer():
return
doc_before_save = self.get_doc_before_save()
for row in self.items:
if not row.get("serial_and_batch_bundle"):
continue
sabb_details = frappe.db.get_value(
"Serial and Batch Bundle",
row.serial_and_batch_bundle,
["type_of_transaction", "warehouse", "has_serial_no"],
as_dict=True,
)
if not sabb_details:
continue
if sabb_details.type_of_transaction != "Outward":
continue
warehouse = row.get("warehouse") or row.get("s_warehouse")
if sabb_details.warehouse != warehouse:
frappe.throw(
_(
"Row #{0}: Warehouse {1} does not match with the warehouse {2} in Serial and Batch Bundle {3}."
).format(row.idx, warehouse, sabb_details.warehouse, row.serial_and_batch_bundle)
)
if self.doctype == "Stock Reconciliation":
continue
if sabb_details.has_serial_no and doc_before_save and doc_before_save.get("items"):
prev_row = doc_before_save.get("items", {"idx": row.idx})
if prev_row and prev_row[0].serial_and_batch_bundle != row.serial_and_batch_bundle:
sabb_doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
sabb_doc.validate_serial_no_status()
def reset_conversion_factor(self):
for row in self.get("items"):
if row.uom != row.stock_uom:
@@ -2087,7 +2128,9 @@ def check_item_quality_inspection(doctype: str, items: str | list[dict]):
@frappe.whitelist()
def make_quality_inspections(doctype: str, docname: str, items: str | list, inspection_type: str):
def make_quality_inspections(
company: str, doctype: str, docname: str, items: str | list, inspection_type: str
):
if isinstance(items, str):
items = json.loads(items)
@@ -2106,6 +2149,7 @@ def make_quality_inspections(doctype: str, docname: str, items: str | list, insp
quality_inspection = frappe.get_doc(
{
"company": company,
"doctype": "Quality Inspection",
"inspection_type": inspection_type,
"inspected_by": frappe.session.user,

View File

@@ -49,6 +49,7 @@ class TestReactivity(AccountsTestMixin, IntegrationTestCase):
"debit_to": self.debit_to,
"posting_date": today(),
"cost_center": self.cost_center,
"currency": "INR",
"conversion_rate": 1,
"selling_price_list": self.price_list,
}

View File

@@ -34,7 +34,7 @@ class ContractTemplate(Document):
@frappe.whitelist()
def get_contract_template(template_name, doc):
def get_contract_template(template_name: str, doc: str | dict | Document):
if isinstance(doc, str):
doc = json.loads(doc)

View File

@@ -10,8 +10,10 @@ from frappe.contacts.address_and_contact import (
from frappe.contacts.doctype.address.address import get_default_address
from frappe.contacts.doctype.contact.contact import get_default_contact
from frappe.email.inbox import link_communication_to_document
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address
from frappe.utils.data import DateTimeLikeObject
from erpnext.accounts.party import set_taxes
from erpnext.controllers.selling_controller import SellingController
@@ -240,7 +242,7 @@ class Lead(SellingController, CRMNote):
return frappe.db.get_value("Quotation", {"party_name": self.name, "docstatus": 1, "status": "Lost"})
@frappe.whitelist()
def create_prospect_and_contact(self, data):
def create_prospect_and_contact(self, data: dict):
data = frappe._dict(data)
if data.create_contact:
self.create_contact()
@@ -314,7 +316,7 @@ class Lead(SellingController, CRMNote):
@frappe.whitelist()
def make_customer(source_name, target_doc=None):
def make_customer(source_name: str, target_doc: str | Document | None = None):
return _make_customer(source_name, target_doc)
@@ -361,7 +363,7 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False):
@frappe.whitelist()
def make_opportunity(source_name, target_doc=None):
def make_opportunity(source_name: str, target_doc: str | Document | None = None):
def set_missing_values(source, target):
_set_missing_values(source, target)
@@ -391,7 +393,7 @@ def make_opportunity(source_name, target_doc=None):
@frappe.whitelist()
def make_quotation(source_name, target_doc=None):
def make_quotation(source_name: str, target_doc: str | Document | None = None):
def set_missing_values(source, target):
_set_missing_values(source, target)
@@ -442,7 +444,12 @@ def _set_missing_values(source, target):
@frappe.whitelist()
def get_lead_details(lead, posting_date=None, company=None, doctype=None):
def get_lead_details(
lead: str,
posting_date: DateTimeLikeObject | None = None,
company: str | None = None,
doctype: str | None = None,
):
if not lead:
return {}
@@ -481,7 +488,7 @@ def get_lead_details(lead, posting_date=None, company=None, doctype=None):
@frappe.whitelist()
def make_lead_from_communication(communication, ignore_communication_links=False):
def make_lead_from_communication(communication: str, ignore_communication_links: bool = False):
"""raise a issue from email"""
doc = frappe.get_doc("Communication", communication)
@@ -530,7 +537,7 @@ def get_lead_with_phone_number(number):
@frappe.whitelist()
def add_lead_to_prospect(lead, prospect):
def add_lead_to_prospect(lead: str, prospect: str):
prospect = frappe.get_doc("Prospect", prospect)
prospect.append("leads", {"lead": lead})
prospect.save(ignore_permissions=True)

View File

@@ -307,6 +307,21 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller {
};
});
this.frm.set_query("uom", "items", function (doc, cdt, cdn) {
let row = locals[cdt][cdn];
if (!row.item_code) {
return;
}
return {
query: "erpnext.controllers.queries.get_item_uom_query",
filters: {
item_code: row.item_code,
},
};
});
me.frm.set_query("contact_person", erpnext.queries["contact_query"]);
if (me.frm.doc.opportunity_from == "Lead") {

View File

@@ -8,6 +8,7 @@ import frappe
from frappe import _
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.email.inbox import link_communication_to_document
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder import DocType, Interval
from frappe.query_builder.functions import Now
@@ -261,7 +262,9 @@ class Opportunity(TransactionBase, CRMNote):
self.party_name = lead_name
@frappe.whitelist()
def declare_enquiry_lost(self, lost_reasons_list, competitors, detailed_reason=None):
def declare_enquiry_lost(
self, lost_reasons_list: list, competitors: list, detailed_reason: str | None = None
):
if not self.has_active_quotation():
self.status = "Lost"
self.lost_reasons = []
@@ -362,7 +365,7 @@ class Opportunity(TransactionBase, CRMNote):
@frappe.whitelist()
def get_item_details(item_code):
def get_item_details(item_code: str):
item = frappe.db.sql(
"""select item_name, stock_uom, image, description, item_group, brand
from `tabItem` where name = %s""",
@@ -380,7 +383,7 @@ def get_item_details(item_code):
@frappe.whitelist()
def make_quotation(source_name, target_doc=None):
def make_quotation(source_name: str, target_doc: str | Document | None = None):
def set_missing_values(source, target):
from erpnext.controllers.accounts_controller import get_default_taxes_and_charges
@@ -433,7 +436,7 @@ def make_quotation(source_name, target_doc=None):
@frappe.whitelist()
def make_request_for_quotation(source_name, target_doc=None):
def make_request_for_quotation(source_name: str, target_doc: str | Document | None = None):
def update_item(obj, target, source_parent):
target.conversion_factor = 1.0
@@ -455,7 +458,7 @@ def make_request_for_quotation(source_name, target_doc=None):
@frappe.whitelist()
def make_customer(source_name, target_doc=None):
def make_customer(source_name: str, target_doc: str | Document | None = None):
def set_missing_values(source, target):
target.opportunity_name = source.name
@@ -479,7 +482,7 @@ def make_customer(source_name, target_doc=None):
@frappe.whitelist()
def make_supplier_quotation(source_name, target_doc=None):
def make_supplier_quotation(source_name: str, target_doc: str | Document | None = None):
doclist = get_mapped_doc(
"Opportunity",
source_name,
@@ -494,7 +497,7 @@ def make_supplier_quotation(source_name, target_doc=None):
@frappe.whitelist()
def set_multiple_status(names, status):
def set_multiple_status(names: str | list[str], status: str):
names = json.loads(names)
for name in names:
opp = frappe.get_doc("Opportunity", name)
@@ -524,7 +527,9 @@ def auto_close_opportunity():
@frappe.whitelist()
def make_opportunity_from_communication(communication, company, ignore_communication_links=False):
def make_opportunity_from_communication(
communication: str, company: str, ignore_communication_links: bool = False
):
from erpnext.crm.doctype.lead.lead import make_lead_from_communication
doc = frappe.get_doc("Communication", communication)

View File

@@ -6,6 +6,7 @@ from frappe.contacts.address_and_contact import (
delete_contact_and_address,
load_address_and_contact,
)
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
@@ -87,7 +88,7 @@ class Prospect(CRMNote):
@frappe.whitelist()
def make_customer(source_name, target_doc=None):
def make_customer(source_name: str, target_doc: str | Document | None = None):
def set_missing_values(source, target):
target.customer_type = "Company"
target.company_name = source.name
@@ -111,7 +112,7 @@ def make_customer(source_name, target_doc=None):
@frappe.whitelist()
def make_opportunity(source_name, target_doc=None):
def make_opportunity(source_name: str, target_doc: str | Document | None = None):
def set_missing_values(source, target):
target.opportunity_from = "Prospect"
target.customer_name = source.company_name
@@ -135,7 +136,7 @@ def make_opportunity(source_name, target_doc=None):
@frappe.whitelist()
def get_opportunities(prospect):
def get_opportunities(prospect: str):
return frappe.get_all(
"Opportunity",
filters={"opportunity_from": "Prospect", "party_name": prospect},

View File

@@ -2,7 +2,7 @@ import frappe
@frappe.whitelist()
def get_last_interaction(contact=None, lead=None):
def get_last_interaction(contact: str | None = None, lead: str | None = None):
if not contact and not lead:
return

View File

@@ -58,7 +58,9 @@ def create_prospect_against_crm_deal():
)
pass
create_contacts(json.loads(doc.contacts), prospect.company_name, "Prospect", prospect_name)
if doc.contacts and len(doc.contacts):
create_contacts(json.loads(doc.contacts), prospect.company_name, "Prospect", prospect_name)
create_address("Prospect", prospect_name, doc.address)
frappe.response["message"] = prospect_name
@@ -149,7 +151,7 @@ def contact_exists(email, mobile_no):
@frappe.whitelist()
def create_customer(customer_data=None):
def create_customer(customer_data: dict | None = None):
if not customer_data:
customer_data = frappe.form_dict

View File

@@ -144,7 +144,7 @@ def link_open_events(ref_doctype, ref_docname, doc):
@frappe.whitelist()
def get_open_activities(ref_doctype, ref_docname):
def get_open_activities(ref_doctype: str, ref_docname: str):
tasks = get_open_todos(ref_doctype, ref_docname)
events = get_open_events(ref_doctype, ref_docname)
tasks_history = get_closed_todos(ref_doctype, ref_docname)
@@ -242,20 +242,20 @@ def open_leads_opportunities_based_on_todays_event():
class CRMNote(Document):
@frappe.whitelist()
def add_note(self, note):
def add_note(self, note: str):
self.append("notes", {"note": note, "added_by": frappe.session.user, "added_on": now()})
self.save()
notify_mentions(self.doctype, self.name, note)
@frappe.whitelist()
def edit_note(self, note, row_id):
def edit_note(self, note: str, row_id: str):
for d in self.notes:
if cstr(d.name) == row_id:
d.note = note
d.db_update()
@frappe.whitelist()
def delete_note(self, row_id):
def delete_note(self, row_id: str):
for d in self.notes:
if cstr(d.name) == row_id:
self.remove(d)

View File

@@ -0,0 +1,21 @@
{
"app": "erpnext",
"bg_color": "blue",
"creation": "2026-02-24 17:43:08.379896",
"docstatus": 0,
"doctype": "Desktop Icon",
"hidden": 0,
"icon_type": "Link",
"idx": 0,
"label": "Organization",
"link_to": "Organization",
"link_type": "Workspace Sidebar",
"modified": "2026-02-24 17:59:39.885360",
"modified_by": "Administrator",
"name": "Organization",
"owner": "Administrator",
"parent_icon": "",
"restrict_removal": 0,
"roles": [],
"standard": 1
}

View File

@@ -51,7 +51,7 @@ def get_plaid_configuration():
@frappe.whitelist()
def add_institution(token, response):
def add_institution(token: str, response: str):
response = json.loads(response)
plaid = PlaidConnector()
@@ -79,7 +79,7 @@ def add_institution(token, response):
@frappe.whitelist()
def add_bank_accounts(response, bank, company):
def add_bank_accounts(response: str | dict, bank: str | dict, company: str):
try:
response = json.loads(response)
except TypeError:
@@ -334,7 +334,7 @@ def enqueue_synchronization():
@frappe.whitelist()
def get_link_token_for_update(access_token):
def get_link_token_for_update(access_token: str):
plaid = PlaidConnector(access_token)
return plaid.get_link_token(update_mode=True)
@@ -354,7 +354,7 @@ def get_company(bank_account_name):
@frappe.whitelist()
def update_bank_account_ids(response):
def update_bank_account_ids(response: str):
data = json.loads(response)
institution_name = data["institution"]["name"]
bank = frappe.get_doc("Bank", institution_name).as_dict()

View File

@@ -219,6 +219,16 @@ website_route_rules = [
{"from_route": "/tasks", "to_route": "Task"},
]
standard_navbar_items = [
{
"item_label": "Clear Demo Data",
"item_type": "Action",
"action": "erpnext.demo.clear_demo();",
"is_standard": 1,
"condition": "eval: frappe.boot.sysdefaults.demo_company",
},
]
standard_portal_menu_items = [
{"title": "Projects", "route": "/project", "reference_doctype": "Project", "role": "Customer"},
{

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2026-02-22 09:43+0000\n"
"PO-Revision-Date: 2026-02-24 16:58\n"
"PO-Revision-Date: 2026-02-26 16:53\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Persian\n"
"MIME-Version: 1.0\n"
@@ -6996,7 +6996,7 @@ msgstr "حساب بانک / نقدی"
#. Label of the bank_ac_no (Data) field in DocType 'Employee'
#: erpnext/setup/doctype/employee/employee.json
msgid "Bank A/C No."
msgstr "شماره تهویه مطبوع بانک"
msgstr "شماره حساب بانکی"
#. Name of a DocType
#. Label of the bank_account (Link) field in DocType 'Bank Clearance'

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2026-02-22 09:43+0000\n"
"PO-Revision-Date: 2026-02-22 16:37\n"
"PO-Revision-Date: 2026-03-06 17:38\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
@@ -25,7 +25,12 @@ msgid "\n"
"\t\t\tIf it is not possible to make an adjustment entry, please enable 'Allow Negative Stock for Batch' in Stock Settings to proceed.\n"
"\t\t\tHowever, enabling this setting may lead to negative stock in the system.\n"
"\t\t\tSo please ensure the stock levels are adjusted as soon as possible to maintain the correct valuation rate."
msgstr ""
msgstr "\n"
"\t\t\tПартия {0} товара {1} имеет отрицательный остаток на складе {2}{3}.\n"
"\t\t\tПожалуйста, добавьте количество товара {4}, чтобы продолжить ввод данных.\n"
"\t\t\tЕсли невозможно внести корректирующие данные, пожалуйста, включите параметр «Разрешить отрицательный остаток для партии» в настройках склада, чтобы продолжить.\n"
"\t\t\tОднако включение этого параметра может привести к отрицательному остатку в системе.\n"
"\t\t\tПоэтому, пожалуйста, убедитесь, что уровни запасов скорректированы как можно скорее, чтобы поддерживать правильную оценочную стоимость."
#. Label of the column_break_32 (Column Break) field in DocType 'Email Digest'
#: erpnext/setup/doctype/email_digest/email_digest.json
@@ -1094,7 +1099,7 @@ msgstr "Для вас создана новая встреча с {0}"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:3
msgid "A new fiscal year has been automatically created."
msgstr ""
msgstr "Новый финансовый год был создан автоматически."
#: erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py:96
msgid "A template with tax category {0} already exists. Only one template is allowed with each tax category"
@@ -1133,7 +1138,7 @@ msgstr "ACC-PINV-.YYYY.-"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:88
msgid "ALL records will be deleted (entire DocType cleared)"
msgstr ""
msgstr "ВСЕ записи будут удалены (весь DocType будет очищен)"
#: erpnext/stock/report/serial_no_and_batch_traceability/serial_no_and_batch_traceability.py:552
msgid "AMC Expiry (Serial)"
@@ -1149,7 +1154,7 @@ msgstr "Дата истечения срока действия AMC"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "AP Summary"
msgstr ""
msgstr "Сводка по кредиторской задолженности"
#. Label of the api_details_section (Section Break) field in DocType 'Currency
#. Exchange Settings'
@@ -4075,7 +4080,7 @@ msgstr "Разрешить отрицательный запас"
#. Settings'
#: erpnext/stock/doctype/stock_settings/stock_settings.json
msgid "Allow Negative Stock for Batch"
msgstr ""
msgstr "Разрешить отрицательный остаток для партии"
#. Label of the allow_negative_rates_for_items (Check) field in DocType
#. 'Selling Settings'
@@ -4227,7 +4232,7 @@ msgstr "Разрешить пользователю редактировать
#. Label of the allow_warehouse_change (Check) field in DocType 'POS Profile'
#: erpnext/accounts/doctype/pos_profile/pos_profile.json
msgid "Allow User to Edit Warehouse"
msgstr ""
msgstr "Разрешить пользователю редактировать склад"
#. Label of the allow_different_uom (Check) field in DocType 'Item Variant
#. Settings'
@@ -5943,7 +5948,7 @@ msgstr "Необходимо выбрать хотя бы один вариан
#: erpnext/stock/doctype/stock_entry/stock_entry.py:314
msgid "At least one raw material item must be present in the stock entry for the type {0}"
msgstr ""
msgstr "Как минимум одна единица сырья должна присутствовать в записи о запасах для типа {0}"
#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:25
msgid "At least one row is required for a financial report template"
@@ -6336,7 +6341,7 @@ msgstr "Доступно"
#. DocType 'Bin'
#: erpnext/stock/doctype/bin/bin.json
msgid "Available / Future Inventory"
msgstr ""
msgstr "Доступный / Будущий запас"
#. Label of the actual_batch_qty (Float) field in DocType 'Delivery Note Item'
#: erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -7265,7 +7270,7 @@ msgstr "Банковский овердрафтовый счет"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/banking.json
msgid "Bank Reconciliation"
msgstr ""
msgstr "Инвентаризация банковских счетов"
#. Name of a report
#. Label of a Link in the Invoicing Workspace
@@ -8459,7 +8464,7 @@ msgstr "Дата начала бюджета"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/budget.json
msgid "Budget Variance"
msgstr ""
msgstr "Отклонение от бюджета"
#. Name of a report
#. Label of a Link in the Invoicing Workspace
@@ -8653,7 +8658,7 @@ msgstr "Копия для"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "COA Importer"
msgstr ""
msgstr "Импорт плана счетов"
#. Option for the 'Barcode Type' (Select) field in DocType 'Item Barcode'
#: erpnext/stock/doctype/item_barcode/item_barcode.json
@@ -8937,7 +8942,7 @@ msgstr "Графики кампаний"
#: erpnext/crm/doctype/email_campaign/email_campaign.py:113
msgid "Campaign {0} not found"
msgstr ""
msgstr "Кампания {0} не найдена"
#: erpnext/setup/doctype/authorization_control/authorization_control.py:60
msgid "Can be approved by {0}"
@@ -9052,7 +9057,7 @@ msgstr "Невозможно повторно отправить записи в
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:202
msgid "Cannot add child table {0} to deletion list. Child tables are automatically deleted with their parent DocTypes."
msgstr ""
msgstr "Невозможно добавить дочернюю таблицу {0} в список для удаления. Дочерние таблицы автоматически удаляются вместе с родительскими DocType."
#: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:226
msgid "Cannot amend {0} {1}, please create a new one instead."
@@ -9195,7 +9200,7 @@ msgstr ""
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:211
msgid "Cannot delete virtual DocType: {0}. Virtual DocTypes do not have database tables."
msgstr ""
msgstr "Невозможно удалить виртуальный DocType: {0}. Виртуальные DocType не имеют таблиц в базе данных."
#: erpnext/setup/doctype/company/company.py:559
msgid "Cannot disable perpetual inventory, as there are existing Stock Ledger Entries for the company {0}. Please cancel the stock transactions first and try again."
@@ -9232,7 +9237,7 @@ msgstr "Невозможно объединить {0} '{1}' с '{2}', поско
#: erpnext/manufacturing/doctype/work_order/work_order.py:543
msgid "Cannot produce more Item {0} than Sales Order quantity {1} {2}"
msgstr ""
msgstr "Невозможно произвести больше товаров {0}, чем количество товаров в заказе на продажу {1} {2}"
#: erpnext/manufacturing/doctype/work_order/work_order.py:1439
msgid "Cannot produce more item for {0}"
@@ -9287,7 +9292,7 @@ msgstr "Невозможно установить несколько парам
#: erpnext/assets/doctype/asset_category/asset_category.py:108
msgid "Cannot set multiple account rows for the same company"
msgstr ""
msgstr "Невозможно задать несколько счетов для одной и той же компании"
#: erpnext/controllers/accounts_controller.py:3885
msgid "Cannot set quantity less than delivered quantity"
@@ -9303,7 +9308,7 @@ msgstr "Невозможно установить поле <b>{0}</b> для к
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:264
msgid "Cannot start deletion. Another deletion {0} is already queued/running. Please wait for it to complete."
msgstr ""
msgstr "Невозможно начать удаление. Другое удаление {0} уже находится в очереди/выполняется. Пожалуйста, дождитесь его завершения."
#: erpnext/accounts/doctype/payment_entry/payment_entry.py:1920
msgid "Cannot {0} from {1} without any negative outstanding invoice"
@@ -9945,7 +9950,7 @@ msgstr "Технический директор"
#. Deletion Record To Delete'
#: erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.json
msgid "Child DocTypes"
msgstr ""
msgstr "Дочерние типы документов"
#. Label of the child_docname (Data) field in DocType 'Pricing Rule Detail'
#: erpnext/accounts/doctype/pricing_rule_detail/pricing_rule_detail.json
@@ -9961,7 +9966,7 @@ msgstr "Ссылка на дочернюю строку"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:205
msgid "Child Table Not Allowed"
msgstr ""
msgstr "Дочерняя таблица не допускается"
#: erpnext/projects/doctype/task/task.py:312
msgid "Child Task exists for this Task. You can not delete this Task."
@@ -9975,7 +9980,7 @@ msgstr "Дочерние узлы могут быть созданы тольк
#. 'Transaction Deletion Record To Delete'
#: erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.json
msgid "Child tables that will also be deleted"
msgstr ""
msgstr "Дочерние таблицы, которые также будут удалены"
#: erpnext/stock/doctype/warehouse/warehouse.py:103
msgid "Child warehouse exists for this warehouse. You can not delete this warehouse."
@@ -10833,7 +10838,7 @@ msgstr "Счет компании"
#: erpnext/accounts/doctype/bank_account/bank_account.py:63
msgid "Company Account is mandatory"
msgstr ""
msgstr "Счет компании обязателен"
#. Label of the company_address (Link) field in DocType 'Dunning'
#. Label of the company_address_display (Text Editor) field in DocType 'POS
@@ -10950,7 +10955,7 @@ msgstr "Электронная почта компании"
#. Record To Delete'
#: erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.json
msgid "Company Field"
msgstr ""
msgstr "Поле компании"
#. Label of the company_logo (Attach Image) field in DocType 'Company'
#: erpnext/public/js/print.js:64 erpnext/setup/doctype/company/company.json
@@ -11004,7 +11009,7 @@ msgstr "Компания обязательна для создания счет
#. Deletion Record To Delete'
#: erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.json
msgid "Company link field name used for filtering (optional - leave empty to delete all records)"
msgstr ""
msgstr "Название поля ссылки на компанию, используемое для фильтрации (необязательно — оставьте пустым, чтобы удалить все записи)"
#: erpnext/setup/doctype/company/company.js:222
msgid "Company name not same"
@@ -11197,12 +11202,12 @@ msgstr "Наименование компонента"
#. Option for the 'Asset Type' (Select) field in DocType 'Asset'
#: erpnext/assets/doctype/asset/asset.json
msgid "Composite Asset"
msgstr ""
msgstr "Составной актив"
#. Option for the 'Asset Type' (Select) field in DocType 'Asset'
#: erpnext/assets/doctype/asset/asset.json
msgid "Composite Component"
msgstr ""
msgstr "Составной компонент"
#. Label of the comprehensive_insurance (Data) field in DocType 'Asset'
#: erpnext/assets/doctype/asset/asset.json
@@ -11261,7 +11266,7 @@ msgstr "Дата подтверждения"
#. Label of the connection_tab (Tab Break) field in DocType 'Asset Repair'
#: erpnext/assets/doctype/asset_repair/asset_repair.json
msgid "Connection"
msgstr ""
msgstr "Подключение"
#: erpnext/accounts/report/general_ledger/general_ledger.js:175
msgid "Consider Accounting Dimensions"
@@ -11361,7 +11366,7 @@ msgstr "Консолидированный финансовый отчет"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Consolidated Report"
msgstr ""
msgstr "Сводный отчет"
#. Label of the consolidated_invoice (Link) field in DocType 'POS Invoice'
#. Label of the consolidated_invoice (Link) field in DocType 'POS Invoice Merge
@@ -13717,7 +13722,7 @@ msgstr "Номер клиента LPO"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Customer Ledger"
msgstr ""
msgstr "Клиентская книга"
#. Name of a report
#. Label of a Link in the Financial Reports Workspace
@@ -13831,7 +13836,7 @@ msgstr "Подробности заказа клиента"
#. Label of the customer_pos_id (Data) field in DocType 'Customer'
#: erpnext/selling/doctype/customer/customer.json
msgid "Customer POS ID"
msgstr ""
msgstr "Идентификатор клиента точки продаж"
#. Label of the portal_users (Table) field in DocType 'Customer'
#: erpnext/selling/doctype/customer/customer.json
@@ -15141,11 +15146,11 @@ msgstr "Для страны не разрешено удаление {0}"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:216
msgid "Deletion process restarted"
msgstr ""
msgstr "Процесс удаления перезапущен"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:97
msgid "Deletion will start automatically after submission."
msgstr ""
msgstr "Удаление начнётся автоматически после отправки."
#. Label of the delimiter_options (Data) field in DocType 'Bank Statement
#. Import'
@@ -16602,27 +16607,27 @@ msgstr "DocType {0} не существует"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:295
msgid "DocType {0} with company field '{1}' is already in the list"
msgstr ""
msgstr "DocType {0} с полем компании '{1}' уже есть в списке"
#. Label of the doctypes_to_delete (Table) field in DocType 'Transaction
#. Deletion Record'
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
msgid "DocTypes To Delete"
msgstr ""
msgstr "DocType для удаления"
#. Description of the 'Excluded DocTypes' (Table) field in DocType 'Transaction
#. Deletion Record'
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
msgid "DocTypes that will NOT be deleted."
msgstr ""
msgstr "DocType, которые НЕ будут удалены."
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:84
msgid "DocTypes with a company field:"
msgstr ""
msgstr "DocTypes с полем \"Компания\":"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:88
msgid "DocTypes without a company field:"
msgstr ""
msgstr "Типы документов без поля \"Компания\":"
#: erpnext/templates/pages/search_help.py:22
msgid "Docs Search"
@@ -16632,11 +16637,11 @@ msgstr "Поиск документов"
#. Record To Delete'
#: erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.json
msgid "Document Count"
msgstr ""
msgstr "Количество документов"
#: erpnext/stock/report/negative_batch_report/negative_batch_report.py:78
msgid "Document No"
msgstr ""
msgstr "Документ №"
#. Label of the document_type (Link) field in DocType 'Subscription Invoice'
#: erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json
@@ -16899,7 +16904,7 @@ msgstr "Дублировать группу клиентов"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:188
msgid "Duplicate DocType"
msgstr ""
msgstr "Дублирование DocType"
#: erpnext/setup/doctype/authorization_rule/authorization_rule.py:71
msgid "Duplicate Entry. Please check Authorization Rule {0}"
@@ -16957,7 +16962,7 @@ msgstr "Повторяющаяся запись с кодом товара {0}
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:187
msgid "Duplicate entry: {0}{1}"
msgstr ""
msgstr "Повторяющаяся запись: {0}{1}"
#: erpnext/accounts/doctype/pos_profile/pos_profile.py:165
msgid "Duplicate item group found in the item group table"
@@ -17230,7 +17235,7 @@ msgstr "Кампания по электронной почте"
#: erpnext/crm/doctype/email_campaign/email_campaign.py:149
#: erpnext/crm/doctype/email_campaign/email_campaign.py:157
msgid "Email Campaign Error"
msgstr ""
msgstr "Ошибка кампании электронной почты"
#. Label of the email_campaign_for (Select) field in DocType 'Email Campaign'
#: erpnext/crm/doctype/email_campaign/email_campaign.json
@@ -17239,7 +17244,7 @@ msgstr "Email-кампания для"
#: erpnext/crm/doctype/email_campaign/email_campaign.py:125
msgid "Email Campaign Send Error"
msgstr ""
msgstr "Ошибка отправки кампании электронной почты"
#. Label of the supplier_response_section (Section Break) field in DocType
#. 'Request for Quotation'
@@ -17490,7 +17495,7 @@ msgstr "Пустой"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:752
msgid "Empty To Delete List"
msgstr ""
msgstr "Пустой список для удаления"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -17576,7 +17581,7 @@ msgstr "Включить учет скидок при продаже"
#. Settings'
#: erpnext/accounts/doctype/accounts_settings/accounts_settings.json
msgid "Enable Discounts and Margin"
msgstr ""
msgstr "Включить скидки и наценку"
#. Label of the enable_european_access (Check) field in DocType 'Plaid
#. Settings'
@@ -17640,7 +17645,7 @@ msgstr "Включить резервирование запасов"
#. Label of the enable_utm (Check) field in DocType 'Selling Settings'
#: erpnext/selling/doctype/selling_settings/selling_settings.json
msgid "Enable UTM"
msgstr ""
msgstr "Включить UTM"
#. Description of the 'Enable UTM' (Check) field in DocType 'Selling Settings'
#: erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -18695,7 +18700,7 @@ msgstr "Не удалось провести записи по амортиза
#: erpnext/crm/doctype/email_campaign/email_campaign.py:126
msgid "Failed to send email for campaign {0} to {1}"
msgstr ""
msgstr "Не удалось отправить электронное письмо для кампании {0} на адрес {1}"
#: erpnext/setup/setup_wizard/setup_wizard.py:30
#: erpnext/setup/setup_wizard/setup_wizard.py:31
@@ -18749,7 +18754,7 @@ msgstr "Отзыв от"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/quality.json
msgid "Feedback Template"
msgstr ""
msgstr "Шаблон обратной связи"
#. Option for the 'Reference Type' (Select) field in DocType 'Journal Entry
#. Account'
@@ -18837,7 +18842,7 @@ msgstr "Получение данных..."
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:222
msgid "Field '{0}' is not a valid Company link field for DocType {1}"
msgstr ""
msgstr "Поле '{0}' не является действительным полем ссылки на компанию для DocType {1}"
#. Label of the field_mapping_section (Section Break) field in DocType
#. 'Inventory Dimension'
@@ -18859,15 +18864,15 @@ msgstr "Поля будут скопированы только во время
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:1064
msgid "File does not belong to this Transaction Deletion Record"
msgstr ""
msgstr "Файл не относится к данной записи об удалении транзакции"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:1058
msgid "File not found"
msgstr ""
msgstr "Файл не найден"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:1072
msgid "File not found on server"
msgstr ""
msgstr "Файл не найден на сервере"
#. Label of the file_to_rename (Attach) field in DocType 'Rename Tool'
#: erpnext/utilities/doctype/rename_tool/rename_tool.json
@@ -19322,7 +19327,7 @@ msgstr "Финансовый год компании"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:5
msgid "Fiscal Year Details"
msgstr ""
msgstr "Подробная информация о финансовом году"
#: erpnext/accounts/doctype/fiscal_year/fiscal_year.py:47
msgid "Fiscal Year End Date should be one year after Fiscal Year Start Date"
@@ -19638,7 +19643,7 @@ msgstr "Для элемента {0} ставка должна быть поло
#: erpnext/manufacturing/doctype/bom/bom.py:347
msgid "For operation {0} at row {1}, please add raw materials or set a BOM against it."
msgstr ""
msgstr "Для операции {0} в строке {1} добавьте сырье или создайте спецификацию материалов для нее."
#: erpnext/manufacturing/doctype/work_order/work_order.py:2591
msgid "For operation {0}: Quantity ({1}) can not be greater than pending quantity({2})"
@@ -19692,7 +19697,7 @@ msgstr "Для удобства клиентов эти коды можно ис
#: erpnext/stock/doctype/stock_entry/stock_entry.py:978
msgid "For the item {0}, the consumed quantity should be {1} according to the BOM {2}."
msgstr ""
msgstr "Для изделия {0} количество потребленного материала должно быть {1} согласно спецификации материалов {2}."
#: erpnext/public/js/controllers/transaction.js:1299
msgctxt "Clear payment terms template and/or payment schedule when due date is changed"
@@ -20198,7 +20203,7 @@ msgstr "Условия и положения выполнения"
#: erpnext/stock/doctype/shipment/shipment.js:275
msgid "Full Name, Email or Phone/Mobile of the user are mandatory to continue."
msgstr ""
msgstr "Для продолжения необходимо указать полное имя, адрес электронной почты или номер телефона/мобильного телефона пользователя."
#. Option for the 'Reference Type' (Select) field in DocType 'Journal Entry
#. Account'
@@ -20476,11 +20481,11 @@ msgstr "Создать запись закрытия складского зап
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:112
msgid "Generate To Delete List"
msgstr ""
msgstr "Создать список для удаления"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:470
msgid "Generate To Delete list first"
msgstr ""
msgstr "Сначала сгенерируйте список для удаления"
#. Description of a DocType
#: erpnext/stock/doctype/packing_slip/packing_slip.json
@@ -30013,7 +30018,7 @@ msgstr "Новые расходы"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:1
msgid "New Fiscal Year - {0}"
msgstr ""
msgstr "Новый финансовый год - {0}"
#. Label of the income (Check) field in DocType 'Email Digest'
#: erpnext/setup/doctype/email_digest/email_digest.json
@@ -30183,7 +30188,7 @@ msgstr "Нет примечания о доставке для клиента {}
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:751
msgid "No DocTypes in To Delete list. Please generate or import the list before submitting."
msgstr ""
msgstr "В списке «Для удаления» нет DocTypes. Пожалуйста, сгенерируйте или импортируйте список перед отправкой."
#: erpnext/public/js/utils/ledger_preview.js:64
msgid "No Impact on Accounting Ledger"
@@ -30203,7 +30208,7 @@ msgstr "Не выбрано ни одного товара для передач
#: erpnext/selling/doctype/sales_order/sales_order.js:1226
msgid "No Items with Bill of Materials to Manufacture or all items already manufactured"
msgstr ""
msgstr "Нет товаров, для которых имеется спецификация материалов для производства, или все товары уже изготовлены"
#: erpnext/selling/doctype/sales_order/sales_order.js:1372
msgid "No Items with Bill of Materials."
@@ -30334,7 +30339,7 @@ msgstr "Различий по складскому счёту {0} не обна
#: erpnext/crm/doctype/email_campaign/email_campaign.py:150
msgid "No email found for {0} {1}"
msgstr ""
msgstr "Адрес электронной почты для {0} {1} не найден"
#: erpnext/telephony/doctype/call_log/call_log.py:117
msgid "No employee was scheduled for call popup"
@@ -30489,7 +30494,7 @@ msgstr "Не найдено принятых транзакций"
#: erpnext/crm/doctype/email_campaign/email_campaign.py:158
msgid "No recipients found for campaign {0}"
msgstr ""
msgstr "Получателей для кампании {0} не найдено."
#: erpnext/accounts/report/purchase_register/purchase_register.py:45
#: erpnext/accounts/report/sales_register/sales_register.py:46
@@ -30515,7 +30520,7 @@ msgstr "Нет зарезервированных запасов для отме
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:152
msgid "No rows with zero document count found"
msgstr ""
msgstr "Строки с нулевым количеством документов не найдены"
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:804
msgid "No stock ledger entries were created. Please set the quantity or valuation rate for the items properly and try again."
@@ -31121,7 +31126,7 @@ msgstr "Для импорта данных можно использовать
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:1067
msgid "Only CSV files are allowed"
msgstr ""
msgstr "Разрешается использовать только CSV-файлы"
#. Label of the tax_on_excess_amount (Check) field in DocType 'Tax Withholding
#. Category'
@@ -31438,7 +31443,7 @@ msgstr "Открытие счета"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "Opening Invoice Tool"
msgstr ""
msgstr "Инструмент для открытия счета-фактуры"
#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1646
#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1990
@@ -32209,7 +32214,7 @@ msgstr "Внешний"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/subcontracting.json
msgid "Outward Order"
msgstr ""
msgstr "Исходящий заказ"
#. Label of the over_billing_allowance (Currency) field in DocType 'Accounts
#. Settings'
@@ -32362,7 +32367,7 @@ msgstr "Владелец"
#. Label of the asset_owner_section (Section Break) field in DocType 'Asset'
#: erpnext/assets/doctype/asset/asset.json
msgid "Ownership"
msgstr ""
msgstr "Собственность"
#. Label of the p_l_closing_balance (JSON) field in DocType 'Process Period
#. Closing Voucher'
@@ -32457,7 +32462,7 @@ msgstr "Закрытие точки продаж не удалось при вы
#. Profile'
#: erpnext/accounts/doctype/pos_profile/pos_profile.json
msgid "POS Configurations"
msgstr ""
msgstr "Настройки POS"
#. Name of a DocType
#: erpnext/accounts/doctype/pos_customer_group/pos_customer_group.json
@@ -33857,7 +33862,7 @@ msgstr "Режим платежа"
#. 'Accounts Settings'
#: erpnext/accounts/doctype/accounts_settings/accounts_settings.json
msgid "Payment Options"
msgstr ""
msgstr "Варианты оплаты"
#. Label of the payment_order (Link) field in DocType 'Journal Entry'
#. Label of the payment_order (Link) field in DocType 'Payment Entry'
@@ -33924,7 +33929,7 @@ msgstr "Платеж получен"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/payments.json
msgid "Payment Reconciliaition"
msgstr ""
msgstr "Сверка платежей"
#. Name of a DocType
#. Label of the payment_reconciliation (Table) field in DocType 'POS Closing
@@ -35257,7 +35262,7 @@ msgstr "Пожалуйста, введите утверждении роли и
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:677
msgid "Please enter Batch No"
msgstr ""
msgstr "Пожалуйста, введите номер партии"
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:970
msgid "Please enter Cost Center"
@@ -35322,7 +35327,7 @@ msgstr "Пожалуйста, укажите корневой тип для сч
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:679
msgid "Please enter Serial No"
msgstr ""
msgstr "Пожалуйста, введите серийный номер"
#: erpnext/public/js/utils/serial_no_batch_selector.js:309
msgid "Please enter Serial Nos"
@@ -35423,7 +35428,7 @@ msgstr "Пожалуйста, заполните таблицу заказов
#: erpnext/stock/doctype/shipment/shipment.js:277
msgid "Please first set Full Name, Email and Phone for the user"
msgstr ""
msgstr "Пожалуйста, сначала укажите полное имя, адрес электронной почты и номер телефона пользователя"
#: erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js:94
msgid "Please fix overlapping time slots for {0}"
@@ -35435,11 +35440,11 @@ msgstr "Пожалуйста, исправьте накладывающиеся
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:270
msgid "Please generate To Delete list before submitting"
msgstr ""
msgstr "Пожалуйста, сгенерируйте список для удаления перед проведением"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:70
msgid "Please generate the To Delete list before submitting"
msgstr ""
msgstr "Пожалуйста, сгенерируйте список для удаления перед проведением"
#: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py:67
msgid "Please import accounts against parent company or enable {} in company master."
@@ -35488,7 +35493,7 @@ msgstr "Пожалуйста, обновите или сбросьте прив
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:43
msgid "Please review the {0} configuration and complete any required financial setup activities."
msgstr ""
msgstr "Пожалуйста, проверьте конфигурацию {0} и выполните все необходимые действия по настройке финансовых параметров."
#: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js:12
#: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js:28
@@ -36577,7 +36582,7 @@ msgstr "Предыдущий финансовый год не закрыт"
#: erpnext/stock/report/negative_batch_report/negative_batch_report.py:54
msgid "Previous Qty"
msgstr ""
msgstr "Предыдущее количество"
#. Label of the previous_work_experience (Section Break) field in DocType
#. 'Employee'
@@ -36667,7 +36672,7 @@ msgstr "Прайс-лист"
#. DocType 'POS Profile'
#: erpnext/accounts/doctype/pos_profile/pos_profile.json
msgid "Price List & Currency"
msgstr ""
msgstr "Прайс-лист и валюта"
#. Name of a DocType
#: erpnext/stock/doctype/price_list_country/price_list_country.json
@@ -37325,7 +37330,7 @@ msgstr "Обработка файлов XML"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:188
msgid "Processing import..."
msgstr ""
msgstr "Обработка импорта..."
#: erpnext/buying/doctype/supplier/supplier_dashboard.py:10
msgid "Procurement"
@@ -37503,7 +37508,7 @@ msgstr "Производство товара"
#: erpnext/manufacturing/doctype/bom/bom.json
#: erpnext/manufacturing/doctype/work_order/work_order.json
msgid "Production Item Info"
msgstr ""
msgstr "Информация о товаре"
#. Label of the production_plan (Link) field in DocType 'Purchase Order Item'
#. Name of a DocType
@@ -38003,7 +38008,7 @@ msgstr "Перспективные, но не работающие"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:196
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:781
msgid "Protected DocType"
msgstr ""
msgstr "Защищенный DocType"
#. Description of the 'Company Email' (Data) field in DocType 'Employee'
#: erpnext/setup/doctype/employee/employee.json
@@ -39205,17 +39210,17 @@ msgstr "Название шаблона проверки качества"
#: erpnext/manufacturing/doctype/job_card/job_card.py:782
msgid "Quality Inspection is required for the item {0} before completing the job card {1}"
msgstr ""
msgstr "Перед заполнением накладной {1} необходимо провести контроль качества изделия {0}"
#: erpnext/manufacturing/doctype/job_card/job_card.py:793
#: erpnext/manufacturing/doctype/job_card/job_card.py:802
msgid "Quality Inspection {0} is not submitted for the item: {1}"
msgstr ""
msgstr "Контроль качества {0} не проведён для товара: {1}"
#: erpnext/manufacturing/doctype/job_card/job_card.py:812
#: erpnext/manufacturing/doctype/job_card/job_card.py:821
msgid "Quality Inspection {0} is rejected for the item: {1}"
msgstr ""
msgstr "Контроль качества {0} отклоняется для изделия: {1}"
#: erpnext/public/js/controllers/transaction.js:384
#: erpnext/stock/doctype/stock_entry/stock_entry.js:196
@@ -39702,7 +39707,7 @@ msgstr "Указанная сумма"
#. in DocType 'Supplier'
#: erpnext/buying/doctype/supplier/supplier.json
msgid "RFQ and Purchase Order Settings"
msgstr ""
msgstr "Настройки запроса коммерческого предложения и заказа на закупку"
#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:120
msgid "RFQs are not allowed for {0} due to a scorecard standing of {1}"
@@ -40158,7 +40163,7 @@ msgstr "Потребление сырья"
#: erpnext/stock/doctype/stock_entry/stock_entry.py:317
msgid "Raw Materials Missing"
msgstr ""
msgstr "Отсутствует сырье"
#. Label of the raw_materials_received_section (Section Break) field in DocType
#. 'Subcontracting Inward Order'
@@ -41147,7 +41152,7 @@ msgstr "Удалить номер родительской строки в та
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:140
msgid "Remove Zero Counts"
msgstr ""
msgstr "Удалить нулевые значения"
#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js:9
msgid "Remove item if charges is not applicable to that item"
@@ -43126,7 +43131,7 @@ msgstr "Строка #{0}: Запись в журнале {1} не имеет у
#: erpnext/assets/doctype/asset_category/asset_category.py:149
msgid "Row #{0}: Missing <b>{1}</b> for company <b>{2}</b>."
msgstr ""
msgstr "Строка #{0}: Отсутствует <b>{1}</b> для компании <b>{2}</b>."
#: erpnext/assets/doctype/asset/asset.py:678
msgid "Row #{0}: Next Depreciation Date cannot be before Available-for-use Date"
@@ -43271,7 +43276,10 @@ msgid "Row #{0}: Selling rate for item {1} is lower than its {2}.\n"
"\t\t\t\t\tSelling {3} should be atleast {4}.<br><br>Alternatively,\n"
"\t\t\t\t\tyou can disable '{5}' in {6} to bypass\n"
"\t\t\t\t\tthis validation."
msgstr ""
msgstr "Строка #{0}: Продажный курс для товара {1} ниже, чем для его {2}.\n"
"\t\t\t\t\tПродажный курс для {3} должен быть не ниже {4}.<br><br>В качестве альтернативы,\n"
"\t\t\t\t\tвы можете отключить '{5}' в {6}, чтобы обойти\n"
"\t\t\t\t\tэту проверку."
#: erpnext/manufacturing/doctype/work_order/work_order.py:276
msgid "Row #{0}: Sequence ID must be {1} or {2} for Operation {3}."
@@ -43705,7 +43713,7 @@ msgstr "Строка {0}: Позиция {1} должна быть субпод
#: erpnext/controllers/subcontracting_controller.py:183
msgid "Row {0}: Item {1} must be linked to a {2}."
msgstr ""
msgstr "Строка {0}: Элемент {1} должен быть связан с {2}."
#: erpnext/controllers/subcontracting_controller.py:204
msgid "Row {0}: Item {1}'s quantity cannot be higher than the available quantity."
@@ -45845,7 +45853,7 @@ msgstr "Настройки серии и партии товара"
#. Label of the section_break_jcmx (Section Break) field in DocType 'Job Card'
#: erpnext/manufacturing/doctype/job_card/job_card.json
msgid "Serial / Batch"
msgstr ""
msgstr "Серийный номер/номер партии"
#. Label of the serial_and_batch_bundle (Link) field in DocType 'Stock
#. Reconciliation Item'
@@ -46642,7 +46650,7 @@ msgstr "Установить общую сумму на метод оплаты
#. 'Selling Settings'
#: erpnext/selling/doctype/selling_settings/selling_settings.json
msgid "Set Incoming Rate as Zero for Expired Batch"
msgstr ""
msgstr "Установить нулевую стоимость входящей партии для просроченных товаров"
#. Description of the 'Territory Targets' (Section Break) field in DocType
#. 'Territory'
@@ -47590,7 +47598,7 @@ msgstr "Одновременный"
#: erpnext/assets/doctype/asset_category/asset_category.py:183
msgid "Since there are active depreciable assets under this category, the following accounts are required. <br><br>"
msgstr ""
msgstr "Поскольку в этой категории имеются активные амортизируемые активы, необходимы следующие счета. <br><br>"
#: erpnext/stock/doctype/stock_entry/stock_entry.py:688
msgid "Since there is a process loss of {0} units for the finished good {1}, you should reduce the quantity by {0} units for the finished good {1} in the Items Table."
@@ -47644,7 +47652,7 @@ msgstr "Пропустить передачу материалов на скла
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:561
msgid "Skipped {0} DocType(s):<br>{1}"
msgstr ""
msgstr "Пропущено {0} DocType(s):<br>{1}"
#. Label of the customer_skype (Data) field in DocType 'Appointment'
#: erpnext/crm/doctype/appointment/appointment.json
@@ -49952,7 +49960,7 @@ msgstr "Время поставки от поставщика (дни)"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Supplier Ledger"
msgstr ""
msgstr "Главная книга поставщика"
#. Name of a report
#. Label of a Link in the Financial Reports Workspace
@@ -50848,7 +50856,7 @@ msgstr "Настройки налогов"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/selling.json
msgid "Tax Template"
msgstr ""
msgstr "Шаблон Налога"
#: erpnext/accounts/doctype/tax_rule/tax_rule.py:83
msgid "Tax Template is mandatory."
@@ -51682,7 +51690,7 @@ msgstr "Поля от Акционера и Акционера не могут
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:40
msgid "The fiscal year has been automatically created in a Disabled state to maintain consistency with the previous fiscal year's status."
msgstr ""
msgstr "Для обеспечения согласованности со статусом предыдущего финансового года финансовый год был автоматически создан в состоянии «Отключено»."
#: erpnext/accounts/doctype/share_transfer/share_transfer.py:240
msgid "The folio numbers are not matching"
@@ -52562,7 +52570,7 @@ msgstr "Ко времени"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:118
msgid "To Delete list generated with {0} DocTypes"
msgstr ""
msgstr "Список для удаления, создан для {0} типов документов"
#. Option for the 'Sales Order Status' (Select) field in DocType 'Production
#. Plan'
@@ -53113,7 +53121,7 @@ msgstr "Всего завершено кол-во"
#: erpnext/manufacturing/doctype/job_card/job_card.py:189
msgid "Total Completed Qty is required for Job Card {0}, please start and complete the job card before submission"
msgstr ""
msgstr "Для ввода данных в карточку задания {0} необходимо указать общее количество выполненных работ. Пожалуйста, начните и завершите заполнение карточки задания перед проведением"
#. Label of the total_consumed_material_cost (Currency) field in DocType
#. 'Project'
@@ -53522,7 +53530,7 @@ msgstr "Совокупный налог"
#: erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py:109
msgid "Total Taxable Amount"
msgstr ""
msgstr "Общая налогооблагаемая сумма"
#. Label of the total_taxes_and_charges (Currency) field in DocType 'Payment
#. Entry'
@@ -53747,7 +53755,7 @@ msgstr "Всего (кол-во)"
#: erpnext/selling/doctype/sales_order/sales_order.json
#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
msgid "Totals (Company Currency)"
msgstr ""
msgstr "Всего (валюта компании)"
#: erpnext/stock/doctype/item/item_dashboard.py:33
msgid "Traceability"
@@ -53856,15 +53864,15 @@ msgstr "Элемент записи удаления транзакции"
#. Name of a DocType
#: erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.json
msgid "Transaction Deletion Record To Delete"
msgstr ""
msgstr "Запись удаления транзакции"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:1098
msgid "Transaction Deletion Record {0} is already running. {1}"
msgstr ""
msgstr "Запись удаления транзакции {0} уже выполняется. {1}"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:1117
msgid "Transaction Deletion Record {0} is currently deleting {1}. Cannot save documents until deletion completes."
msgstr ""
msgstr "Запись удаления транзакции {0} в настоящее время удаляет {1}. Невозможно сохранить документы до завершения процесса."
#. Label of the transaction_details_section (Section Break) field in DocType
#. 'GL Entry'
@@ -53900,7 +53908,7 @@ msgstr "Название транзакции"
#: erpnext/stock/report/negative_batch_report/negative_batch_report.py:60
msgid "Transaction Qty"
msgstr ""
msgstr "Количество транзакций"
#. Label of the transaction_settings_section (Tab Break) field in DocType
#. 'Buying Settings'
@@ -53934,7 +53942,7 @@ msgstr "Валюта транзакции: {0} не может отличать
#: erpnext/assets/doctype/asset_movement/asset_movement.py:65
msgid "Transaction date can't be earlier than previous movement date"
msgstr ""
msgstr "Дата транзакции не может быть раньше предыдущей даты движения"
#. Description of the 'Applicable For' (Section Break) field in DocType 'Tax
#. Withholding Entry'
@@ -54500,7 +54508,7 @@ msgstr "URL может быть только строкой"
#: erpnext/selling/doctype/sales_order/sales_order.json
#: erpnext/stock/doctype/delivery_note/delivery_note.json
msgid "UTM Analytics"
msgstr ""
msgstr "UTM аналитика"
#. Option for the 'Data Fetch Method' (Select) field in DocType 'Accounts
#. Settings'
@@ -54519,7 +54527,7 @@ msgstr "Несогласованные распределения"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:464
msgid "Unable to fetch DocType details. Please contact system administrator."
msgstr ""
msgstr "Не удалось получить детали DocType. Пожалуйста, свяжитесь с системным администратором."
#: erpnext/setup/utils.py:182
msgid "Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually"
@@ -54608,7 +54616,7 @@ msgstr "В таблице «Рабочие часы» можно добавит
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:933
msgid "Unexpected Naming Series Pattern"
msgstr ""
msgstr "Непредвиденный шаблон именования серий"
#. Option for the 'Fulfilment Status' (Select) field in DocType 'Contract'
#: erpnext/crm/doctype/contract/contract.json
@@ -55212,7 +55220,7 @@ msgstr "Использовать многоуровневую специфика
#. DocType 'Global Defaults'
#: erpnext/setup/doctype/global_defaults/global_defaults.json
msgid "Use Posting Datetime for Naming Documents"
msgstr ""
msgstr "Использовать дату и время проведения для именования документов"
#. Label of the fallback_to_default_price_list (Check) field in DocType
#. 'Selling Settings'
@@ -55358,7 +55366,7 @@ msgstr "Пользователи могут включить флажок, ес
#. 'BOM'
#: erpnext/manufacturing/doctype/bom/bom.json
msgid "Users can make manufacture entry against Job Cards"
msgstr ""
msgstr "Пользователи могут вносить производственные записи на основании заказ-нарядов"
#. Description of the 'Role Allowed to Over Bill ' (Link) field in DocType
#. 'Accounts Settings'
@@ -56023,7 +56031,7 @@ msgstr "Vimeo"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:214
msgid "Virtual DocType"
msgstr ""
msgstr "Виртуальный DocType"
#: erpnext/templates/pages/help.html:46
msgid "Visit the forums"
@@ -56558,7 +56566,7 @@ msgstr "Внимание: Сделка {0} уже существует по За
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:75
msgid "Warning: This action cannot be undone!"
msgstr ""
msgstr "Внимание: Это действие нельзя отменить!"
#: erpnext/accounts/doctype/financial_report_template/financial_report_validation.py:74
msgid "Warnings"
@@ -56640,7 +56648,7 @@ msgstr "Длина волны в мегаметрах"
#: erpnext/controllers/accounts_controller.py:190
msgid "We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck the '{2}' checkbox."
msgstr ""
msgstr "Мы видим, что {0} создано для {1}. Если вы хотите обновить незавершенные операции {1}, снимите флажок '{2}'."
#: erpnext/www/support/index.html:7
msgid "We're here to help!"
@@ -56780,7 +56788,7 @@ msgstr "Как я могу вам помочь?"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:82
msgid "What will be deleted:"
msgstr ""
msgstr "Что будет удалено:"
#. Label of the whatsapp_no (Data) field in DocType 'Lead'
#. Label of the whatsapp (Data) field in DocType 'Opportunity'
@@ -56816,7 +56824,7 @@ msgstr "Если этот флажок установлен, то к каждо
#. in DocType 'Global Defaults'
#: erpnext/setup/doctype/global_defaults/global_defaults.json
msgid "When checked, the system will use the posting datetime of the document for naming the document instead of the creation datetime of the document."
msgstr ""
msgstr "Если этот параметр установлен, система будет использовать дату и время публикации документа для его именования вместо даты и времени создания документа."
#: erpnext/stock/doctype/item/item.js:1079
msgid "When creating an Item, entering a value for this field will automatically create an Item Price at the backend."
@@ -57430,7 +57438,7 @@ msgstr "Вы можете изменить родительский счет н
#: erpnext/assets/doctype/asset_category/asset_category.py:186
msgid "You can either configure default depreciation accounts in the Company or set the required accounts in the following rows: <br><br>"
msgstr ""
msgstr "Вы можете либо настроить счета амортизации по умолчанию в разделе «Компания», либо указать необходимые счета в следующих строках: <br><br>"
#: erpnext/accounts/doctype/journal_entry/journal_entry.py:703
msgid "You can not enter current voucher in 'Against Journal Entry' column"
@@ -57459,7 +57467,7 @@ msgstr "Вы можете задать его как имя машины или
#: erpnext/controllers/accounts_controller.py:211
msgid "You can use {0} to reconcile against {1} later."
msgstr ""
msgstr "Вы можете использовать {0} для сверки с {1} позже."
#: erpnext/manufacturing/doctype/job_card/job_card.py:1315
msgid "You can't make any changes to Job Card since Work Order is closed."
@@ -57675,7 +57683,7 @@ msgstr "в процентах от количества готовой прод
#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1472
msgid "as of {0}"
msgstr ""
msgstr "по состоянию на {0}"
#: erpnext/www/book_appointment/index.html:43
msgid "at"
@@ -58149,7 +58157,7 @@ msgstr "{0} в строке {1}"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:450
msgid "{0} is a child table and will be deleted automatically with its parent"
msgstr ""
msgstr "{0} — это дочерняя таблица, и она будет автоматически удалена вместе со своей родительской таблицей"
#: erpnext/accounts/doctype/pos_profile/pos_profile.py:95
msgid "{0} is a mandatory Accounting Dimension. <br>Please set a value for {0} in Accounting Dimensions section."
@@ -58276,7 +58284,7 @@ msgstr "{0} единиц товара {1} нет в наличии ни на о
#: erpnext/stock/doctype/pick_list/pick_list.py:1009
msgid "{0} units of Item {1} is not available in any of the warehouses. Other Pick Lists exist for this item."
msgstr ""
msgstr "{0} единиц товара {1} нет в наличии ни на одном из складов. Для этого товара существуют другие списки комплектации."
#: erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py:144
msgid "{0} units of {1} are required in {2} with the inventory dimension: {3} on {4} {5} for {6} to complete the transaction."
@@ -58524,19 +58532,19 @@ msgstr "{0}, завершите операцию {1} перед операцие
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:521
msgid "{0}: Child table (auto-deleted with parent)"
msgstr ""
msgstr "{0}: Дочерняя таблица (автоматически удаляется вместе с родительской)"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:516
msgid "{0}: Not found"
msgstr ""
msgstr "{0} Не найдено"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:512
msgid "{0}: Protected DocType"
msgstr ""
msgstr "{0}: Защищенный DocType"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:526
msgid "{0}: Virtual DocType (no database table)"
msgstr ""
msgstr "{0}: Виртуальный DocType (нет таблицы в базе данных)"
#: erpnext/controllers/accounts_controller.py:539
msgid "{0}: {1} does not belong to the Company: {2}"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2026-02-22 09:43+0000\n"
"PO-Revision-Date: 2026-02-22 16:38\n"
"PO-Revision-Date: 2026-02-27 16:51\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Serbian (Cyrillic)\n"
"MIME-Version: 1.0\n"
@@ -25,7 +25,12 @@ msgid "\n"
"\t\t\tIf it is not possible to make an adjustment entry, please enable 'Allow Negative Stock for Batch' in Stock Settings to proceed.\n"
"\t\t\tHowever, enabling this setting may lead to negative stock in the system.\n"
"\t\t\tSo please ensure the stock levels are adjusted as soon as possible to maintain the correct valuation rate."
msgstr ""
msgstr "\n"
"\t\t\tШаржа {0} ставке {1} има негативно стање залиха у складишту {2}{3}.\n"
"\t\t\tМолимо Вас да унесете количину залиха од {4} како бисте наставили са овим уносом.\n"
"\t\t\tУколико није могуће извршити корективно књижење, омогућите опцију 'Дозволи негативно стање залиха за шаржу' у подешавањима залиха како бисте наставили.\n"
"\t\t\tМеђутим, омогућавањем ове опције може довести до негативног стања залиха у систему.\n"
"\t\t\tОбавезно ускладите стање залиха што је пре могуће како би стопа вредновања остала тачна."
#. Label of the column_break_32 (Column Break) field in DocType 'Email Digest'
#: erpnext/setup/doctype/email_digest/email_digest.json
@@ -1094,7 +1099,7 @@ msgstr "Нови састанак је заказан за Вас са {0}"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:3
msgid "A new fiscal year has been automatically created."
msgstr ""
msgstr "Нова фискална година је аутоматски креирана."
#: erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py:96
msgid "A template with tax category {0} already exists. Only one template is allowed with each tax category"
@@ -1149,7 +1154,7 @@ msgstr "Датум истека годишњег уговора о одржав
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "AP Summary"
msgstr ""
msgstr "Резиме обавеза"
#. Label of the api_details_section (Section Break) field in DocType 'Currency
#. Exchange Settings'
@@ -1160,7 +1165,7 @@ msgstr "API Детаљи"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "AR Summary"
msgstr ""
msgstr "Резиме потраживања"
#. Label of the awb_number (Data) field in DocType 'Shipment'
#: erpnext/stock/doctype/shipment/shipment.json
@@ -2132,7 +2137,7 @@ msgstr "Подешавање рачуна"
#: erpnext/desktop_icon/accounts_setup.json
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "Accounts Setup"
msgstr ""
msgstr "Подешавање рачуна"
#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1319
msgid "Accounts table cannot be blank."
@@ -7265,7 +7270,7 @@ msgstr "Рачун за прекорачење"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/banking.json
msgid "Bank Reconciliation"
msgstr ""
msgstr "Банкарско усклађивање"
#. Name of a report
#. Label of a Link in the Invoicing Workspace
@@ -8459,7 +8464,7 @@ msgstr "Датум почетка буџета"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/budget.json
msgid "Budget Variance"
msgstr ""
msgstr "Одступање од буџета"
#. Name of a report
#. Label of a Link in the Invoicing Workspace
@@ -8653,7 +8658,7 @@ msgstr "CC за"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "COA Importer"
msgstr ""
msgstr "Увоз контног оквира"
#. Option for the 'Barcode Type' (Select) field in DocType 'Item Barcode'
#: erpnext/stock/doctype/item_barcode/item_barcode.json
@@ -9287,7 +9292,7 @@ msgstr "Не може се поставити више подразумеван
#: erpnext/assets/doctype/asset_category/asset_category.py:108
msgid "Cannot set multiple account rows for the same company"
msgstr ""
msgstr "Није могуће поставити више редова рачуна за исту компанију"
#: erpnext/controllers/accounts_controller.py:3885
msgid "Cannot set quantity less than delivered quantity"
@@ -11361,7 +11366,7 @@ msgstr "Консолидовани финансијски извештај"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Consolidated Report"
msgstr ""
msgstr "Консолидован извештај"
#. Label of the consolidated_invoice (Link) field in DocType 'POS Invoice'
#. Label of the consolidated_invoice (Link) field in DocType 'POS Invoice Merge
@@ -13717,7 +13722,7 @@ msgstr "Купац локална наруџбина бр."
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Customer Ledger"
msgstr ""
msgstr "Књига купаца"
#. Name of a report
#. Label of a Link in the Financial Reports Workspace
@@ -14472,7 +14477,7 @@ msgstr "Подаци о ентитету где се врши одбитак"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/taxes.json
msgid "Deduction Certificate"
msgstr ""
msgstr "Потврда о одбитку"
#. Label of the deductions_or_loss_section (Section Break) field in DocType
#. 'Payment Entry'
@@ -16636,7 +16641,7 @@ msgstr "Број докумената"
#: erpnext/stock/report/negative_batch_report/negative_batch_report.py:78
msgid "Document No"
msgstr ""
msgstr "Број документа"
#. Label of the document_type (Link) field in DocType 'Subscription Invoice'
#: erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json
@@ -18654,7 +18659,7 @@ msgstr "ФИФО/ЛИФО ред чекања"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "FX Revaluation"
msgstr ""
msgstr "Ревалоризација девизног курса"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -18749,7 +18754,7 @@ msgstr "Повратна информација од"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/quality.json
msgid "Feedback Template"
msgstr ""
msgstr "Шаблон за повратне информације"
#. Option for the 'Reference Type' (Select) field in DocType 'Journal Entry
#. Account'
@@ -19322,7 +19327,7 @@ msgstr "Фискална година компаније"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:5
msgid "Fiscal Year Details"
msgstr ""
msgstr "Детаљи фискалне године"
#: erpnext/accounts/doctype/fiscal_year/fiscal_year.py:47
msgid "Fiscal Year End Date should be one year after Fiscal Year Start Date"
@@ -21898,7 +21903,7 @@ msgstr "Уколико је омогућено, изворно и циљно с
#. 'Stock Settings'
#: erpnext/stock/doctype/stock_settings/stock_settings.json
msgid "If enabled, the system will allow negative stock entries for the batch. But, this may lead to incorrect valuation rates, so it is recommended to avoid using this option. The system will permit negative stock only when it is caused by backdated entries and will validate and block negative stock in all other cases."
msgstr ""
msgstr "Уколико је омогућено, систем ће дозволити књижење негативног стања залиха за шаржу. То може довести до нетачне стопе вредновања, па се препоручује избегавање ове опције. Систем ће дозволити негативно стање само у случају ретроактивних књижења и блокираће негативно стање у свим осталим случајевима."
#. Description of the 'Allow UOM with Conversion Rate Defined in Item' (Check)
#. field in DocType 'Stock Settings'
@@ -23920,7 +23925,7 @@ msgstr "Улазно"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/subcontracting.json
msgid "Inward Order"
msgstr ""
msgstr "Налог за пријем"
#. Label of the is_account_payable (Check) field in DocType 'Cheque Print
#. Template'
@@ -26001,7 +26006,7 @@ msgstr "Регистар продаје по ставкама"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Item-wise sales Register"
msgstr ""
msgstr "Књига продаје по ставкама"
#: erpnext/stock/get_item_details.py:713
msgid "Item/Item Code required to get Item Tax Template."
@@ -26035,7 +26040,7 @@ msgstr "Потребне ставке"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/subcontracting.json
msgid "Items To Be Received"
msgstr ""
msgstr "Ставке за пријем"
#. Label of a Link in the Buying Workspace
#. Name of a report
@@ -28327,7 +28332,7 @@ msgstr "Издавање материјала"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/manufacturing.json
msgid "Material Planning"
msgstr ""
msgstr "Планирање материјала"
#. Option for the 'Purpose' (Select) field in DocType 'Stock Entry'
#. Option for the 'Purpose' (Select) field in DocType 'Stock Entry Type'
@@ -28605,7 +28610,7 @@ msgstr "Материјал ка добављачу"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/subcontracting.json
msgid "Materials To Be Transferred"
msgstr ""
msgstr "Материјал за пренос"
#: erpnext/controllers/subcontracting_controller.py:1569
msgid "Materials are already received against the {0} {1}"
@@ -28851,7 +28856,7 @@ msgstr "Поруке дуже од 160 карактера биће подеље
#: erpnext/setup/install.py:127
msgid "Messaging CRM Campaign"
msgstr ""
msgstr "CRM кампања за поруке"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -29148,7 +29153,7 @@ msgstr "Недостајући рачун"
#: erpnext/assets/doctype/asset_category/asset_category.py:191
msgid "Missing Accounts"
msgstr ""
msgstr "Недостајући рачуни"
#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:430
msgid "Missing Asset"
@@ -29193,7 +29198,7 @@ msgstr "Недостаје број серије пакета"
#: erpnext/assets/doctype/asset_category/asset_category.py:156
msgid "Missing account configuration for company <b>{0}</b>."
msgstr ""
msgstr "Недостаје конфигурација рачун за компанију <b>{0}</b>."
#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:154
msgid "Missing email template for dispatch. Please set one in Delivery Settings."
@@ -29613,7 +29618,7 @@ msgstr "Анализа потребна"
#. Name of a report
#: erpnext/stock/report/negative_batch_report/negative_batch_report.json
msgid "Negative Batch Report"
msgstr ""
msgstr "Извештај о шаржама са негативним стањем"
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:622
msgid "Negative Quantity is not allowed"
@@ -30015,7 +30020,7 @@ msgstr "Нови расходи"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:1
msgid "New Fiscal Year - {0}"
msgstr ""
msgstr "Нова фискална година - {0}"
#. Label of the income (Check) field in DocType 'Email Digest'
#: erpnext/setup/doctype/email_digest/email_digest.json
@@ -31440,7 +31445,7 @@ msgstr "Ставка почетне фактуре"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "Opening Invoice Tool"
msgstr ""
msgstr "Алат за унос почетних фактура"
#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1646
#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1990
@@ -32211,7 +32216,7 @@ msgstr "Излазно"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/subcontracting.json
msgid "Outward Order"
msgstr ""
msgstr "Налог за издавање"
#. Label of the over_billing_allowance (Currency) field in DocType 'Accounts
#. Settings'
@@ -33926,7 +33931,7 @@ msgstr "Плаћање примљено"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/payments.json
msgid "Payment Reconciliaition"
msgstr ""
msgstr "Усклађивање плаћања"
#. Name of a DocType
#. Label of the payment_reconciliation (Table) field in DocType 'POS Closing
@@ -35489,7 +35494,7 @@ msgstr "Молимо Вас да освежите или ресетујете Pl
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:43
msgid "Please review the {0} configuration and complete any required financial setup activities."
msgstr ""
msgstr "Погледајте конфигурацију {0} и довршите неопходна финансијска подешавања."
#: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js:12
#: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js:28
@@ -36578,7 +36583,7 @@ msgstr "Претходна фискална година није затворе
#: erpnext/stock/report/negative_batch_report/negative_batch_report.py:54
msgid "Previous Qty"
msgstr ""
msgstr "Претходна количина"
#. Label of the previous_work_experience (Section Break) field in DocType
#. 'Employee'
@@ -40652,7 +40657,7 @@ msgstr "Величина реда за усклађивање"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/banking.json
msgid "Reconciliation Statement"
msgstr ""
msgstr "Извештај о усклађености"
#. Label of the reconciliation_takes_effect_on (Select) field in DocType
#. 'Company'
@@ -40941,7 +40946,7 @@ msgstr "Регионални"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Registers"
msgstr ""
msgstr "Регистри"
#. Label of the registration_details (Code) field in DocType 'Company'
#: erpnext/setup/doctype/company/company.json
@@ -43127,7 +43132,7 @@ msgstr "Ред #{0}: Налог књижења {1} не садржи рачун
#: erpnext/assets/doctype/asset_category/asset_category.py:149
msgid "Row #{0}: Missing <b>{1}</b> for company <b>{2}</b>."
msgstr ""
msgstr "Ред #{0}: Недостаје <b>{1}</b> за компанију <b>{2}</b>."
#: erpnext/assets/doctype/asset/asset.py:678
msgid "Row #{0}: Next Depreciation Date cannot be before Available-for-use Date"
@@ -47596,7 +47601,7 @@ msgstr "Симултано"
#: erpnext/assets/doctype/asset_category/asset_category.py:183
msgid "Since there are active depreciable assets under this category, the following accounts are required. <br><br>"
msgstr ""
msgstr "Пошто постоје активна средства која се амортизују у овој категорији, следећи рачуни су обавезни. <br><br>"
#: erpnext/stock/doctype/stock_entry/stock_entry.py:688
msgid "Since there is a process loss of {0} units for the finished good {1}, you should reduce the quantity by {0} units for the finished good {1} in the Items Table."
@@ -49958,7 +49963,7 @@ msgstr "Време испоруке добављача (дани)"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Supplier Ledger"
msgstr ""
msgstr "Књига добављача"
#. Name of a report
#. Label of a Link in the Financial Reports Workspace
@@ -50854,7 +50859,7 @@ msgstr "Подешавање пореза"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/selling.json
msgid "Tax Template"
msgstr ""
msgstr "Порески шаблон"
#: erpnext/accounts/doctype/tax_rule/tax_rule.py:83
msgid "Tax Template is mandatory."
@@ -51689,7 +51694,7 @@ msgstr "Поља од власника и ка власнику не могу б
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:40
msgid "The fiscal year has been automatically created in a Disabled state to maintain consistency with the previous fiscal year's status."
msgstr ""
msgstr "Фискална година је аутоматски креирана у онемогућеном статусу ради усклађености са статусом претходне фискалне године."
#: erpnext/accounts/doctype/share_transfer/share_transfer.py:240
msgid "The folio numbers are not matching"
@@ -53907,7 +53912,7 @@ msgstr "Назив трансакције"
#: erpnext/stock/report/negative_batch_report/negative_batch_report.py:60
msgid "Transaction Qty"
msgstr ""
msgstr "Трансакциона количина"
#. Label of the transaction_settings_section (Tab Break) field in DocType
#. 'Buying Settings'
@@ -57437,7 +57442,7 @@ msgstr "Можете променити матични рачун у рачун
#: erpnext/assets/doctype/asset_category/asset_category.py:186
msgid "You can either configure default depreciation accounts in the Company or set the required accounts in the following rows: <br><br>"
msgstr ""
msgstr "Можете конфигурисати подразумеване рачуне амортизације у подешавањима компаније или унети потребне рачуне у следећим редовима: <br><br>"
#: erpnext/accounts/doctype/journal_entry/journal_entry.py:703
msgid "You can not enter current voucher in 'Against Journal Entry' column"
@@ -57682,7 +57687,7 @@ msgstr "као проценат количине финалне ставке"
#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1472
msgid "as of {0}"
msgstr ""
msgstr "на дан {0}"
#: erpnext/www/book_appointment/index.html:43
msgid "at"
@@ -58283,7 +58288,7 @@ msgstr "{0} јединица ставке {1} није доступно ни у
#: erpnext/stock/doctype/pick_list/pick_list.py:1009
msgid "{0} units of Item {1} is not available in any of the warehouses. Other Pick Lists exist for this item."
msgstr ""
msgstr "{0} јединица ставке {1} није доступно ни у једном складишту. Постоје друге листе за одабир за ову ставку."
#: erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py:144
msgid "{0} units of {1} are required in {2} with the inventory dimension: {3} on {4} {5} for {6} to complete the transaction."

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2026-02-22 09:43+0000\n"
"PO-Revision-Date: 2026-02-22 16:38\n"
"PO-Revision-Date: 2026-02-27 16:51\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Serbian (Latin)\n"
"MIME-Version: 1.0\n"
@@ -25,7 +25,12 @@ msgid "\n"
"\t\t\tIf it is not possible to make an adjustment entry, please enable 'Allow Negative Stock for Batch' in Stock Settings to proceed.\n"
"\t\t\tHowever, enabling this setting may lead to negative stock in the system.\n"
"\t\t\tSo please ensure the stock levels are adjusted as soon as possible to maintain the correct valuation rate."
msgstr ""
msgstr "\n"
"\t\t\tŠarža {0} stavke {1} ima negativno stanje zaliha u skladištu {2}{3}.\n"
"\t\t\tMolimo Vas da uneste količinu zaliha od {4} kako biste nastavili sa ovim unosom.\n"
"\t\t\tUkoliko nije moguće izvršiti korektivno knjiženje, omogućite opciju 'Dozvoli negativno stanje zaliha za šaržu' u podešavanjima zaliha kako biste nastavili.\n"
"\t\t\tMeđutim, omogućavanjem ove opcije može dovesti do negativnog stanja zaliha u sistemu.\n"
"\t\t\tObavezno uskladite stanje zaliha što je pre moguće kako bi stopa vrednovanja ostala tačna."
#. Label of the column_break_32 (Column Break) field in DocType 'Email Digest'
#: erpnext/setup/doctype/email_digest/email_digest.json
@@ -1094,7 +1099,7 @@ msgstr "Novi sastanak je zakazan za Vas sa {0}"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:3
msgid "A new fiscal year has been automatically created."
msgstr ""
msgstr "Nova fiskalna godina je automatski kreirana."
#: erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py:96
msgid "A template with tax category {0} already exists. Only one template is allowed with each tax category"
@@ -1149,7 +1154,7 @@ msgstr "Datum isteka godišnjeg ugovora o održavanju"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "AP Summary"
msgstr ""
msgstr "Rezime obaveza"
#. Label of the api_details_section (Section Break) field in DocType 'Currency
#. Exchange Settings'
@@ -1160,7 +1165,7 @@ msgstr "API Detalji"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "AR Summary"
msgstr ""
msgstr "Rezime potraživanja"
#. Label of the awb_number (Data) field in DocType 'Shipment'
#: erpnext/stock/doctype/shipment/shipment.json
@@ -2132,7 +2137,7 @@ msgstr "Podešavanje računa"
#: erpnext/desktop_icon/accounts_setup.json
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "Accounts Setup"
msgstr ""
msgstr "Podešavanje računa"
#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1319
msgid "Accounts table cannot be blank."
@@ -7265,7 +7270,7 @@ msgstr "Račun za prekoračenje"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/banking.json
msgid "Bank Reconciliation"
msgstr ""
msgstr "Bankarsko usklađivanje"
#. Name of a report
#. Label of a Link in the Invoicing Workspace
@@ -8459,7 +8464,7 @@ msgstr "Datum početka budžeta"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/budget.json
msgid "Budget Variance"
msgstr ""
msgstr "Odstupanje od budžeta"
#. Name of a report
#. Label of a Link in the Invoicing Workspace
@@ -8653,7 +8658,7 @@ msgstr "CC za"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "COA Importer"
msgstr ""
msgstr "Uvoz kontnog okvira"
#. Option for the 'Barcode Type' (Select) field in DocType 'Item Barcode'
#: erpnext/stock/doctype/item_barcode/item_barcode.json
@@ -9287,7 +9292,7 @@ msgstr "Ne može se postaviti više podrazumevanih stavki za jednu kompaniju."
#: erpnext/assets/doctype/asset_category/asset_category.py:108
msgid "Cannot set multiple account rows for the same company"
msgstr ""
msgstr "Nije moguće postaviti više redova računa za istu kompaniju"
#: erpnext/controllers/accounts_controller.py:3885
msgid "Cannot set quantity less than delivered quantity"
@@ -11361,7 +11366,7 @@ msgstr "Konsolidovani finansijski izveštaj"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Consolidated Report"
msgstr ""
msgstr "Konsolidovan izveštaj"
#. Label of the consolidated_invoice (Link) field in DocType 'POS Invoice'
#. Label of the consolidated_invoice (Link) field in DocType 'POS Invoice Merge
@@ -13717,7 +13722,7 @@ msgstr "Kupac lokalna narudžbina br."
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Customer Ledger"
msgstr ""
msgstr "Knjiga kupaca"
#. Name of a report
#. Label of a Link in the Financial Reports Workspace
@@ -14472,7 +14477,7 @@ msgstr "Podaci o entitetu gde se vrši odbitak"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/taxes.json
msgid "Deduction Certificate"
msgstr ""
msgstr "Potvrda o odbitku"
#. Label of the deductions_or_loss_section (Section Break) field in DocType
#. 'Payment Entry'
@@ -16636,7 +16641,7 @@ msgstr "Broj dokumenata"
#: erpnext/stock/report/negative_batch_report/negative_batch_report.py:78
msgid "Document No"
msgstr ""
msgstr "Broj dokumenta"
#. Label of the document_type (Link) field in DocType 'Subscription Invoice'
#: erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json
@@ -18654,7 +18659,7 @@ msgstr "FIFO/LIFO red čekanja"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "FX Revaluation"
msgstr ""
msgstr "Revalorizacija deviznog kursa"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -18749,7 +18754,7 @@ msgstr "Povratna informacija od"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/quality.json
msgid "Feedback Template"
msgstr ""
msgstr "Šablon za povratne informacije"
#. Option for the 'Reference Type' (Select) field in DocType 'Journal Entry
#. Account'
@@ -19322,7 +19327,7 @@ msgstr "Fiskalna godina kompanije"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:5
msgid "Fiscal Year Details"
msgstr ""
msgstr "Detalji fiskalne godine"
#: erpnext/accounts/doctype/fiscal_year/fiscal_year.py:47
msgid "Fiscal Year End Date should be one year after Fiscal Year Start Date"
@@ -21898,7 +21903,7 @@ msgstr "Ukoliko je omogućeno, izvorno i ciljno skladište u unosu zaliha prenos
#. 'Stock Settings'
#: erpnext/stock/doctype/stock_settings/stock_settings.json
msgid "If enabled, the system will allow negative stock entries for the batch. But, this may lead to incorrect valuation rates, so it is recommended to avoid using this option. The system will permit negative stock only when it is caused by backdated entries and will validate and block negative stock in all other cases."
msgstr ""
msgstr "Ukoliko je omogućeno, sistem će dozvoliti knjiženje negativnog stanja zaliha za šaržu. To može dovesti do netačne stope vrednovanja, pa se preporučuje izbegavanje ove opcije. Sistem će dozvoliti negativno stanje samo u slučaju retroaktivnih knjiženja i blokiraće negativno stanje u svim ostalim slučajevima."
#. Description of the 'Allow UOM with Conversion Rate Defined in Item' (Check)
#. field in DocType 'Stock Settings'
@@ -23920,7 +23925,7 @@ msgstr "Ulazno"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/subcontracting.json
msgid "Inward Order"
msgstr ""
msgstr "Nalog za prijem"
#. Label of the is_account_payable (Check) field in DocType 'Cheque Print
#. Template'
@@ -26001,7 +26006,7 @@ msgstr "Registar prodaje po stavkama"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Item-wise sales Register"
msgstr ""
msgstr "Knjiga prodaje po stavkama"
#: erpnext/stock/get_item_details.py:713
msgid "Item/Item Code required to get Item Tax Template."
@@ -26035,7 +26040,7 @@ msgstr "Potrebne stavke"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/subcontracting.json
msgid "Items To Be Received"
msgstr ""
msgstr "Stavke za prijem"
#. Label of a Link in the Buying Workspace
#. Name of a report
@@ -28327,7 +28332,7 @@ msgstr "Izdavanje materijala"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/manufacturing.json
msgid "Material Planning"
msgstr ""
msgstr "Planiranje materijala"
#. Option for the 'Purpose' (Select) field in DocType 'Stock Entry'
#. Option for the 'Purpose' (Select) field in DocType 'Stock Entry Type'
@@ -28605,7 +28610,7 @@ msgstr "Materijal ka dobavljaču"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/subcontracting.json
msgid "Materials To Be Transferred"
msgstr ""
msgstr "Materijal za prenos"
#: erpnext/controllers/subcontracting_controller.py:1569
msgid "Materials are already received against the {0} {1}"
@@ -28851,7 +28856,7 @@ msgstr "Poruke duže od 160 karaktera biće podeljene u više poruka"
#: erpnext/setup/install.py:127
msgid "Messaging CRM Campaign"
msgstr ""
msgstr "CRM kampanja za poruke"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -29148,7 +29153,7 @@ msgstr "Nedostajući račun"
#: erpnext/assets/doctype/asset_category/asset_category.py:191
msgid "Missing Accounts"
msgstr ""
msgstr "Nedostajući računi"
#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:430
msgid "Missing Asset"
@@ -29193,7 +29198,7 @@ msgstr "Nedostaje broj serije paketa"
#: erpnext/assets/doctype/asset_category/asset_category.py:156
msgid "Missing account configuration for company <b>{0}</b>."
msgstr ""
msgstr "Nedostaje konfiguracija računa za kompaniju <b>{0}</b>."
#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:154
msgid "Missing email template for dispatch. Please set one in Delivery Settings."
@@ -29613,7 +29618,7 @@ msgstr "Analiza potrebna"
#. Name of a report
#: erpnext/stock/report/negative_batch_report/negative_batch_report.json
msgid "Negative Batch Report"
msgstr ""
msgstr "Izveštaj o šaržama sa negativnim stanjem"
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:622
msgid "Negative Quantity is not allowed"
@@ -30015,7 +30020,7 @@ msgstr "Novi rashodi"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:1
msgid "New Fiscal Year - {0}"
msgstr ""
msgstr "Nova fiskalna godina - {0}"
#. Label of the income (Check) field in DocType 'Email Digest'
#: erpnext/setup/doctype/email_digest/email_digest.json
@@ -31440,7 +31445,7 @@ msgstr "Stavka početne fakture"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/accounts_setup.json
msgid "Opening Invoice Tool"
msgstr ""
msgstr "Alat za unos početnih faktura"
#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1646
#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1990
@@ -32211,7 +32216,7 @@ msgstr "Izlazno"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/subcontracting.json
msgid "Outward Order"
msgstr ""
msgstr "Nalog za izdavanje"
#. Label of the over_billing_allowance (Currency) field in DocType 'Accounts
#. Settings'
@@ -33926,7 +33931,7 @@ msgstr "Plaćanje primljeno"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/payments.json
msgid "Payment Reconciliaition"
msgstr ""
msgstr "Usklađivanje plaćanja"
#. Name of a DocType
#. Label of the payment_reconciliation (Table) field in DocType 'POS Closing
@@ -35489,7 +35494,7 @@ msgstr "Molimo Vas da osvežite ili resetujete Plaid vezu sa bankom {}."
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:43
msgid "Please review the {0} configuration and complete any required financial setup activities."
msgstr ""
msgstr "Pogledajte konfiguraciju {0} i dovršite neophodna finansijska podešavanja."
#: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js:12
#: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js:28
@@ -36578,7 +36583,7 @@ msgstr "Prethodna fiskalna godina nije zatvorena"
#: erpnext/stock/report/negative_batch_report/negative_batch_report.py:54
msgid "Previous Qty"
msgstr ""
msgstr "Prethodna količina"
#. Label of the previous_work_experience (Section Break) field in DocType
#. 'Employee'
@@ -40652,7 +40657,7 @@ msgstr "Veličina reda za usklađivanje"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/banking.json
msgid "Reconciliation Statement"
msgstr ""
msgstr "Izveštaj o usklađenosti"
#. Label of the reconciliation_takes_effect_on (Select) field in DocType
#. 'Company'
@@ -40941,7 +40946,7 @@ msgstr "Regionalni"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Registers"
msgstr ""
msgstr "Registri"
#. Label of the registration_details (Code) field in DocType 'Company'
#: erpnext/setup/doctype/company/company.json
@@ -43127,7 +43132,7 @@ msgstr "Red #{0}: Nalog knjiženja {1} ne sadrži račun {2} ili je već povezan
#: erpnext/assets/doctype/asset_category/asset_category.py:149
msgid "Row #{0}: Missing <b>{1}</b> for company <b>{2}</b>."
msgstr ""
msgstr "Red #{0}: Nedostaje <b>{1}</b> za kompaniju <b>{2}</b>."
#: erpnext/assets/doctype/asset/asset.py:678
msgid "Row #{0}: Next Depreciation Date cannot be before Available-for-use Date"
@@ -47596,7 +47601,7 @@ msgstr "Simultano"
#: erpnext/assets/doctype/asset_category/asset_category.py:183
msgid "Since there are active depreciable assets under this category, the following accounts are required. <br><br>"
msgstr ""
msgstr "Pošto postoje aktivna sredstva koja se amortizuju u ovoj kategoriji, sledeći računi su obavezni. <br><br>"
#: erpnext/stock/doctype/stock_entry/stock_entry.py:688
msgid "Since there is a process loss of {0} units for the finished good {1}, you should reduce the quantity by {0} units for the finished good {1} in the Items Table."
@@ -49958,7 +49963,7 @@ msgstr "Vreme isporuke dobavljača (dani)"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Supplier Ledger"
msgstr ""
msgstr "Knjiga dobavljača"
#. Name of a report
#. Label of a Link in the Financial Reports Workspace
@@ -50854,7 +50859,7 @@ msgstr "Podešavanje poreza"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/selling.json
msgid "Tax Template"
msgstr ""
msgstr "Poreski šablon"
#: erpnext/accounts/doctype/tax_rule/tax_rule.py:83
msgid "Tax Template is mandatory."
@@ -51689,7 +51694,7 @@ msgstr "Polja od vlasnika i ka vlasniku ne mogu biti prazna"
#: erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html:40
msgid "The fiscal year has been automatically created in a Disabled state to maintain consistency with the previous fiscal year's status."
msgstr ""
msgstr "Fiskalna godina je automatski kreirana u onemogućenom statusu radi usklađenosti sa statusom prethodne fiskalne godine."
#: erpnext/accounts/doctype/share_transfer/share_transfer.py:240
msgid "The folio numbers are not matching"
@@ -53907,7 +53912,7 @@ msgstr "Naziv transakcije"
#: erpnext/stock/report/negative_batch_report/negative_batch_report.py:60
msgid "Transaction Qty"
msgstr ""
msgstr "Transakciona količina"
#. Label of the transaction_settings_section (Tab Break) field in DocType
#. 'Buying Settings'
@@ -57437,7 +57442,7 @@ msgstr "Možete promeniti matični račun u račun bilansa stanja ili izabrati d
#: erpnext/assets/doctype/asset_category/asset_category.py:186
msgid "You can either configure default depreciation accounts in the Company or set the required accounts in the following rows: <br><br>"
msgstr ""
msgstr "Možete konfigurisati podrazumevane račune amortizacije u podešavanjima kompanije ili uneti potrebne račune u sledećim redovima: <br><br>"
#: erpnext/accounts/doctype/journal_entry/journal_entry.py:703
msgid "You can not enter current voucher in 'Against Journal Entry' column"
@@ -57682,7 +57687,7 @@ msgstr "kao procenat količine finalne stavke"
#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1472
msgid "as of {0}"
msgstr ""
msgstr "na dan {0}"
#: erpnext/www/book_appointment/index.html:43
msgid "at"
@@ -58283,7 +58288,7 @@ msgstr "{0} jedinica stavke {1} nije dostupno ni u jednom skladištu."
#: erpnext/stock/doctype/pick_list/pick_list.py:1009
msgid "{0} units of Item {1} is not available in any of the warehouses. Other Pick Lists exist for this item."
msgstr ""
msgstr "{0} jedinica stavke {1} nije dostupno ni u jednom skladištu. Postoje druge liste za odabir za ovu stavku."
#: erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py:144
msgid "{0} units of {1} are required in {2} with the inventory dimension: {3} on {4} {5} for {6} to complete the transaction."

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2026-02-22 09:43+0000\n"
"PO-Revision-Date: 2026-02-24 16:58\n"
"PO-Revision-Date: 2026-02-27 16:51\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Swedish\n"
"MIME-Version: 1.0\n"
@@ -28,7 +28,7 @@ msgid "\n"
msgstr "\n"
"\t\t\tParti {0} av artikel {1} har negativt lager på lager {2}{3}.\n"
"\t\t\tLägg till lager kvantitet på {4} för att gå vidare med denna post.\n"
"\t\t\tOm det inte är möjligt att göra justering postering, aktivera \"Tillåt Negativt Lager för Parti\" i Lager Inställningar för att fortsätta.\n"
"\t\t\tOm det inte är möjligt att göra justering post, aktivera \"Tillåt Negativt Lager för Parti\" i Lager Inställningar för att fortsätta.\n"
"\t\t\tVid aktivering av denna inställning kan det dock leda till negativt lager i system.\n"
"\t\t\tSe till att lager nivåer justeras så snart som möjligt för att bibehålla korrekt grund pris."
@@ -2027,7 +2027,7 @@ msgstr "Bokföring"
#: erpnext/accounts/doctype/accounts_settings/accounts_settings.json
#: erpnext/setup/doctype/company/company.json
msgid "Accounts Closing"
msgstr "Bokföring Stängning"
msgstr "Bokföring Period"
#. Label of the accounts_frozen_till_date (Date) field in DocType 'Company'
#: erpnext/setup/doctype/company/company.json
@@ -3459,7 +3459,7 @@ msgstr "Mot Intäkt Konto"
#: erpnext/accounts/doctype/journal_entry/journal_entry.py:729
#: erpnext/accounts/doctype/payment_entry/payment_entry.py:776
msgid "Against Journal Entry {0} does not have any unmatched {1} entry"
msgstr "Mot Journal Post {0} som inte har någon oavstämd {1} post"
msgstr "Mot Journal Post {0} som inte har någon ej avstämd {1} post"
#: erpnext/accounts/doctype/gl_entry/gl_entry.py:393
msgid "Against Journal Entry {0} is already adjusted against some other voucher"
@@ -5278,13 +5278,13 @@ msgstr "Arshin"
#: erpnext/stock/report/stock_ageing/stock_ageing.js:16
#: erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.js:30
msgid "As On Date"
msgstr "Som den"
msgstr "Datum"
#: erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js:15
#: erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.js:15
#: erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.js:15
msgid "As on Date"
msgstr "Som den"
msgstr "Datum"
#. Description of the 'Finished Good Quantity ' (Float) field in DocType 'Stock
#. Entry'
@@ -13245,7 +13245,7 @@ msgstr "Aktuell Lager"
#. Reconciliation Item'
#: erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
msgid "Current Valuation Rate"
msgstr "Aktuell Värderingssats"
msgstr "Aktuell Grund Pris"
#: erpnext/selling/report/sales_analytics/sales_analytics.js:90
msgid "Curves"
@@ -17605,7 +17605,7 @@ msgstr "Aktivera System Övervakning"
#. Settings'
#: erpnext/accounts/doctype/accounts_settings/accounts_settings.json
msgid "Enable Immutable Ledger"
msgstr "Aktivera Oförenderlig Register"
msgstr "Aktivera Oförenderlig Bokföring"
#. Label of the enable_item_wise_inventory_account (Check) field in DocType
#. 'Company'
@@ -19659,7 +19659,7 @@ msgstr "För projekt {0}, uppdatera din status"
#: erpnext/manufacturing/doctype/master_production_schedule/master_production_schedule.json
#: erpnext/manufacturing/doctype/sales_forecast/sales_forecast.json
msgid "For projected and forecast quantities, the system will consider all child warehouses under the selected parent warehouse."
msgstr "För beräknade och prognostiserade kvantiteter kommer system att inkludera alla underordnade lager under vald överordnad lager."
msgstr "För beräknade och förväntade kvantiteter kommer system att inkludera alla underordnade lager under vald överordnad lager."
#: erpnext/stock/doctype/stock_entry/stock_entry.py:1703
msgid "For quantity {0} should not be greater than allowed quantity {1}"
@@ -20744,7 +20744,7 @@ msgstr "Hämta Tidrapporter"
#: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js:102
#: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js:107
msgid "Get Unreconciled Entries"
msgstr "Hämta Oavstämda Poster"
msgstr "Hämta Ej Avstämda Poster"
#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:69
msgid "Get stops from"
@@ -21890,7 +21890,7 @@ msgstr "Om aktiverad kommer Konsoliderad Faktura att ha avrundad totalt inaktive
#. field in DocType 'Stock Settings'
#: erpnext/stock/doctype/stock_settings/stock_settings.json
msgid "If enabled, the item rate won't adjust to the valuation rate during internal transfers, but accounting will still use the valuation rate."
msgstr "Om aktiverad, kommer artikel pris inte att anpassas till värderingssats vid interna överföringar, men bokföring kommer fortfarande att använda värderingssats."
msgstr "Om aktiverad, kommer artikel pris inte att sättas till grund pris vid interna överföringar, men bokföring kommer fortfarande att använda grund pris."
#. Description of the 'Validate Material Transfer Warehouses' (Check) field in
#. DocType 'Stock Settings'
@@ -22015,7 +22015,7 @@ msgstr "Om konto är låst, tillåts poster för Behöriga Användare."
#: erpnext/stock/stock_ledger.py:1999
msgid "If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table."
msgstr "Om artikel handlas som Noll Värderingssats i denna post, aktivera 'Tillåt Noll Värderingssats' i {0} Artikel Tabell."
msgstr "Om artikel handlas som Noll Grund Pris i denna post, aktivera 'Tillåt Noll Grund Pris' i {0} Artikel Tabell."
#. Description of the 'Projected On Hand' (Float) field in DocType 'Material
#. Request Item'
@@ -22084,7 +22084,7 @@ msgstr "Om vald kan flera material användas för en Arbetsorder. Detta är anv
#: erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js:24
msgid "If ticked, the BOM cost will be automatically updated based on Valuation Rate / Price List Rate / last purchase rate of raw materials."
msgstr "Om vald uppdateras Stycklista Kostnad automatiskt baserat på Värderingssats / Prislista Pris / Senaste Inköp Pris för Råmaterial."
msgstr "Om vald uppdateras Stycklista Kostnad automatiskt baserat på Grund Pris / Prislista Pris / Senaste Inköp Pris för Råmaterial."
#: erpnext/accounts/doctype/pricing_rule/pricing_rule.js:46
msgid "If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions."
@@ -25828,7 +25828,7 @@ msgstr "Artikel kvantitet kan inte uppdateras eftersom råmaterial redan är bea
#: erpnext/stock/doctype/stock_entry/stock_entry.py:1149
msgid "Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {0}"
msgstr "Artikel pris har ändrats till noll eftersom Tillåt Noll Värderingssats är vald för artikel {0}"
msgstr "Artikel pris har ändrats till noll eftersom Tillåt Noll Grund Pris är vald för artikel {0}"
#. Label of the finished_good (Link) field in DocType 'Job Card'
#: erpnext/manufacturing/doctype/job_card/job_card.json
@@ -25842,7 +25842,7 @@ msgstr "Artikel som ska produceras eller packas om"
#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js:9
msgid "Item valuation rate is recalculated considering landed cost voucher amount"
msgstr "Värderingssats räknas om med hänsyn till landad kostnad verifikat belopp"
msgstr "Grund Pris räknas om med hänsyn till landad kostnad verifikat belopp"
#: erpnext/stock/utils.py:531
msgid "Item valuation reposting in progress. Report might show incorrect item valuation."
@@ -26073,7 +26073,7 @@ msgstr "Artiklar hittades inte."
#: erpnext/stock/doctype/stock_entry/stock_entry.py:1145
msgid "Items rate has been updated to zero as Allow Zero Valuation Rate is checked for the following items: {0}"
msgstr "Artikel Pris har ändrats till noll eftersom Tillåt Noll Värderingssats är vald för följande artiklar: {0}"
msgstr "Artikel Pris har ändrats till noll eftersom Tillåt Noll Grund Pris är vald för följande artiklar: {0}"
#. Label of the items_to_be_repost (Code) field in DocType 'Repost Item
#. Valuation'
@@ -29629,7 +29629,7 @@ msgstr "Negativt Lager Fel"
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:627
msgid "Negative Valuation Rate is not allowed"
msgstr "Negativ Värderingssats är inte tillåtet"
msgstr "Negativ Grund Pris är inte tillåtet"
#: erpnext/setup/setup_wizard/data/sales_stage.txt:8
#: erpnext/setup/setup_wizard/operations/install_fixtures.py:439
@@ -30285,11 +30285,11 @@ msgstr "Inga Villkor"
#: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js:236
msgid "No Unreconciled Invoices and Payments found for this party and account"
msgstr "Inga Oavstämda Fakturor och Betalningar hittades för denna parti och konto"
msgstr "Inga Ej Avstämda Fakturor och Betalningar hittades för denna parti och konto"
#: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js:241
msgid "No Unreconciled Payments found for this party"
msgstr "Inga Oavstämda Betalningar hittades för denna parti"
msgstr "Inga Ej Avstämda Betalningar hittades för denna parti"
#: erpnext/manufacturing/doctype/production_plan/production_plan.py:782
#: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py:250
@@ -30524,7 +30524,7 @@ msgstr "Inga rader med noll dokument antal hittades"
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:804
msgid "No stock ledger entries were created. Please set the quantity or valuation rate for the items properly and try again."
msgstr "Inga Lager Register Poster har skapats. Ange kvantitet eller grund pris för artiklar på rätt sätt och försök igen."
msgstr "Inga Lager Register Poster skapade. Ange kvantitet eller grund pris för artiklar på rätt sätt och försök igen."
#. Description of the 'Stock Frozen Up To' (Date) field in DocType 'Stock
#. Settings'
@@ -40662,7 +40662,7 @@ msgstr "Avstämning Rapport"
#. 'Company'
#: erpnext/setup/doctype/company/company.json
msgid "Reconciliation Takes Effect On"
msgstr "Avstämning Träder i Kraft"
msgstr "Avstämning tar effekt på"
#. Label of the recording_html (HTML) field in DocType 'Call Log'
#: erpnext/telephony/doctype/call_log/call_log.json
@@ -43701,7 +43701,7 @@ msgstr "Rad # {0}: Artikel Moms Mall uppdaterad enligt giltighet och tillämpad
#: erpnext/controllers/selling_controller.py:633
msgid "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
msgstr "Rad # {0}: Artikel Pris är uppdaterad enligt Värderingssats eftersom det är intern lager överföring"
msgstr "Rad # {0}: Artikel Pris är uppdaterad enligt Grund Pris eftersom det är intern lager överföring"
#: erpnext/controllers/subcontracting_controller.py:151
msgid "Row {0}: Item {1} must be a stock item."
@@ -51895,7 +51895,7 @@ msgstr "Aktier finns inte med {0}"
#: erpnext/stock/stock_ledger.py:801
msgid "The stock for the item {0} in the {1} warehouse was negative on the {2}. You should create a positive entry {3} before the date {4} and time {5} to post the correct valuation rate. For more details, please read the <a href='https://docs.erpnext.com/docs/user/manual/en/stock-adjustment-cogs-with-negative-stock'>documentation<a>."
msgstr "Lager för artikel {0} i {1} lager var negativt {2}. Skapa positiv post {3} före {4} och {5} för att bokföra rätt Värderingssats. För mer information, läs dokumentation <a href='https://docs.erpnext.com/docs/user/manual/en/stock-adjustment-cogs-with-negative-stock'><a>."
msgstr "Lager för artikel {0} i {1} lager var negativt {2}. Skapa positiv post {3} före {4} och {5} för att bokföra rätt grund pris. För mer information, läs dokumentation <a href='https://docs.erpnext.com/docs/user/manual/en/stock-adjustment-cogs-with-negative-stock'><a>."
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:731
msgid "The stock has been reserved for the following Items and Warehouses, un-reserve the same to {0} the Stock Reconciliation: <br /><br /> {1}"
@@ -55547,7 +55547,7 @@ msgstr "Validera Prissättning Regel"
#. Settings'
#: erpnext/selling/doctype/selling_settings/selling_settings.json
msgid "Validate Selling Price for Item Against Purchase Rate or Valuation Rate"
msgstr "Validera Försäljning Pris för Artikel mot Inköp Pris eller Värderingssats"
msgstr "Validera Försäljning Pris för Artikel mot Inköp Pris eller Grund Pris"
#. Label of the validate_stock_on_save (Check) field in DocType 'POS Profile'
#: erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -55666,7 +55666,7 @@ msgstr "Grund Pris erfordras för Artikel {0} på rad {1}"
#. 'Purchase Taxes and Charges'
#: erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
msgid "Valuation and Total"
msgstr "Värdering och Totalt"
msgstr "Grund Pris och Totalt"
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:1003
msgid "Valuation rate for customer provided items has been set to zero."
@@ -56833,7 +56833,7 @@ msgstr "När detta alternativ är aktiverad använder system dokument registreri
#: erpnext/stock/doctype/item/item.js:1079
msgid "When creating an Item, entering a value for this field will automatically create an Item Price at the backend."
msgstr "När du skapar en artikel, om du anger ett värde för detta fält, skapas automatiskt ett artikelpris i bakgrund."
msgstr "När artikel skapas, om värde är angiven för detta fält, skapas artikel pris automatiskt i bakgrunden."
#: erpnext/stock/doctype/stock_entry/stock_entry.py:294
msgid "When there are multiple finished goods ({0}) in a Repack stock entry, the basic rate for all finished goods must be set manually. To set rate manually, enable the checkbox 'Set Basic Rate Manually' in the respective finished good row."

View File

@@ -405,9 +405,11 @@ class MaintenanceSchedule(TransactionBase):
delete_events(self.doctype, self.name)
@frappe.whitelist()
def get_pending_data(self, data_type, s_date=None, item_name=None):
def get_pending_data(self, data_type: str, s_date: str | None = None, item_name: str | None = None):
if data_type == "date":
dates = ""
if not item_name:
frappe.throw(_("Item Name is required."))
for schedule in self.schedules:
if schedule.item_name == item_name and schedule.completion_status == "Pending":
dates = dates + "\n" + formatdate(schedule.scheduled_date, "dd-MM-yyyy")
@@ -421,6 +423,8 @@ class MaintenanceSchedule(TransactionBase):
break
return items
elif data_type == "id":
if not s_date:
frappe.throw(_("Scheduled Date is required."))
for schedule in self.schedules:
if schedule.item_name == item_name and s_date == formatdate(
schedule.scheduled_date, "dd-mm-yyyy"
@@ -429,12 +433,10 @@ class MaintenanceSchedule(TransactionBase):
@frappe.whitelist()
def get_serial_nos_from_schedule(item_code, schedule=None):
serial_nos = []
if schedule:
serial_nos = frappe.db.get_value(
"Maintenance Schedule Item", {"parent": schedule, "item_code": item_code}, "serial_no"
)
def get_serial_nos_from_schedule(item_code: str, schedule: str):
serial_nos = frappe.db.get_value(
"Maintenance Schedule Item", {"parent": schedule, "item_code": item_code}, "serial_no"
)
if serial_nos:
serial_nos = get_serial_nos(serial_nos)
@@ -443,7 +445,12 @@ def get_serial_nos_from_schedule(item_code, schedule=None):
@frappe.whitelist()
def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None):
def make_maintenance_visit(
source_name: str,
target_doc: str | dict | None = None,
item_name: str | None = None,
s_id: str | None = None,
):
from frappe.model.mapper import get_mapped_doc
def condition(doc):

View File

@@ -1322,9 +1322,9 @@ class JobCard(Document):
def is_work_order_closed(self):
if self.work_order:
status = frappe.get_value("Work Order", self.work_order)
status = frappe.get_value("Work Order", self.work_order, "status")
if status == "Closed":
if status in ["Closed", "Stopped"]:
return True
return False

View File

@@ -225,7 +225,12 @@ class WorkOrder(Document):
frappe.throw(_("Actual End Date cannot be before Actual Start Date"))
def validate_fg_warehouse_for_reservation(self):
if self.reserve_stock and self.sales_order and not self.subcontracting_inward_order:
if (
self.reserve_stock
and self.sales_order
and not self.subcontracting_inward_order
and not self.production_plan_sub_assembly_item
):
warehouses = frappe.get_all(
"Sales Order Item",
filters={"parent": self.sales_order, "item_code": self.production_item},
@@ -413,39 +418,52 @@ class WorkOrder(Document):
)
def validate_sales_order(self):
if self.production_plan_sub_assembly_item:
return
if self.sales_order:
self.check_sales_order_on_hold_or_close()
so = frappe.db.sql(
"""
select so.name, so_item.delivery_date, so.project
from `tabSales Order` so
inner join `tabSales Order Item` so_item on so_item.parent = so.name
left join `tabProduct Bundle Item` pk_item on so_item.item_code = pk_item.parent
where so.name=%s and so.docstatus = 1
and so.skip_delivery_note = 0 and (
so_item.item_code=%s or
pk_item.item_code=%s )
""",
(self.sales_order, self.production_item, self.production_item),
as_dict=1,
SalesOrder = frappe.qb.DocType("Sales Order")
SalesOrderItem = frappe.qb.DocType("Sales Order Item")
PackedItem = frappe.qb.DocType("Packed Item")
ProductBundleItem = frappe.qb.DocType("Product Bundle Item")
so = (
frappe.qb.from_(SalesOrder)
.inner_join(SalesOrderItem)
.on(SalesOrderItem.parent == SalesOrder.name)
.left_join(ProductBundleItem)
.on(ProductBundleItem.parent == SalesOrderItem.item_code)
.select(SalesOrder.name, SalesOrder.project, SalesOrderItem.delivery_date)
.where(
(SalesOrder.skip_delivery_note == 0)
& (SalesOrder.docstatus == 1)
& (SalesOrder.name == self.sales_order)
& (
(SalesOrderItem.item_code == self.production_item)
| (ProductBundleItem.item_code == self.production_item)
)
)
.run(as_dict=1)
)
if not so:
so = frappe.db.sql(
"""
select
so.name, so_item.delivery_date, so.project
from
`tabSales Order` so, `tabSales Order Item` so_item, `tabPacked Item` packed_item
where so.name=%s
and so.name=so_item.parent
and so.name=packed_item.parent
and so.skip_delivery_note = 0
and so_item.item_code = packed_item.parent_item
and so.docstatus = 1 and packed_item.item_code=%s
""",
(self.sales_order, self.production_item),
as_dict=1,
so = (
frappe.qb.from_(SalesOrder)
.inner_join(SalesOrderItem)
.on(SalesOrderItem.parent == SalesOrder.name)
.inner_join(PackedItem)
.on(PackedItem.parent == SalesOrder.name)
.select(SalesOrder.name, SalesOrder.project, SalesOrderItem.delivery_date)
.where(
(SalesOrder.name == self.sales_order)
& (SalesOrder.skip_delivery_note == 0)
& (SalesOrderItem.item_code == PackedItem.parent_item)
& (SalesOrder.docstatus == 1)
& (PackedItem.item_code == self.production_item)
)
.run(as_dict=1)
)
if len(so):
@@ -651,7 +669,7 @@ class WorkOrder(Document):
from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item
if self.sales_order and self.sales_order_item:
if self.sales_order and self.sales_order_item and not self.production_plan_sub_assembly_item:
update_produced_qty_in_so_item(self.sales_order, self.sales_order_item)
if self.production_plan:
@@ -695,19 +713,25 @@ class WorkOrder(Document):
self.db_set("disassembled_qty", self.disassembled_qty)
def get_transferred_or_manufactured_qty(self, purpose, fieldname):
table = frappe.qb.DocType("Stock Entry")
query = frappe.qb.from_(table).where(
(table.work_order == self.name) & (table.docstatus == 1) & (table.purpose == purpose)
parent = frappe.qb.DocType("Stock Entry")
query = frappe.qb.from_(parent).where(
(parent.work_order == self.name)
& (parent.docstatus == 1)
& (parent.purpose == purpose)
& (parent.is_additional_transfer_entry == cint(fieldname == "additional_transferred_qty"))
)
if purpose == "Manufacture":
query = query.select(Sum(table.fg_completed_qty) - Sum(table.process_loss_qty))
child = frappe.qb.DocType("Stock Entry Detail")
query = (
query.join(child)
.on(parent.name == child.parent)
.select(Sum(child.transfer_qty))
.where(child.is_finished_item == 1)
)
else:
query = query.select(Sum(table.fg_completed_qty))
query = query.where(
table.is_additional_transfer_entry == cint(fieldname == "additional_transferred_qty")
)
query = query.select(Sum(parent.fg_completed_qty))
return flt(query.run()[0][0])
@@ -1159,7 +1183,7 @@ class WorkOrder(Document):
doc.db_set("status", doc.status)
def update_work_order_qty_in_so(self):
if not self.sales_order and not self.sales_order_item:
if (not self.sales_order and not self.sales_order_item) or self.production_plan_sub_assembly_item:
return
total_bundle_qty = 1
@@ -2106,7 +2130,7 @@ def make_stock_reservation_entries(
@frappe.whitelist()
def cancel_stock_reservation_entries(doc, sre_list):
def cancel_stock_reservation_entries(doc: str | dict, sre_list: str | list):
if isinstance(doc, str):
doc = parse_json(doc)
doc = frappe.get_doc("Work Order", doc.get("name"))

View File

@@ -468,4 +468,6 @@ erpnext.patches.v15_0.replace_http_with_https_in_sales_partner
erpnext.patches.v16_0.migrate_asset_type_checkboxes_to_select
erpnext.patches.v15_0.delete_quotation_lost_record_detail
erpnext.patches.v16_0.add_portal_redirects
erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2
erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po
erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2
erpnext.patches.v16_0.enable_serial_batch_setting

View File

@@ -0,0 +1,9 @@
import frappe
def execute():
if not frappe.get_all("Serial No", limit=1) and not frappe.get_all("Batch", limit=1):
return
frappe.db.set_single_value("Stock Settings", "enable_serial_and_batch_no_for_item", 1)
frappe.db.set_default("enable_serial_and_batch_no_for_item", 1)

View File

@@ -0,0 +1,33 @@
import frappe
from frappe.query_builder import DocType
from frappe.query_builder.functions import Sum
def execute():
PurchaseOrderItem = DocType("Purchase Order Item")
MaterialRequestItem = DocType("Material Request Item")
poi_query = (
frappe.qb.from_(PurchaseOrderItem)
.select(PurchaseOrderItem.sales_order_item, Sum(PurchaseOrderItem.stock_qty))
.where(PurchaseOrderItem.sales_order_item.isnotnull() & PurchaseOrderItem.docstatus == 1)
.groupby(PurchaseOrderItem.sales_order_item)
)
mri_query = (
frappe.qb.from_(MaterialRequestItem)
.select(MaterialRequestItem.sales_order_item, Sum(MaterialRequestItem.stock_qty))
.where(MaterialRequestItem.sales_order_item.isnotnull() & MaterialRequestItem.docstatus == 1)
.groupby(MaterialRequestItem.sales_order_item)
)
poi_data = poi_query.run()
mri_data = mri_query.run()
updates_against_poi = {data[0]: {"ordered_qty": data[1]} for data in poi_data}
updates_against_mri = {data[0]: {"requested_qty": data[1], "ordered_qty": 0} for data in mri_data}
frappe.db.auto_commit_on_many_writes = 1
frappe.db.bulk_update("Sales Order Item", updates_against_mri)
frappe.db.bulk_update("Sales Order Item", updates_against_poi)
frappe.db.auto_commit_on_many_writes = 0

View File

@@ -205,7 +205,7 @@ frappe.ui.form.on("Project", {
collect_progress: function (frm) {
if (frm.doc.collect_progress && !frm.doc.subject) {
frm.set_value("subject", __("For project {0}, update your status", [frm.doc.name]));
frm.set_value("subject", __("For project - {0}, update your status", [frm.doc.project_name]));
}
},
});

View File

@@ -12,29 +12,21 @@
"project_name",
"status",
"project_type",
"is_active",
"percent_complete_method",
"percent_complete",
"column_break_5",
"project_template",
"expected_start_date",
"expected_end_date",
"priority",
"department",
"customer_details",
"customer",
"column_break_14",
"sales_order",
"users_section",
"users",
"copied_from",
"section_break0",
"notes",
"is_active",
"percent_complete",
"section_break_18",
"expected_start_date",
"actual_start_date",
"actual_time",
"column_break_20",
"expected_end_date",
"actual_end_date",
"costing_tab",
"project_details",
"estimated_costing",
"total_costing_amount",
@@ -50,7 +42,7 @@
"gross_margin",
"column_break_37",
"per_gross_margin",
"monitor_progress",
"monitor_progress_tab",
"collect_progress",
"holiday_list",
"frequency",
@@ -63,7 +55,18 @@
"weekly_time_to_send",
"column_break_45",
"subject",
"message"
"message",
"more_info_tab",
"customer_details",
"customer",
"column_break_14",
"sales_order",
"users_section",
"users",
"copied_from",
"section_break0",
"notes",
"connections_tab"
],
"fields": [
{
@@ -231,7 +234,7 @@
"collapsible": 1,
"fieldname": "section_break_18",
"fieldtype": "Section Break",
"label": "Start and End Dates"
"label": "Timeline"
},
{
"fieldname": "actual_start_date",
@@ -258,7 +261,6 @@
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "project_details",
"fieldtype": "Section Break",
"label": "Costing and Billing",
@@ -329,7 +331,6 @@
"options": "Cost Center"
},
{
"collapsible": 1,
"fieldname": "margin",
"fieldtype": "Section Break",
"label": "Margin",
@@ -357,12 +358,6 @@
"oldfieldtype": "Currency",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "monitor_progress",
"fieldtype": "Section Break",
"label": "Monitor Progress"
},
{
"default": "0",
"fieldname": "collect_progress",
@@ -455,6 +450,27 @@
"fieldtype": "Data",
"label": "Subject",
"mandatory_depends_on": "collect_progress"
},
{
"fieldname": "costing_tab",
"fieldtype": "Tab Break",
"label": "Costing"
},
{
"fieldname": "monitor_progress_tab",
"fieldtype": "Tab Break",
"label": "Progress"
},
{
"fieldname": "more_info_tab",
"fieldtype": "Tab Break",
"label": "More Info"
},
{
"fieldname": "connections_tab",
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
}
],
"icon": "fa fa-puzzle-piece",
@@ -462,7 +478,7 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 4,
"modified": "2025-08-21 17:57:58.314809",
"modified": "2026-03-04 11:09:55.253367",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project",

View File

@@ -19,6 +19,13 @@ frappe.ui.form.on("Project Template", {
frappe.ui.form.on("Project Template Task", {
task: function (frm, cdt, cdn) {
var row = locals[cdt][cdn];
if (!row.task) {
row.subject = null;
refresh_field("tasks");
return;
}
frappe.db.get_value("Task", row.task, "subject", (value) => {
row.subject = value.subject;
refresh_field("tasks");

View File

@@ -13,7 +13,6 @@
"type",
"color",
"is_group",
"is_template",
"column_break0",
"status",
"priority",
@@ -21,17 +20,21 @@
"parent_task",
"completed_by",
"completed_on",
"section_break_dafi",
"is_template",
"column_break_vvfp",
"start",
"duration",
"sb_timeline",
"exp_start_date",
"expected_time",
"start",
"column_break_11",
"exp_end_date",
"progress",
"duration",
"is_milestone",
"sb_details",
"description",
"dependencies_tab",
"sb_depends_on",
"depends_on",
"depends_on_tasks",
@@ -44,12 +47,13 @@
"total_costing_amount",
"column_break_20",
"total_billing_amount",
"more_info_tab",
"sb_more_info",
"company",
"review_date",
"closing_date",
"column_break_22",
"department",
"company",
"lft",
"rgt",
"old_parent",
@@ -78,7 +82,6 @@
"oldfieldname": "project",
"oldfieldtype": "Link",
"options": "Project",
"remember_last_selected_value": 1,
"search_index": 1
},
{
@@ -218,7 +221,6 @@
{
"fieldname": "sb_depends_on",
"fieldtype": "Section Break",
"label": "Dependencies",
"oldfieldtype": "Section Break"
},
{
@@ -298,10 +300,9 @@
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "sb_more_info",
"fieldtype": "Section Break",
"label": "More Info"
"label": "Additional Info"
},
{
"depends_on": "eval:doc.status == \"Closed\" || doc.status == \"Pending Review\"",
@@ -334,8 +335,7 @@
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"remember_last_selected_value": 1
"options": "Company"
},
{
"fieldname": "lft",
@@ -368,6 +368,7 @@
"options": "User"
},
{
"allow_in_quick_entry": 1,
"default": "0",
"fieldname": "is_template",
"fieldtype": "Check",
@@ -397,6 +398,24 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Template Task"
},
{
"fieldname": "dependencies_tab",
"fieldtype": "Tab Break",
"label": "Dependencies"
},
{
"fieldname": "more_info_tab",
"fieldtype": "Tab Break",
"label": "More Info"
},
{
"fieldname": "section_break_dafi",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_vvfp",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-check",
@@ -404,11 +423,11 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
"modified": "2025-10-16 08:39:12.214577",
"modified": "2026-03-04 11:47:10.454548",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
"naming_rule": "Expression (old style)",
"naming_rule": "Expression",
"nsm_parent_field": "parent_task",
"owner": "Administrator",
"permissions": [
@@ -425,6 +444,7 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "subject",
"show_name_in_global_search": 1,
"show_preview_popup": 1,
@@ -434,4 +454,4 @@
"timeline_field": "project",
"title_field": "subject",
"track_seen": 1
}
}

View File

@@ -138,6 +138,8 @@ class Task(NestedSet):
def validate_status(self):
if self.is_template and self.status != "Template":
self.status = "Template"
if self.status == "Template" and not self.is_template:
self.status = "Open"
if self.status != self.get_db_value("status") and self.status == "Completed":
for d in self.depends_on:
if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):

View File

@@ -260,6 +260,33 @@ frappe.ui.form.on("Timesheet", {
parent_project: function (frm) {
set_project_in_timelog(frm);
},
employee: function (frm) {
if (frm.doc.employee && frm.doc.time_logs) {
const selected_employee = frm.doc.employee;
frm.doc.time_logs.forEach((row) => {
if (row.activity_type) {
frappe.call({
method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost",
args: {
employee: frm.doc.employee,
activity_type: row.activity_type,
currency: frm.doc.currency,
},
callback: function (r) {
if (r.message) {
if (selected_employee !== frm.doc.employee) return;
row.billing_rate = r.message["billing_rate"];
row.costing_rate = r.message["costing_rate"];
frm.refresh_fields("time_logs");
calculate_billing_costing_amount(frm, row.doctype, row.name);
}
},
});
}
});
}
},
});
frappe.ui.form.on("Timesheet Detail", {

View File

@@ -18,28 +18,29 @@
"column_break_3",
"status",
"parent_project",
"employee_detail",
"employee",
"employee_name",
"department",
"column_break_9",
"user",
"start_date",
"end_date",
"employee_detail",
"employee",
"department",
"column_break_9",
"employee_name",
"section_break_5",
"time_logs",
"working_hours",
"total_hours",
"billing_tab",
"billing_details",
"total_billable_hours",
"total_billable_amount",
"total_costing_amount",
"base_total_billable_amount",
"base_total_billed_amount",
"base_total_costing_amount",
"column_break_10",
"total_billed_hours",
"total_billable_amount",
"total_billed_amount",
"total_costing_amount",
"base_total_billed_amount",
"per_billed",
"section_break_18",
"note",
@@ -176,7 +177,6 @@
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "billing_details",
"fieldtype": "Section Break",
"label": "Billing Details",
@@ -304,13 +304,18 @@
"fieldname": "exchange_rate",
"fieldtype": "Float",
"label": "Exchange Rate"
},
{
"fieldname": "billing_tab",
"fieldtype": "Tab Break",
"label": "Billing"
}
],
"icon": "fa fa-clock-o",
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-12-19 13:48:23.453636",
"modified": "2026-03-04 11:56:51.438298",
"modified_by": "Administrator",
"module": "Projects",
"name": "Timesheet",

View File

@@ -9,7 +9,7 @@ import frappe
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def query_task(doctype, txt, searchfield, start, page_len, filters):
def query_task(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
from frappe.desk.reportview import build_match_conditions
search_string = "%%%s%%" % txt

View File

@@ -0,0 +1,4 @@
<svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M38.5714 0H15.4286C6.90761 0 0 6.90761 0 15.4286V38.5714C0 47.0924 6.90761 54 15.4286 54H38.5714C47.0924 54 54 47.0924 54 38.5714V15.4286C54 6.90761 47.0924 0 38.5714 0Z" fill="#0289F7"/>
<path d="M19.2857 15.4286H22.1786C23.7763 15.4286 25.0714 16.7237 25.0714 18.3214V24.1071C25.0714 25.7048 23.7763 27 22.1786 27H19.2857V38.5714H15.4286V27H11.5714V23.1428H21.2143V19.2857H11.5714V15.4286H15.4286V11.5714H19.2857V15.4286ZM38.5714 38.5714H34.7143V34.7143H31.8214C30.2238 34.7143 28.9286 33.4191 28.9286 31.8214V26.0357C28.9286 24.438 30.2238 23.1428 31.8214 23.1428H34.7143V11.5714H38.5714V23.1428H42.4286V27H32.7857V30.8571H42.4286V34.7143H38.5714V38.5714Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 787 B

View File

@@ -0,0 +1,5 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 0H8C3.58172 0 0 3.58172 0 8V20C0 24.4183 3.58172 28 8 28H20C24.4183 28 28 24.4183 28 20V8C28 3.58172 24.4183 0 20 0Z" fill="#0289F7"/>
<path d="M20.5 13.25C20.5 13.0926 20.5 13 20.5 13L18.5 11.499V19.5H20C20.2761 19.5 20 19.5 20.5 19.5V13.25ZM14.5 14V16H10.5V14H14.5ZM22.5 19C22.5 20.3807 21.3807 21.5 20 21.5H16.5V19.5V7.5C16.5 7.5 16.2761 7.5 16 7.5H9C8.72386 7.5 9 7.5 8.5 7.5V19.5C9 19.5 8.72386 19.5 9 19.5H12.5V21.5H9C7.61929 21.5 6.5 20.3807 6.5 19V8C6.5 6.61929 7.61929 5.5 9 5.5H16C17.3807 5.5 18.5 6.61929 18.5 8V9L21.5 11.25C22.1295 11.7221 22.5 12.4631 22.5 13.25V19Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 713 B

View File

@@ -0,0 +1,4 @@
<svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M38.5714 0H15.4286C6.90761 0 0 6.90761 0 15.4286V38.5714C0 47.0924 6.90761 54 15.4286 54H38.5714C47.0924 54 54 47.0924 54 38.5714V15.4286C54 6.90761 47.0924 0 38.5714 0Z" fill="#0289F7" fill-opacity="0.1"/>
<path d="M19.2857 15.4286H22.1786C23.7762 15.4286 25.0714 16.7238 25.0714 18.3215V24.1072C25.0714 25.7049 23.7762 27 22.1786 27H19.2857V38.5715H15.4286V27H11.5714V23.1429H21.2143V19.2858H11.5714V15.4286H15.4286V11.5715H19.2857V15.4286ZM38.5714 38.5715H34.7143V34.7143H31.8214C30.2237 34.7143 28.9286 33.4192 28.9286 31.8215V26.0358C28.9286 24.4381 30.2237 23.1429 31.8214 23.1429H34.7143V11.5715H38.5714V23.1429H42.4286V27H32.7857V30.8572H42.4286V34.7143H38.5714V38.5715Z" fill="#0981E3"/>
</svg>

After

Width:  |  Height:  |  Size: 809 B

View File

@@ -0,0 +1,4 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 0H8C3.58172 0 0 3.58172 0 8V20C0 24.4183 3.58172 28 8 28H20C24.4183 28 28 24.4183 28 20V8C28 3.58172 24.4183 0 20 0Z" fill="#0289F7" fill-opacity="0.1"/>
<path d="M20.5 13.25C20.5 13.0926 20.5 13 20.5 13L18.5 11.499V19.5H20C20.2761 19.5 20 19.5 20.5 19.5V13.25ZM14.5 14V16H10.5V14H14.5ZM22.5 19C22.5 20.3807 21.3807 21.5 20 21.5H16.5V19.5V7.5C16.5 7.5 16.2761 7.5 16 7.5H9C8.72386 7.5 9 7.5 8.5 7.5V19.5C9 19.5 8.72386 19.5 9 19.5H12.5V21.5H9C7.61929 21.5 6.5 20.3807 6.5 19V8C6.5 6.61929 7.61929 5.5 9 5.5H16C17.3807 5.5 18.5 6.61929 18.5 8V9L21.5 11.25C22.1295 11.7221 22.5 12.4631 22.5 13.25V19Z" fill="#0981E3"/>
</svg>

After

Width:  |  Height:  |  Size: 733 B

View File

@@ -580,6 +580,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
this.validate_has_items();
erpnext.utils.view_serial_batch_nos(this.frm);
this.set_route_options_for_new_doc();
erpnext.toggle_serial_batch_fields(this.frm);
}
set_route_options_for_new_doc() {
@@ -1307,6 +1308,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (this.frm.doc.transaction_date) {
this.frm.transaction_date = this.frm.doc.transaction_date;
frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
this.recalculate_terms();
}
}
@@ -2961,6 +2963,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
frappe.call({
method: "erpnext.controllers.stock_controller.make_quality_inspections",
args: {
company: me.frm.doc.company,
doctype: me.frm.doc.doctype,
docname: me.frm.doc.name,
items: selected_data,

View File

@@ -19,6 +19,71 @@ $.extend(erpnext, {
return currency_list;
},
toggle_serial_batch_fields(frm) {
let hide_fields = cint(frappe.user_defaults?.enable_serial_and_batch_no_for_item) === 0 ? 1 : 0;
let fields = ["serial_and_batch_bundle", "use_serial_batch_fields", "serial_no", "batch_no"];
if (
[
"Stock Entry",
"Purchase Receipt",
"Purchase Invoice",
"Stock Reconciliation",
"Subcontracting Receipt",
].includes(frm.doc.doctype)
) {
fields.push("add_serial_batch_bundle");
}
if (["Stock Reconciliation"].includes(frm.doc.doctype)) {
fields.push("reconcile_all_serial_batch");
}
if (["Sales Invoice", "Delivery Note", "Pick List"].includes(frm.doc.doctype)) {
fields.push("pick_serial_and_batch");
}
if (["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(frm.doc.doctype)) {
fields.push("add_serial_batch_for_rejected_qty", "rejected_serial_and_batch_bundle");
}
let child_name = "items";
if (frm.doc.doctype === "Pick List") {
child_name = "locations";
}
if (frm.doc.doctype === "Asset Capitalization") {
child_name = "stock_items";
}
fields.forEach((field) => {
frm.fields_dict[child_name].grid.update_docfield_property(field, "hidden", hide_fields);
frm.fields_dict[child_name].grid.update_docfield_property(
field,
"in_list_view",
hide_fields ? 0 : 1
);
if (
frm.doc.doctype === "Subcontracting Receipt" &&
!["add_serial_batch_for_rejected_qty", "rejected_serial_and_batch_bundle"].includes(field)
) {
frm.fields_dict["supplied_items"].grid.update_docfield_property(field, "hidden", hide_fields);
frm.fields_dict["supplied_items"].grid.update_docfield_property(
field,
"in_list_view",
hide_fields ? 0 : 1
);
frm.fields_dict["supplied_items"].grid.reset_grid();
}
});
frm.fields_dict[child_name].grid.reset_grid();
},
toggle_naming_series: function () {
if (
cur_frm &&

View File

@@ -122,7 +122,12 @@ class QualityProcedure(NestedSet):
@frappe.whitelist()
def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False):
def get_children(
doctype: str,
parent: str | None = None,
parent_quality_procedure: str | None = None,
is_root: bool = False,
):
if parent is None or parent == "All Quality Procedures":
parent = ""

View File

@@ -31,7 +31,7 @@ def update_itemised_tax_data(doc):
@frappe.whitelist()
def export_invoices(filters=None):
def export_invoices(filters: str | None = None):
frappe.has_permission("Sales Invoice", throw=True)
invoices = frappe.get_all(
@@ -359,7 +359,7 @@ def prepare_and_attach_invoice(doc, replace=False):
@frappe.whitelist()
def generate_single_invoice(docname):
def generate_single_invoice(docname: str):
doc = frappe.get_doc("Sales Invoice", docname)
frappe.has_permission("Sales Invoice", doc=doc, throw=True)

View File

@@ -85,7 +85,7 @@ def get_columns():
@frappe.whitelist()
def irs_1099_print(filters):
def irs_1099_print(filters: str):
if not filters:
frappe._dict(
{

View File

@@ -101,7 +101,7 @@ class ProductBundle(Document):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
def get_new_item_code(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
product_bundles = frappe.db.get_list("Product Bundle", {"disabled": 0}, pluck="name")
if not searchfield or searchfield == "name":

View File

@@ -8,7 +8,7 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.utils import flt, getdate, nowdate
from frappe.utils import cint, flt, getdate, nowdate
from erpnext.controllers.selling_controller import SellingController
@@ -451,6 +451,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
automatically_fetch_payment_terms = cint(
frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
)
doclist = get_mapped_doc(
"Quotation",
source_name,
@@ -458,6 +462,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
"Quotation": {
"doctype": "Sales Order",
"validation": {"docstatus": ["=", 1]},
"field_no_map": ["payment_terms_template"],
},
"Quotation Item": {
"doctype": "Sales Order Item",
@@ -467,13 +472,15 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
},
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
"Payment Schedule": {"doctype": "Payment Schedule", "add_if_empty": True},
},
target_doc,
set_missing_values,
ignore_permissions=ignore_permissions,
)
if automatically_fetch_payment_terms:
doclist.set_payment_schedule()
return doclist

View File

@@ -58,8 +58,22 @@ class TestQuotation(IntegrationTestCase):
qo.payment_schedule[0].due_date = add_days(qo.transaction_date, -2)
self.assertRaises(frappe.ValidationError, qo.save)
def test_update_child_disallow_rate_change(self):
qo = make_quotation(qty=4)
def test_update_child_rate_change(self):
from erpnext.stock.doctype.item.test_item import make_item
item_1 = make_item("_Test Item")
item_2 = make_item("_Test Item 1")
item_list = [
{"item_code": item_1.item_code, "warehouse": "_Test Warehouse - _TC", "qty": 10, "rate": 300},
{"item_code": item_2.item_code, "warehouse": "_Test Warehouse - _TC", "qty": 5, "rate": 400},
]
qo = make_quotation(item_list=item_list)
so = make_sales_order(qo.name, args={"filtered_children": [qo.items[0].name]})
so.delivery_date = nowdate()
so.submit()
qo.reload()
trans_item = json.dumps(
[
{
@@ -67,10 +81,35 @@ class TestQuotation(IntegrationTestCase):
"rate": 5000,
"qty": qo.items[0].qty,
"docname": qo.items[0].name,
}
},
{
"item_code": qo.items[1].item_code,
"rate": qo.items[1].rate,
"qty": qo.items[1].qty,
"docname": qo.items[1].name,
},
]
)
self.assertRaises(frappe.ValidationError, update_child_qty_rate, "Quotation", trans_item, qo.name)
trans_item = json.dumps(
[
{
"item_code": qo.items[0].item_code,
"rate": qo.items[0].rate,
"qty": qo.items[0].qty,
"docname": qo.items[0].name,
},
{
"item_code": qo.items[1].item_code,
"rate": 50,
"qty": qo.items[1].qty,
"docname": qo.items[1].name,
},
]
)
update_child_qty_rate("Quotation", trans_item, qo.name)
qo.reload()
self.assertEqual(qo.items[1].rate, 50)
def test_update_child_removing_item(self):
qo = make_quotation(qty=10)
@@ -142,6 +181,10 @@ class TestQuotation(IntegrationTestCase):
self.assertTrue(quotation.payment_schedule)
@IntegrationTestCase.change_settings(
"Accounts Settings",
{"automatically_fetch_payment_terms": 1},
)
def test_make_sales_order_terms_copied(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
@@ -284,7 +327,11 @@ class TestQuotation(IntegrationTestCase):
@IntegrationTestCase.change_settings(
"Accounts Settings",
{"add_taxes_from_item_tax_template": 0, "add_taxes_from_taxes_and_charges_template": 0},
{
"add_taxes_from_item_tax_template": 0,
"add_taxes_from_taxes_and_charges_template": 0,
"automatically_fetch_payment_terms": 1,
},
)
def test_make_sales_order_with_terms(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
@@ -322,10 +369,13 @@ class TestQuotation(IntegrationTestCase):
sales_order.save()
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 8906.00)
self.assertEqual(sales_order.payment_schedule[0].due_date, getdate(quotation.transaction_date))
self.assertEqual(
getdate(sales_order.payment_schedule[0].due_date), getdate(quotation.transaction_date)
)
self.assertEqual(sales_order.payment_schedule[1].payment_amount, 8906.00)
self.assertEqual(
sales_order.payment_schedule[1].due_date, getdate(add_days(quotation.transaction_date, 30))
getdate(sales_order.payment_schedule[1].due_date),
getdate(add_days(quotation.transaction_date, 30)),
)
def test_valid_till_before_transaction_date(self):
@@ -1025,6 +1075,56 @@ class TestQuotation(IntegrationTestCase):
quotation.reload()
self.assertEqual(quotation.status, "Open")
@IntegrationTestCase.change_settings(
"Accounts Settings",
{"automatically_fetch_payment_terms": 1},
)
def test_make_sales_order_with_payment_terms(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
template = frappe.get_doc(
{
"doctype": "Payment Terms Template",
"template_name": "_Test Payment Terms Template for Quotation",
"terms": [
{
"doctype": "Payment Terms Template Detail",
"invoice_portion": 50.00,
"credit_days_based_on": "Day(s) after invoice date",
"credit_days": 0,
},
{
"doctype": "Payment Terms Template Detail",
"invoice_portion": 50.00,
"credit_days_based_on": "Day(s) after invoice date",
"credit_days": 10,
},
],
}
).save()
quotation = make_quotation(qty=10, rate=1000, do_not_submit=1)
quotation.transaction_date = add_days(nowdate(), -2)
quotation.valid_till = add_days(nowdate(), 10)
quotation.update({"payment_terms_template": template.name, "payment_schedule": []})
quotation.save()
quotation.submit()
self.assertEqual(quotation.payment_schedule[0].payment_amount, 5000)
self.assertEqual(quotation.payment_schedule[1].payment_amount, 5000)
self.assertEqual(quotation.payment_schedule[0].due_date, quotation.transaction_date)
self.assertEqual(quotation.payment_schedule[1].due_date, add_days(quotation.transaction_date, 10))
sales_order = make_sales_order(quotation.name)
sales_order.transaction_date = nowdate()
sales_order.delivery_date = nowdate()
sales_order.save()
self.assertEqual(sales_order.payment_schedule[0].due_date, sales_order.transaction_date)
self.assertEqual(sales_order.payment_schedule[1].due_date, add_days(sales_order.transaction_date, 10))
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 5000)
self.assertEqual(sales_order.payment_schedule[1].payment_amount, 5000)
def enable_calculate_bundle_price(enable=1):
selling_settings = frappe.get_doc("Selling Settings")

View File

@@ -18,6 +18,7 @@
"column_break_7",
"order_type",
"transaction_date",
"transaction_time",
"delivery_date",
"column_break1",
"tax_id",
@@ -122,6 +123,7 @@
"company_contact_person",
"payment_schedule_section",
"payment_terms_section",
"ignore_default_payment_terms_template",
"payment_terms_template",
"payment_schedule",
"terms_section_break",
@@ -1724,6 +1726,22 @@
"fieldname": "utm_analytics_section",
"fieldtype": "Section Break",
"label": "UTM Analytics"
},
{
"default": "Now",
"depends_on": "is_internal_customer",
"fieldname": "transaction_time",
"fieldtype": "Time",
"label": "Time",
"mandatory_depends_on": "is_internal_customer"
},
{
"default": "0",
"fieldname": "ignore_default_payment_terms_template",
"fieldtype": "Check",
"hidden": 1,
"label": "Ignore Default Payment Terms Template",
"read_only": 1
}
],
"grid_page_length": 50,
@@ -1731,7 +1749,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2026-02-23 13:25:56.665392",
"modified": "2026-03-04 18:04:05.873483",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",

View File

@@ -117,6 +117,7 @@ class SalesOrder(SellingController):
grand_total: DF.Currency
group_same_items: DF.Check
has_unit_price_items: DF.Check
ignore_default_payment_terms_template: DF.Check
ignore_pricing_rule: DF.Check
in_words: DF.Data | None
incoterm: DF.Link | None
@@ -187,6 +188,7 @@ class SalesOrder(SellingController):
total_qty: DF.Float
total_taxes_and_charges: DF.Currency
transaction_date: DF.Date
transaction_time: DF.Time | None
utm_campaign: DF.Link | None
utm_content: DF.Data | None
utm_medium: DF.Link | None
@@ -1610,12 +1612,14 @@ def make_purchase_order(
def set_missing_values(source, target):
target.supplier = supplier
target.currency = frappe.db.get_value(
"Supplier", filters={"name": supplier}, fieldname=["default_currency"]
)
company_currency = frappe.db.get_value(
"Company", filters={"name": target.company}, fieldname=["default_currency"]
)
supplier_currency = frappe.db.get_value(
"Supplier", filters={"name": supplier}, fieldname=["default_currency"]
)
target.currency = supplier_currency if supplier_currency else company_currency
target.conversion_rate = get_exchange_rate(target.currency, company_currency, args="for_buying")

View File

@@ -2647,6 +2647,49 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
si2 = make_sales_invoice(so.name)
self.assertEqual(si2.items[0].qty, 20)
@change_settings("Selling Settings", {"validate_selling_price": 1})
def test_selling_price_validation_for_manufactured_item(self):
"""
Unit test to check the selling price validation for manufactured item, without last purchae rate in Item master.
"""
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
# create a FG Item and RM Item
rm_item = make_item(
"_Test RM Item for SO selling validation",
{"is_stock_item": 1, "valuation_rate": 100, "stock_uom": "Nos"},
).name
rm_warehouse = create_warehouse("_Test RM SPV Warehouse")
fg_item = make_item("_Test FG Item for SO selling validation", {"is_stock_item": 1}).name
fg_warehouse = create_warehouse("_Test FG SPV Warehouse")
# create BOM and inward entry for RM Item
bom_no = make_bom(item=fg_item, raw_materials=[rm_item]).name
make_stock_entry(item_code=rm_item, target=rm_warehouse, qty=10, rate=100)
# create a manufacture entry, so system won't update the last purchase rate in Item master.
se = make_stock_entry(item_code=fg_item, qty=10, purpose="Manufacture", do_not_save=True)
se.from_bom = 1
se.use_multi_level_bom = 1
se.bom_no = bom_no
se.fg_completed_qty = 1
se.from_warehouse = rm_warehouse
se.to_warehouse = fg_warehouse
se.get_items()
se.save()
se.submit()
# check valuation of FG Item
self.assertEqual(se.items[1].valuation_rate, 100)
# create a SO for FG Item with selling rate than valuation rate.
so = make_sales_order(item_code=fg_item, qty=10, rate=50, warehouse=fg_warehouse, do_not_save=1)
self.assertRaises(frappe.ValidationError, so.save)
def compare_payment_schedules(doc, doc1, doc2):
for index, schedule in enumerate(doc1.get("payment_schedule")):

View File

@@ -95,6 +95,7 @@
"ordered_qty",
"planned_qty",
"production_plan_qty",
"requested_qty",
"column_break_69",
"work_order_qty",
"delivered_qty",
@@ -1010,6 +1011,14 @@
"fieldtype": "Float",
"label": "Finished Good Qty",
"mandatory_depends_on": "eval:parent.is_subcontracted"
},
{
"fieldname": "requested_qty",
"fieldtype": "Float",
"label": "Requested Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"grid_page_length": 50,

View File

@@ -80,6 +80,7 @@ class SalesOrderItem(Document):
quotation_item: DF.Data | None
rate: DF.Currency
rate_with_margin: DF.Currency
requested_qty: DF.Float
reserve_stock: DF.Check
returned_qty: DF.Float
stock_qty: DF.Float

View File

@@ -329,7 +329,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-02-12 10:38:34.605126",
"modified": "2026-02-27 00:47:46.003305",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",

View File

@@ -3,6 +3,7 @@
import json
from typing import Literal
import frappe
import frappe.defaults
@@ -938,7 +939,7 @@ def cache_companies_monthly_sales_history():
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False):
def get_children(doctype: str, parent: str | None = None, company: str | None = None, is_root: bool = False):
if parent is None or parent == "All Companies":
parent = ""
@@ -1045,10 +1046,11 @@ def get_timeline_data(doctype, name):
@frappe.whitelist()
def get_default_company_address(name, sort_key="is_primary_address", existing_address=None):
if sort_key not in ["is_shipping_address", "is_primary_address"]:
return None
def get_default_company_address(
name: str,
sort_key: Literal["is_shipping_address", "is_primary_address"] = "is_primary_address",
existing_address: str | None = None,
):
out = frappe.db.sql(
""" SELECT
addr.name, addr.{}
@@ -1072,7 +1074,9 @@ def get_default_company_address(name, sort_key="is_primary_address", existing_ad
@frappe.whitelist()
def get_billing_shipping_address(name, billing_address=None, shipping_address=None):
def get_billing_shipping_address(
name: str, billing_address: str | None = None, shipping_address: str | None = None
):
primary_address = get_default_company_address(name, "is_primary_address", billing_address)
shipping_address = get_default_company_address(name, "is_shipping_address", shipping_address)
@@ -1080,7 +1084,7 @@ def get_billing_shipping_address(name, billing_address=None, shipping_address=No
@frappe.whitelist()
def create_transaction_deletion_request(company):
def create_transaction_deletion_request(company: str):
frappe.only_for("System Manager")
from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import (

View File

@@ -70,7 +70,13 @@ def get_abbreviated_name(name, company):
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False, include_disabled=False):
def get_children(
doctype: str,
parent: str | None = None,
company: str | None = None,
is_root: bool = False,
include_disabled: str | dict | None = None,
):
if isinstance(include_disabled, str):
include_disabled = json.loads(include_disabled)
fields = ["name as value", "is_group as expandable"]

View File

@@ -900,7 +900,7 @@ def send():
@frappe.whitelist()
def get_digest_msg(name):
def get_digest_msg(name: str):
return frappe.get_doc("Email Digest", name).get_msg_html()

View File

@@ -410,7 +410,7 @@ def is_holiday(employee, date=None, raise_exception=True, only_non_weekly=False,
@frappe.whitelist()
def deactivate_sales_person(status=None, employee=None):
def deactivate_sales_person(status: str | None = None, employee: str | None = None):
if status == "Left":
sales_person = frappe.db.get_value("Sales Person", {"Employee": employee})
if sales_person:
@@ -519,7 +519,13 @@ def get_employee_emails(employee_list):
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
def get_children(
doctype: str,
parent: str | None = None,
company: str | None = None,
is_root: bool = False,
is_tree: bool = False,
):
filters = [["status", "=", "Active"]]
if company and company != "All Companies":
filters.append(["company", "=", company])

View File

@@ -8,7 +8,7 @@ from datetime import date
import frappe
from frappe import _, throw
from frappe.model.document import Document
from frappe.utils import formatdate, getdate, today
from frappe.utils import DateTimeLikeObject, formatdate, getdate, today
class OverlapError(frappe.ValidationError):
@@ -168,7 +168,7 @@ class HolidayList(Document):
@frappe.whitelist()
def get_events(start, end, filters=None):
def get_events(start: DateTimeLikeObject, end: DateTimeLikeObject, filters: str | dict | None = None):
"""Returns events for Gantt / Calendar view rendering.
:param start: Start date-time.

View File

@@ -24,7 +24,7 @@ class PartyType(Document):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_party_type(doctype, txt, searchfield, start, page_len, filters):
def get_party_type(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
cond = ""
account_type = None

View File

@@ -35,7 +35,7 @@ class TermsandConditions(Document):
@frappe.whitelist()
def get_terms_and_conditions(template_name, doc):
def get_terms_and_conditions(template_name: str, doc: str | dict):
if isinstance(doc, str):
doc = json.loads(doc)

View File

@@ -75,7 +75,7 @@ def get_protected_doctypes():
@frappe.whitelist()
def get_company_link_fields(doctype_name):
def get_company_link_fields(doctype_name: str):
"""Get all Company Link field names for a DocType (whitelisted for frontend autocomplete)
Args:
@@ -428,7 +428,9 @@ class TransactionDeletionRecord(Document):
return {"count": len(self.doctypes_to_delete)}
@frappe.whitelist()
def populate_doctype_details(self, doctype_name, company=None, company_field=None):
def populate_doctype_details(
self, doctype_name: str, company: str | None = None, company_field: str | None = None
):
"""Get child DocTypes and document count for specified DocType
Args:
@@ -1035,7 +1037,7 @@ def get_doctypes_to_be_ignored():
@frappe.whitelist()
def export_to_delete_template(name):
def export_to_delete_template(name: str):
"""Export To Delete list as CSV via URL access"""
frappe.only_for("System Manager")
doc = frappe.get_doc("Transaction Deletion Record", name)
@@ -1044,7 +1046,7 @@ def export_to_delete_template(name):
@frappe.whitelist()
def process_import_template(transaction_deletion_record_name, file_url):
def process_import_template(transaction_deletion_record_name: str, file_url: str):
"""Import CSV template and populate To Delete list"""
import os

View File

@@ -0,0 +1,47 @@
{
"allow_roles": [
{
"role": "System Manager"
},
{
"role": "Sales Manager"
},
{
"role": "Accounts Manager"
},
{
"role": "Manufacturing Manager"
},
{
"role": "Stock Manager"
}
],
"creation": "2026-02-24 18:03:53.158438",
"docstatus": 0,
"doctype": "Module Onboarding",
"idx": 0,
"is_complete": 0,
"modified": "2026-02-24 18:07:36.808560",
"modified_by": "Administrator",
"module": "Setup",
"name": "Organization Onboarding",
"owner": "Administrator",
"steps": [
{
"step": "Setup Company"
},
{
"step": "Invite Users"
},
{
"step": "Setup Email Account"
},
{
"step": "Setup Role Permissions"
},
{
"step": "Review System Settings"
}
],
"title": "Setup Organization"
}

View File

@@ -0,0 +1,20 @@
{
"action": "Create Entry",
"action_label": "Invite Users",
"creation": "2026-02-24 18:04:21.585575",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2026-02-24 18:04:21.585575",
"modified_by": "Administrator",
"name": "Invite Users",
"owner": "Administrator",
"reference_document": "User",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Invite Users",
"validate_action": 1
}

View File

@@ -0,0 +1,21 @@
{
"action": "Update Settings",
"action_label": "Review System Settings",
"creation": "2026-02-24 18:06:56.781335",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 1,
"is_skipped": 0,
"modified": "2026-02-24 18:06:56.781335",
"modified_by": "Administrator",
"name": "Review System Settings",
"owner": "Administrator",
"reference_document": "System Settings",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Review System Settings",
"validate_action": 0,
"value_to_validate": ""
}

View File

@@ -0,0 +1,21 @@
{
"action": "Go to Page",
"action_label": "Setup Company",
"creation": "2026-02-20 11:12:50.373049",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 1,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2026-02-23 21:10:17.680053",
"modified_by": "Administrator",
"name": "Setup Company",
"owner": "Administrator",
"path": "company",
"reference_document": "Company",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Setup Company",
"validate_action": 1
}

View File

@@ -0,0 +1,20 @@
{
"action": "Create Entry",
"action_label": "Setup Email Account",
"creation": "2026-02-24 18:04:39.983155",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2026-02-24 18:04:39.983155",
"modified_by": "Administrator",
"name": "Setup Email Account",
"owner": "Administrator",
"reference_document": "Email Account",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Setup Email Account",
"validate_action": 1
}

View File

@@ -0,0 +1,20 @@
{
"action": "Go to Page",
"action_label": "Setup Role Permissions",
"creation": "2026-02-24 18:05:10.485778",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2026-02-24 18:05:10.485778",
"modified_by": "Administrator",
"name": "Setup Role Permissions",
"owner": "Administrator",
"path": "permission-manager",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Setup Role Permissions",
"validate_action": 1
}

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import _
from frappe.utils import add_days, flt, get_datetime_str, nowdate
from frappe.utils.data import now_datetime
from frappe.utils.data import DateTimeLikeObject, now_datetime
from frappe.utils.nestedset import get_root_of
from erpnext import get_default_company
@@ -92,7 +92,12 @@ def get_pegged_rate(pegged_map, from_currency, to_currency, transaction_date=Non
@frappe.whitelist()
def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=None):
def get_exchange_rate(
from_currency: str,
to_currency: str,
transaction_date: DateTimeLikeObject | None = None,
args: str | None = None,
):
if not (from_currency and to_currency):
# manqala 19/09/2016: Should this be an empty return or should it throw and exception?
return
@@ -221,6 +226,8 @@ def set_defaults_for_tests():
frappe.db.set_default(key, value)
frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
frappe.db.set_single_value("Stock Settings", "enable_serial_and_batch_no_for_item", 1)
def insert_record(records):
from frappe.desk.page.setup_wizard.setup_wizard import make_records

View File

@@ -54,7 +54,7 @@ def get_leaderboards():
@frappe.whitelist()
def get_all_customers(date_range, company, field, limit=None):
def get_all_customers(date_range: str, company: str, field: str, limit: int | None = None):
filters = [["docstatus", "=", "1"], ["company", "=", company]]
from_date, to_date = parse_date_range(date_range)
if field == "outstanding_amount":
@@ -89,7 +89,7 @@ def get_all_customers(date_range, company, field, limit=None):
@frappe.whitelist()
def get_all_items(date_range, company, field, limit=None):
def get_all_items(date_range: str, company: str, field: str, limit: int | None = None):
if field in ("available_stock_qty", "available_stock_value"):
sum_field = "actual_qty" if field == "available_stock_qty" else "stock_value"
results = frappe.db.get_all(
@@ -135,7 +135,7 @@ def get_all_items(date_range, company, field, limit=None):
@frappe.whitelist()
def get_all_suppliers(date_range, company, field, limit=None):
def get_all_suppliers(date_range: str, company: str, field: str, limit: int | None = None):
filters = [["docstatus", "=", "1"], ["company", "=", company]]
from_date, to_date = parse_date_range(date_range)
@@ -171,7 +171,7 @@ def get_all_suppliers(date_range, company, field, limit=None):
@frappe.whitelist()
def get_all_sales_partner(date_range, company, field, limit=None):
def get_all_sales_partner(date_range: str, company: str, field: str, limit: int | None = None):
if field == "total_sales_amount":
select_field = "base_net_total"
elif field == "total_commission":
@@ -196,7 +196,7 @@ def get_all_sales_partner(date_range, company, field, limit=None):
@frappe.whitelist()
def get_all_sales_person(date_range, company, field=None, limit=0):
def get_all_sales_person(date_range: str, company: str, field: str | None = None, limit: int | None = None):
filters = [
["docstatus", "=", "1"],
["company", "=", company],

View File

@@ -84,7 +84,25 @@ frappe.ui.form.on("Item", {
}
},
toggle_has_serial_batch_fields(frm) {
let hide_fields = cint(frappe.user_defaults?.enable_serial_and_batch_no_for_item) === 0 ? 1 : 0;
frm.toggle_display(["serial_no_series", "batch_number_series", "create_new_batch"], !hide_fields);
frm.toggle_enable(["has_serial_no", "has_batch_no"], !hide_fields);
if (hide_fields) {
let description = __(
"To enable the Serial No and Batch No feature, please check the 'Enable Serial / Batch No for Item' checkbox in Stock Settings."
);
frm.set_df_property("has_serial_no", "description", description);
frm.set_df_property("has_batch_no", "description", description);
}
},
refresh: function (frm) {
frm.trigger("toggle_has_serial_batch_fields");
if (frm.doc.is_stock_item) {
frm.add_custom_button(
__("Stock Balance"),

View File

@@ -452,6 +452,7 @@
"fieldname": "batch_number_series",
"fieldtype": "Data",
"label": "Batch Number Series",
"show_description_on_click": 1,
"translatable": 1
},
{
@@ -493,7 +494,8 @@
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
"fieldname": "serial_no_series",
"fieldtype": "Data",
"label": "Serial Number Series"
"label": "Serial Number Series",
"show_description_on_click": 1
},
{
"collapsible": 1,
@@ -985,7 +987,7 @@
"image_field": "image",
"links": [],
"make_attachments_public": 1,
"modified": "2026-02-05 17:20:35.605734",
"modified": "2026-03-05 16:29:31.653447",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@@ -16,6 +16,10 @@ from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class IncorrectCompanyValidationError(frappe.ValidationError):
pass
class LandedCostVoucher(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -77,6 +81,7 @@ class LandedCostVoucher(Document):
self.check_mandatory()
self.validate_receipt_documents()
self.validate_line_items()
self.validate_expense_accounts()
init_landed_taxes_and_totals(self)
self.set_total_taxes_and_charges()
if not self.get("items"):
@@ -118,11 +123,28 @@ class LandedCostVoucher(Document):
receipt_documents = []
for d in self.get("purchase_receipts"):
docstatus = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "docstatus")
docstatus, company = frappe.get_cached_value(
d.receipt_document_type, d.receipt_document, ["docstatus", "company"]
)
if docstatus != 1:
msg = f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted"
frappe.throw(_(msg), title=_("Invalid Document"))
if company != self.company:
frappe.throw(
_(
"Row {0}: {1} {2} is linked to company {3}. Please select a document belonging to company {4}."
).format(
d.idx,
d.receipt_document_type,
frappe.bold(d.receipt_document),
frappe.bold(company),
frappe.bold(self.company),
),
title=_("Incorrect Company"),
exc=IncorrectCompanyValidationError,
)
if d.receipt_document_type == "Purchase Invoice":
update_stock = frappe.db.get_value(
d.receipt_document_type, d.receipt_document, "update_stock"
@@ -154,6 +176,24 @@ class LandedCostVoucher(Document):
_("Row {0}: Cost center is required for an item {1}").format(item.idx, item.item_code)
)
def validate_expense_accounts(self):
for t in self.taxes:
company = frappe.get_cached_value("Account", t.expense_account, "company")
if company != self.company:
frappe.throw(
_(
"Row {0}: Expense Account {1} is linked to company {2}. Please select an account belonging to company {3}."
).format(
t.idx,
frappe.bold(t.expense_account),
frappe.bold(company),
frappe.bold(self.company),
),
title=_("Incorrect Account"),
exc=IncorrectCompanyValidationError,
)
def set_total_taxes_and_charges(self):
self.total_taxes_and_charges = sum(flt(d.base_amount) for d in self.get("taxes"))

Some files were not shown because too many files have changed in this diff Show More