Merge pull request #33776 from ruthra-kumar/performance_tuning_ple_migration
patch: reduce memory usage by paging through records
diff --git a/.github/helper/.flake8_strict b/.github/helper/.flake8_strict
index 198ec7b..3e8f7dd 100644
--- a/.github/helper/.flake8_strict
+++ b/.github/helper/.flake8_strict
@@ -66,7 +66,8 @@
F841,
E713,
E712,
- B023
+ B023,
+ B028
max-line-length = 200
diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py
index 378983e..8334604 100644
--- a/.github/helper/documentation.py
+++ b/.github/helper/documentation.py
@@ -3,52 +3,71 @@
from urllib.parse import urlparse
-docs_repos = [
- "frappe_docs",
- "erpnext_documentation",
+WEBSITE_REPOS = [
"erpnext_com",
"frappe_io",
]
+DOCUMENTATION_DOMAINS = [
+ "docs.erpnext.com",
+ "frappeframework.com",
+]
-def uri_validator(x):
- result = urlparse(x)
- return all([result.scheme, result.netloc, result.path])
-def docs_link_exists(body):
- for line in body.splitlines():
- for word in line.split():
- if word.startswith('http') and uri_validator(word):
- parsed_url = urlparse(word)
- if parsed_url.netloc == "github.com":
- parts = parsed_url.path.split('/')
- if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
- return True
- elif parsed_url.netloc == "docs.erpnext.com":
- return True
+def is_valid_url(url: str) -> bool:
+ parts = urlparse(url)
+ return all((parts.scheme, parts.netloc, parts.path))
+
+
+def is_documentation_link(word: str) -> bool:
+ if not word.startswith("http") or not is_valid_url(word):
+ return False
+
+ parsed_url = urlparse(word)
+ if parsed_url.netloc in DOCUMENTATION_DOMAINS:
+ return True
+
+ if parsed_url.netloc == "github.com":
+ parts = parsed_url.path.split("/")
+ if len(parts) == 5 and parts[1] == "frappe" and parts[2] in WEBSITE_REPOS:
+ return True
+
+ return False
+
+
+def contains_documentation_link(body: str) -> bool:
+ return any(
+ is_documentation_link(word)
+ for line in body.splitlines()
+ for word in line.split()
+ )
+
+
+def check_pull_request(number: str) -> "tuple[int, str]":
+ response = requests.get(f"https://api.github.com/repos/frappe/erpnext/pulls/{number}")
+ if not response.ok:
+ return 1, "Pull Request Not Found! ⚠️"
+
+ payload = response.json()
+ title = (payload.get("title") or "").lower().strip()
+ head_sha = (payload.get("head") or {}).get("sha")
+ body = (payload.get("body") or "").lower()
+
+ if (
+ not title.startswith("feat")
+ or not head_sha
+ or "no-docs" in body
+ or "backport" in body
+ ):
+ return 0, "Skipping documentation checks... 🏃"
+
+ if contains_documentation_link(body):
+ return 0, "Documentation Link Found. You're Awesome! 🎉"
+
+ return 1, "Documentation Link Not Found! ⚠️"
if __name__ == "__main__":
- pr = sys.argv[1]
- response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr))
-
- if response.ok:
- payload = response.json()
- title = (payload.get("title") or "").lower().strip()
- head_sha = (payload.get("head") or {}).get("sha")
- body = (payload.get("body") or "").lower()
-
- if (title.startswith("feat")
- and head_sha
- and "no-docs" not in body
- and "backport" not in body
- ):
- if docs_link_exists(body):
- print("Documentation Link Found. You're Awesome! 🎉")
-
- else:
- print("Documentation Link Not Found! ⚠️")
- sys.exit(1)
-
- else:
- print("Skipping documentation checks... 🏃")
+ exit_code, message = check_pull_request(sys.argv[1])
+ print(message)
+ sys.exit(exit_code)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 73aae33..d70977c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -32,8 +32,8 @@
- id: black
additional_dependencies: ['click==8.0.4']
- - repo: https://github.com/timothycrosley/isort
- rev: 5.9.1
+ - repo: https://github.com/PyCQA/isort
+ rev: 5.12.0
hooks:
- id: isort
exclude: ".*setup.py$"
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index 8ae90ce..d537adf 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -56,36 +56,41 @@
accounts = nodes;
}
- const get_balances = frappe.call({
- method: 'erpnext.accounts.utils.get_account_balances',
- args: {
- accounts: accounts,
- company: cur_tree.args.company
- },
- });
+ frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
+ if(value) {
- get_balances.then(r => {
- if (!r.message || r.message.length == 0) return;
+ const get_balances = frappe.call({
+ method: 'erpnext.accounts.utils.get_account_balances',
+ args: {
+ accounts: accounts,
+ company: cur_tree.args.company
+ },
+ });
- for (let account of r.message) {
+ get_balances.then(r => {
+ if (!r.message || r.message.length == 0) return;
- const node = cur_tree.nodes && cur_tree.nodes[account.value];
- if (!node || node.is_root) continue;
+ for (let account of r.message) {
- // show Dr if positive since balance is calculated as debit - credit else show Cr
- const balance = account.balance_in_account_currency || account.balance;
- const dr_or_cr = balance > 0 ? "Dr": "Cr";
- const format = (value, currency) => format_currency(Math.abs(value), currency);
+ const node = cur_tree.nodes && cur_tree.nodes[account.value];
+ if (!node || node.is_root) continue;
- if (account.balance!==undefined) {
- node.parent && node.parent.find('.balance-area').remove();
- $('<span class="balance-area pull-right">'
- + (account.balance_in_account_currency ?
- (format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
- + format(account.balance, account.company_currency)
- + " " + dr_or_cr
- + '</span>').insertBefore(node.$ul);
- }
+ // show Dr if positive since balance is calculated as debit - credit else show Cr
+ const balance = account.balance_in_account_currency || account.balance;
+ const dr_or_cr = balance > 0 ? "Dr": "Cr";
+ const format = (value, currency) => format_currency(Math.abs(value), currency);
+
+ if (account.balance!==undefined) {
+ node.parent && node.parent.find('.balance-area').remove();
+ $('<span class="balance-area pull-right">'
+ + (account.balance_in_account_currency ?
+ (format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
+ + format(account.balance, account.company_currency)
+ + " " + dr_or_cr
+ + '</span>').insertBefore(node.$ul);
+ }
+ }
+ });
}
});
},
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json
index ee501f6..741d428 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json
@@ -1,38 +1,38 @@
{
- "country_code": "de",
- "name": "SKR03 mit Kontonummern",
- "tree": {
- "Aktiva": {
- "is_group": 1,
+ "country_code": "de",
+ "name": "SKR03 mit Kontonummern",
+ "tree": {
+ "Aktiva": {
+ "is_group": 1,
"root_type": "Asset",
- "A - Anlagevermögen": {
- "is_group": 1,
- "EDV-Software": {
- "account_number": "0027",
- "account_type": "Fixed Asset"
- },
- "Gesch\u00e4ftsausstattung": {
- "account_number": "0410",
- "account_type": "Fixed Asset"
- },
- "B\u00fcroeinrichtung": {
- "account_number": "0420",
- "account_type": "Fixed Asset"
- },
- "Darlehen": {
- "account_number": "0565"
- },
- "Maschinen": {
- "account_number": "0210",
- "account_type": "Fixed Asset"
- },
- "Betriebsausstattung": {
- "account_number": "0400",
- "account_type": "Fixed Asset"
- },
- "Ladeneinrichtung": {
- "account_number": "0430",
- "account_type": "Fixed Asset"
+ "A - Anlagevermögen": {
+ "is_group": 1,
+ "EDV-Software": {
+ "account_number": "0027",
+ "account_type": "Fixed Asset"
+ },
+ "Geschäftsausstattung": {
+ "account_number": "0410",
+ "account_type": "Fixed Asset"
+ },
+ "Büroeinrichtung": {
+ "account_number": "0420",
+ "account_type": "Fixed Asset"
+ },
+ "Darlehen": {
+ "account_number": "0565"
+ },
+ "Maschinen": {
+ "account_number": "0210",
+ "account_type": "Fixed Asset"
+ },
+ "Betriebsausstattung": {
+ "account_number": "0400",
+ "account_type": "Fixed Asset"
+ },
+ "Ladeneinrichtung": {
+ "account_number": "0430",
+ "account_type": "Fixed Asset"
},
"Accumulated Depreciation": {
"account_type": "Accumulated Depreciation"
@@ -60,36 +60,46 @@
"Durchlaufende Posten": {
"account_number": "1590"
},
- "Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
+ "Verrechnungskonto Gewinnermittlung § 4 Abs. 3 EStG, nicht ergebniswirksam": {
"account_number": "1371"
},
"Abziehbare Vorsteuer": {
- "account_type": "Tax",
"is_group": 1,
- "Abziehbare Vorsteuer 7%": {
- "account_number": "1571"
+ "Abziehbare Vorsteuer 7 %": {
+ "account_number": "1571",
+ "account_type": "Tax",
+ "tax_rate": 7.0
},
- "Abziehbare Vorsteuer 19%": {
- "account_number": "1576"
+ "Abziehbare Vorsteuer 19 %": {
+ "account_number": "1576",
+ "account_type": "Tax",
+ "tax_rate": 19.0
},
- "Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
- "account_number": "1577"
- },
- "Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
- "account_number": "3120"
+ "Abziehbare Vorsteuer nach § 13b UStG 19 %": {
+ "account_number": "1577",
+ "account_type": "Tax",
+ "tax_rate": 19.0
}
}
},
"III. Wertpapiere": {
- "is_group": 1
+ "is_group": 1,
+ "Anteile an verbundenen Unternehmen (Umlaufvermögen)": {
+ "account_number": "1340"
+ },
+ "Anteile an herrschender oder mit Mehrheit beteiligter Gesellschaft": {
+ "account_number": "1344"
+ },
+ "Sonstige Wertpapiere": {
+ "account_number": "1348"
+ }
},
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
"is_group": 1,
"Kasse": {
- "account_type": "Cash",
"is_group": 1,
+ "account_type": "Cash",
"Kasse": {
- "is_group": 1,
"account_number": "1000",
"account_type": "Cash"
}
@@ -111,21 +121,21 @@
"C - Rechnungsabgrenzungsposten": {
"is_group": 1,
"Aktive Rechnungsabgrenzung": {
- "account_number": "0980"
+ "account_number": "0980"
}
},
"D - Aktive latente Steuern": {
"is_group": 1,
"Aktive latente Steuern": {
- "account_number": "0983"
+ "account_number": "0983"
}
},
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
"is_group": 1
}
- },
- "Passiva": {
- "is_group": 1,
+ },
+ "Passiva": {
+ "is_group": 1,
"root_type": "Liability",
"A. Eigenkapital": {
"is_group": 1,
@@ -200,26 +210,32 @@
},
"Umsatzsteuer": {
"is_group": 1,
- "account_type": "Tax",
- "Umsatzsteuer 7%": {
- "account_number": "1771"
+ "Umsatzsteuer 7 %": {
+ "account_number": "1771",
+ "account_type": "Tax",
+ "tax_rate": 7.0
},
- "Umsatzsteuer 19%": {
- "account_number": "1776"
+ "Umsatzsteuer 19 %": {
+ "account_number": "1776",
+ "account_type": "Tax",
+ "tax_rate": 19.0
},
"Umsatzsteuer-Vorauszahlung": {
- "account_number": "1780"
+ "account_number": "1780",
+ "account_type": "Tax"
},
"Umsatzsteuer-Vorauszahlung 1/11": {
"account_number": "1781"
},
- "Umsatzsteuer \u00a7 13b UStG 19%": {
- "account_number": "1787"
+ "Umsatzsteuer nach § 13b UStG 19 %": {
+ "account_number": "1787",
+ "account_type": "Tax",
+ "tax_rate": 19.0
},
"Umsatzsteuer Vorjahr": {
"account_number": "1790"
},
- "Umsatzsteuer fr\u00fchere Jahre": {
+ "Umsatzsteuer frühere Jahre": {
"account_number": "1791"
}
}
@@ -234,44 +250,56 @@
"E. Passive latente Steuern": {
"is_group": 1
}
- },
- "Erl\u00f6se u. Ertr\u00e4ge 2/8": {
- "is_group": 1,
- "root_type": "Income",
- "Erl\u00f6skonten 8": {
+ },
+ "Erlöse u. Erträge 2/8": {
+ "is_group": 1,
+ "root_type": "Income",
+ "Erlöskonten 8": {
"is_group": 1,
- "Erl\u00f6se": {
- "account_number": "8200",
- "account_type": "Income Account"
- },
- "Erl\u00f6se USt. 19%": {
- "account_number": "8400",
- "account_type": "Income Account"
- },
- "Erl\u00f6se USt. 7%": {
- "account_number": "8300",
- "account_type": "Income Account"
- }
- },
- "Ertragskonten 2": {
- "is_group": 1,
- "sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
- "account_number": "2650",
- "account_type": "Income Account"
- },
- "Au\u00dferordentliche Ertr\u00e4ge": {
- "account_number": "2500",
- "account_type": "Income Account"
- },
- "Sonstige Ertr\u00e4ge": {
- "account_number": "2700",
- "account_type": "Income Account"
- }
- }
- },
- "Aufwendungen 2/4": {
- "is_group": 1,
+ "Erlöse": {
+ "account_number": "8200",
+ "account_type": "Income Account"
+ },
+ "Erlöse USt. 19 %": {
+ "account_number": "8400",
+ "account_type": "Income Account"
+ },
+ "Erlöse USt. 7 %": {
+ "account_number": "8300",
+ "account_type": "Income Account"
+ }
+ },
+ "Ertragskonten 2": {
+ "is_group": 1,
+ "sonstige Zinsen und ähnliche Erträge": {
+ "account_number": "2650",
+ "account_type": "Income Account"
+ },
+ "Außerordentliche Erträge": {
+ "account_number": "2500",
+ "account_type": "Income Account"
+ },
+ "Sonstige Erträge": {
+ "account_number": "2700",
+ "account_type": "Income Account"
+ }
+ }
+ },
+ "Aufwendungen 2/4": {
+ "is_group": 1,
"root_type": "Expense",
+ "Fremdleistungen": {
+ "account_number": "3100",
+ "account_type": "Expense Account"
+ },
+ "Fremdleistungen ohne Vorsteuer": {
+ "account_number": "3109",
+ "account_type": "Expense Account"
+ },
+ "Bauleistungen eines im Inland ansässigen Unternehmers 19 % Vorsteuer und 19 % Umsatzsteuer": {
+ "account_number": "3120",
+ "account_type": "Expense Account"
+ },
"Wareneingang": {
"account_number": "3200"
},
@@ -298,234 +326,234 @@
"Gegenkonto 4996-4998": {
"account_number": "4999"
},
- "Abschreibungen": {
- "is_group": 1,
+ "Abschreibungen": {
+ "is_group": 1,
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
- "account_number": "4830",
- "account_type": "Accumulated Depreciation"
+ "account_number": "4830",
+ "account_type": "Accumulated Depreciation"
},
"Abschreibungen auf Gebäude": {
- "account_number": "4831",
- "account_type": "Depreciation"
+ "account_number": "4831",
+ "account_type": "Depreciation"
},
"Abschreibungen auf Kfz": {
- "account_number": "4832",
- "account_type": "Depreciation"
+ "account_number": "4832",
+ "account_type": "Depreciation"
},
"Sofortabschreibung GWG": {
- "account_number": "4855",
- "account_type": "Expense Account"
+ "account_number": "4855",
+ "account_type": "Expense Account"
}
- },
- "Kfz-Kosten": {
- "is_group": 1,
- "Kfz-Steuer": {
- "account_number": "4510",
- "account_type": "Expense Account"
- },
- "Kfz-Versicherungen": {
- "account_number": "4520",
- "account_type": "Expense Account"
- },
- "laufende Kfz-Betriebskosten": {
- "account_number": "4530",
- "account_type": "Expense Account"
- },
- "Kfz-Reparaturen": {
- "account_number": "4540",
- "account_type": "Expense Account"
- },
- "Fremdfahrzeuge": {
- "account_number": "4570",
- "account_type": "Expense Account"
- },
- "sonstige Kfz-Kosten": {
- "account_number": "4580",
- "account_type": "Expense Account"
- }
- },
- "Personalkosten": {
- "is_group": 1,
- "Geh\u00e4lter": {
- "account_number": "4120",
- "account_type": "Expense Account"
- },
- "gesetzliche soziale Aufwendungen": {
- "account_number": "4130",
- "account_type": "Expense Account"
- },
- "Aufwendungen f\u00fcr Altersvorsorge": {
- "account_number": "4165",
- "account_type": "Expense Account"
- },
- "Verm\u00f6genswirksame Leistungen": {
- "account_number": "4170",
- "account_type": "Expense Account"
- },
- "Aushilfsl\u00f6hne": {
- "account_number": "4190",
- "account_type": "Expense Account"
- }
- },
- "Raumkosten": {
- "is_group": 1,
- "Miete und Nebenkosten": {
- "account_number": "4210",
- "account_type": "Expense Account"
- },
- "Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
- "account_number": "4240",
- "account_type": "Expense Account"
- },
- "Reinigung": {
- "account_number": "4250",
- "account_type": "Expense Account"
- }
- },
- "Reparatur/Instandhaltung": {
- "is_group": 1,
- "Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
- "account_number": "4805",
- "account_type": "Expense Account"
- }
- },
- "Versicherungsbeitr\u00e4ge": {
- "is_group": 1,
- "Versicherungen": {
- "account_number": "4360",
- "account_type": "Expense Account"
- },
- "Beitr\u00e4ge": {
- "account_number": "4380",
- "account_type": "Expense Account"
- },
- "sonstige Ausgaben": {
- "account_number": "4390",
- "account_type": "Expense Account"
- },
- "steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
- "account_number": "4396",
- "account_type": "Expense Account"
- }
- },
- "Werbe-/Reisekosten": {
- "is_group": 1,
- "Werbekosten": {
- "account_number": "4610",
- "account_type": "Expense Account"
- },
- "Aufmerksamkeiten": {
- "account_number": "4653",
- "account_type": "Expense Account"
- },
- "nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
- "account_number": "4665",
- "account_type": "Expense Account"
- },
- "Reisekosten Unternehmer": {
- "account_number": "4670",
- "account_type": "Expense Account"
- }
- },
- "verschiedene Kosten": {
- "is_group": 1,
- "Porto": {
- "account_number": "4910",
- "account_type": "Expense Account"
- },
- "Telekom": {
- "account_number": "4920",
- "account_type": "Expense Account"
- },
- "Mobilfunk D2": {
- "account_number": "4921",
- "account_type": "Expense Account"
- },
- "Internet": {
- "account_number": "4922",
- "account_type": "Expense Account"
- },
- "B\u00fcrobedarf": {
- "account_number": "4930",
- "account_type": "Expense Account"
- },
- "Zeitschriften, B\u00fccher": {
- "account_number": "4940",
- "account_type": "Expense Account"
- },
- "Fortbildungskosten": {
- "account_number": "4945",
- "account_type": "Expense Account"
- },
- "Buchf\u00fchrungskosten": {
- "account_number": "4955",
- "account_type": "Expense Account"
- },
- "Abschlu\u00df- u. Pr\u00fcfungskosten": {
- "account_number": "4957",
- "account_type": "Expense Account"
- },
- "Nebenkosten des Geldverkehrs": {
- "account_number": "4970",
- "account_type": "Expense Account"
- },
- "Werkzeuge und Kleinger\u00e4te": {
- "account_number": "4985",
- "account_type": "Expense Account"
- }
- },
- "Zinsaufwendungen": {
- "is_group": 1,
- "Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
- "account_number": "2110",
- "account_type": "Expense Account"
- },
- "Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
- "account_number": "2121",
- "account_type": "Expense Account"
- }
- }
- },
- "Anfangsbestand 9": {
- "is_group": 1,
- "root_type": "Equity",
- "Saldenvortragskonten": {
- "is_group": 1,
- "Saldenvortrag Sachkonten": {
- "account_number": "9000"
- },
- "Saldenvortr\u00e4ge Debitoren": {
- "account_number": "9008"
- },
- "Saldenvortr\u00e4ge Kreditoren": {
- "account_number": "9009"
- }
- }
- },
- "Privatkonten 1": {
- "is_group": 1,
- "root_type": "Equity",
- "Privatentnahmen/-einlagen": {
- "is_group": 1,
- "Privatentnahme allgemein": {
- "account_number": "1800"
- },
- "Privatsteuern": {
- "account_number": "1810"
- },
- "Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
- "account_number": "1820"
- },
- "Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
- "account_number": "1830"
- },
- "Au\u00dfergew\u00f6hnliche Belastungen": {
- "account_number": "1850"
- },
- "Privateinlagen": {
- "account_number": "1890"
- }
- }
- }
- }
+ },
+ "Kfz-Kosten": {
+ "is_group": 1,
+ "Kfz-Steuer": {
+ "account_number": "4510",
+ "account_type": "Expense Account"
+ },
+ "Kfz-Versicherungen": {
+ "account_number": "4520",
+ "account_type": "Expense Account"
+ },
+ "laufende Kfz-Betriebskosten": {
+ "account_number": "4530",
+ "account_type": "Expense Account"
+ },
+ "Kfz-Reparaturen": {
+ "account_number": "4540",
+ "account_type": "Expense Account"
+ },
+ "Fremdfahrzeuge": {
+ "account_number": "4570",
+ "account_type": "Expense Account"
+ },
+ "sonstige Kfz-Kosten": {
+ "account_number": "4580",
+ "account_type": "Expense Account"
+ }
+ },
+ "Personalkosten": {
+ "is_group": 1,
+ "Gehälter": {
+ "account_number": "4120",
+ "account_type": "Expense Account"
+ },
+ "gesetzliche soziale Aufwendungen": {
+ "account_number": "4130",
+ "account_type": "Expense Account"
+ },
+ "Aufwendungen für Altersvorsorge": {
+ "account_number": "4165",
+ "account_type": "Expense Account"
+ },
+ "Vermögenswirksame Leistungen": {
+ "account_number": "4170",
+ "account_type": "Expense Account"
+ },
+ "Aushilfslöhne": {
+ "account_number": "4190",
+ "account_type": "Expense Account"
+ }
+ },
+ "Raumkosten": {
+ "is_group": 1,
+ "Miete und Nebenkosten": {
+ "account_number": "4210",
+ "account_type": "Expense Account"
+ },
+ "Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
+ "account_number": "4240",
+ "account_type": "Expense Account"
+ },
+ "Reinigung": {
+ "account_number": "4250",
+ "account_type": "Expense Account"
+ }
+ },
+ "Reparatur/Instandhaltung": {
+ "is_group": 1,
+ "Reparaturen und Instandhaltungen von anderen Anlagen und Betriebs- und Geschäftsausstattung": {
+ "account_number": "4805",
+ "account_type": "Expense Account"
+ }
+ },
+ "Versicherungsbeiträge": {
+ "is_group": 1,
+ "Versicherungen": {
+ "account_number": "4360",
+ "account_type": "Expense Account"
+ },
+ "Beiträge": {
+ "account_number": "4380",
+ "account_type": "Expense Account"
+ },
+ "sonstige Ausgaben": {
+ "account_number": "4390",
+ "account_type": "Expense Account"
+ },
+ "steuerlich abzugsfähige Verspätungszuschläge und Zwangsgelder": {
+ "account_number": "4396",
+ "account_type": "Expense Account"
+ }
+ },
+ "Werbe-/Reisekosten": {
+ "is_group": 1,
+ "Werbekosten": {
+ "account_number": "4610",
+ "account_type": "Expense Account"
+ },
+ "Aufmerksamkeiten": {
+ "account_number": "4653",
+ "account_type": "Expense Account"
+ },
+ "nicht abzugsfähige Betriebsausg. aus Werbe-, Repräs.- u. Reisekosten": {
+ "account_number": "4665",
+ "account_type": "Expense Account"
+ },
+ "Reisekosten Unternehmer": {
+ "account_number": "4670",
+ "account_type": "Expense Account"
+ }
+ },
+ "verschiedene Kosten": {
+ "is_group": 1,
+ "Porto": {
+ "account_number": "4910",
+ "account_type": "Expense Account"
+ },
+ "Telekom": {
+ "account_number": "4920",
+ "account_type": "Expense Account"
+ },
+ "Mobilfunk D2": {
+ "account_number": "4921",
+ "account_type": "Expense Account"
+ },
+ "Internet": {
+ "account_number": "4922",
+ "account_type": "Expense Account"
+ },
+ "Bürobedarf": {
+ "account_number": "4930",
+ "account_type": "Expense Account"
+ },
+ "Zeitschriften, Bücher": {
+ "account_number": "4940",
+ "account_type": "Expense Account"
+ },
+ "Fortbildungskosten": {
+ "account_number": "4945",
+ "account_type": "Expense Account"
+ },
+ "Buchführungskosten": {
+ "account_number": "4955",
+ "account_type": "Expense Account"
+ },
+ "Abschluß- u. Prüfungskosten": {
+ "account_number": "4957",
+ "account_type": "Expense Account"
+ },
+ "Nebenkosten des Geldverkehrs": {
+ "account_number": "4970",
+ "account_type": "Expense Account"
+ },
+ "Werkzeuge und Kleingeräte": {
+ "account_number": "4985",
+ "account_type": "Expense Account"
+ }
+ },
+ "Zinsaufwendungen": {
+ "is_group": 1,
+ "Zinsaufwendungen für kurzfristige Verbindlichkeiten": {
+ "account_number": "2110",
+ "account_type": "Expense Account"
+ },
+ "Zinsaufwendungen für KFZ Finanzierung": {
+ "account_number": "2121",
+ "account_type": "Expense Account"
+ }
+ }
+ },
+ "Anfangsbestand 9": {
+ "is_group": 1,
+ "root_type": "Equity",
+ "Saldenvortragskonten": {
+ "is_group": 1,
+ "Saldenvortrag Sachkonten": {
+ "account_number": "9000"
+ },
+ "Saldenvorträge Debitoren": {
+ "account_number": "9008"
+ },
+ "Saldenvorträge Kreditoren": {
+ "account_number": "9009"
+ }
+ }
+ },
+ "Privatkonten 1": {
+ "is_group": 1,
+ "root_type": "Equity",
+ "Privatentnahmen/-einlagen": {
+ "is_group": 1,
+ "Privatentnahme allgemein": {
+ "account_number": "1800"
+ },
+ "Privatsteuern": {
+ "account_number": "1810"
+ },
+ "Sonderausgaben beschränkt abzugsfähig": {
+ "account_number": "1820"
+ },
+ "Sonderausgaben unbeschränkt abzugsfähig": {
+ "account_number": "1830"
+ },
+ "Außergewöhnliche Belastungen": {
+ "account_number": "1850"
+ },
+ "Privateinlagen": {
+ "account_number": "1890"
+ }
+ }
+ }
+ }
}
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 1e2e2ac..3f985b6 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -56,7 +56,9 @@
"acc_frozen_upto",
"column_break_25",
"frozen_accounts_modifier",
- "report_settings_sb"
+ "report_settings_sb",
+ "tab_break_dpet",
+ "show_balance_in_coa"
],
"fields": [
{
@@ -347,6 +349,17 @@
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
"fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account "
+ },
+ {
+ "fieldname": "tab_break_dpet",
+ "fieldtype": "Tab Break",
+ "label": "Chart Of Accounts"
+ },
+ {
+ "default": "1",
+ "fieldname": "show_balance_in_coa",
+ "fieldtype": "Check",
+ "label": "Show Balances in Chart Of Accounts"
}
],
"icon": "icon-cog",
@@ -354,7 +367,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2022-11-27 21:49:52.538655",
+ "modified": "2023-01-02 12:07:42.434214",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py
index 53838fb..4c628a4 100644
--- a/erpnext/accounts/doctype/budget/budget.py
+++ b/erpnext/accounts/doctype/budget/budget.py
@@ -184,6 +184,11 @@
amount = expense_amount or get_amount(args, budget)
yearly_action, monthly_action = get_actions(args, budget)
+ if yearly_action in ("Stop", "Warn"):
+ compare_expense_with_budget(
+ args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
+ )
+
if monthly_action in ["Stop", "Warn"]:
budget_amount = get_accumulated_monthly_budget(
budget.monthly_distribution, args.posting_date, args.fiscal_year, budget.budget_amount
@@ -195,28 +200,28 @@
args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount
)
- if (
- yearly_action in ("Stop", "Warn")
- and monthly_action != "Stop"
- and yearly_action != monthly_action
- ):
- compare_expense_with_budget(
- args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
- )
-
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
- actual_expense = amount or get_actual_expense(args)
- if actual_expense > budget_amount:
- diff = actual_expense - budget_amount
+ actual_expense = get_actual_expense(args)
+ total_expense = actual_expense + amount
+
+ if total_expense > budget_amount:
+ if actual_expense > budget_amount:
+ error_tense = _("is already")
+ diff = actual_expense - budget_amount
+ else:
+ error_tense = _("will be")
+ diff = total_expense - budget_amount
+
currency = frappe.get_cached_value("Company", args.company, "default_currency")
- msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format(
+ msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It {5} exceed by {6}").format(
_(action_for),
frappe.bold(args.account),
- args.budget_against_field,
+ frappe.unscrub(args.budget_against_field),
frappe.bold(budget_against),
frappe.bold(fmt_money(budget_amount, currency=currency)),
+ error_tense,
frappe.bold(fmt_money(diff, currency=currency)),
)
@@ -227,9 +232,9 @@
action = "Warn"
if action == "Stop":
- frappe.throw(msg, BudgetError)
+ frappe.throw(msg, BudgetError, title=_("Budget Exceeded"))
else:
- frappe.msgprint(msg, indicator="orange")
+ frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded"))
def get_actions(args, budget):
@@ -351,7 +356,9 @@
"""
select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle
- where gle.account=%(account)s
+ where
+ is_cancelled = 0
+ and gle.account=%(account)s
{condition1}
and gle.fiscal_year=%(fiscal_year)s
and gle.company=%(company)s
diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
index d25016f..54ffe21 100644
--- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
+++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
@@ -28,9 +28,14 @@
class CostCenterAllocation(Document):
+ def __init__(self, *args, **kwargs):
+ super(CostCenterAllocation, self).__init__(*args, **kwargs)
+ self._skip_from_date_validation = False
+
def validate(self):
self.validate_total_allocation_percentage()
- self.validate_from_date_based_on_existing_gle()
+ if not self._skip_from_date_validation:
+ self.validate_from_date_based_on_existing_gle()
self.validate_backdated_allocation()
self.validate_main_cost_center()
self.validate_child_cost_centers()
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
index 7921fcc..c62b711 100644
--- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"api_details_section",
+ "disabled",
"service_provider",
"api_endpoint",
"url",
@@ -77,12 +78,18 @@
"label": "Service Provider",
"options": "frankfurter.app\nexchangerate.host\nCustom",
"reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "label": "Disabled"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2022-01-10 15:51:14.521174",
+ "modified": "2023-01-09 12:19:03.955906",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Currency Exchange Settings",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 30a3201..21f27ae 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -8,7 +8,7 @@
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
- frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
+ frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry'];
},
refresh: function(frm) {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 3f69d5c..498fc7c 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -137,8 +137,7 @@
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
- "options": "Finance Book",
- "read_only": 1
+ "options": "Finance Book"
},
{
"fieldname": "2_add_edit_gl_entries",
@@ -539,7 +538,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-28 17:40:01.241908",
+ "modified": "2023-01-17 12:53:53.280620",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 13712ce..154fdc0 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -69,6 +69,10 @@
def get_jv_entries(self):
condition = self.get_conditions()
+
+ if self.get("cost_center"):
+ condition += f" and t2.cost_center = '{self.cost_center}' "
+
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
@@ -334,7 +338,7 @@
)
# Account Currency has balance
- dr_or_cr = "debit" if self.party_type == "Customer" else "debit"
+ dr_or_cr = "debit" if self.party_type == "Customer" else "credit"
reverse_dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
journal_account = frappe._dict(
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 2ba90b4..00e3934 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -747,6 +747,73 @@
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
+ def test_cost_center_filter_on_vouchers(self):
+ """
+ Test Cost Center filter is applied on Invoices, Payment Entries and Journals
+ """
+ transaction_date = nowdate()
+ rate = 100
+
+ # 'Main - PR' Cost Center
+ si1 = self.create_sales_invoice(
+ qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
+ )
+ si1.cost_center = self.main_cc.name
+ si1.submit()
+
+ pe1 = self.create_payment_entry(posting_date=transaction_date, amount=rate)
+ pe1.cost_center = self.main_cc.name
+ pe1 = pe1.save().submit()
+
+ je1 = self.create_journal_entry(self.bank, self.debit_to, 100, transaction_date)
+ je1.accounts[0].cost_center = self.main_cc.name
+ je1.accounts[1].cost_center = self.main_cc.name
+ je1.accounts[1].party_type = "Customer"
+ je1.accounts[1].party = self.customer
+ je1 = je1.save().submit()
+
+ # 'Sub - PR' Cost Center
+ si2 = self.create_sales_invoice(
+ qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
+ )
+ si2.cost_center = self.sub_cc.name
+ si2.submit()
+
+ pe2 = self.create_payment_entry(posting_date=transaction_date, amount=rate)
+ pe2.cost_center = self.sub_cc.name
+ pe2 = pe2.save().submit()
+
+ je2 = self.create_journal_entry(self.bank, self.debit_to, 100, transaction_date)
+ je2.accounts[0].cost_center = self.sub_cc.name
+ je2.accounts[1].cost_center = self.sub_cc.name
+ je2.accounts[1].party_type = "Customer"
+ je2.accounts[1].party = self.customer
+ je2 = je2.save().submit()
+
+ pr = self.create_payment_reconciliation()
+ pr.cost_center = self.main_cc.name
+
+ pr.get_unreconciled_entries()
+
+ # check PR tool output
+ self.assertEqual(len(pr.get("invoices")), 1)
+ self.assertEqual(pr.get("invoices")[0].get("invoice_number"), si1.name)
+ self.assertEqual(len(pr.get("payments")), 2)
+ payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
+ self.assertCountEqual(payment_vouchers, [pe1.name, je1.name])
+
+ # Change cost center
+ pr.cost_center = self.sub_cc.name
+
+ pr.get_unreconciled_entries()
+
+ # check PR tool output
+ self.assertEqual(len(pr.get("invoices")), 1)
+ self.assertEqual(pr.get("invoices")[0].get("invoice_number"), si2.name)
+ self.assertEqual(len(pr.get("payments")), 2)
+ payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
+ self.assertCountEqual(payment_vouchers, [je2.name, pe2.name])
+
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 4fc12db..fc837c7 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -51,7 +51,7 @@
if existing_payment_request_amount:
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
- if hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") != "Shopping Cart":
+ if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart":
ref_amount = get_amount(ref_doc, self.payment_account)
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index b543016..a1239d6 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -675,7 +675,7 @@
def get_pos_reserved_qty(item_code, warehouse):
reserved_qty = frappe.db.sql(
- """select sum(p_item.qty) as qty
+ """select sum(p_item.stock_qty) as qty
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
where p.name = p_item.parent
and ifnull(p.consolidated_invoice, '') = ''
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 1ce780e..57feaa0 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -687,11 +687,21 @@
def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
if pricing_rule_args:
- items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)
+ args = {(d["item_code"], d["pricing_rules"]): d for d in pricing_rule_args}
- for args in pricing_rule_args:
- if not items or (args.get("item_code"), args.get("pricing_rules")) not in items:
- doc.append("items", args)
+ for item in doc.items:
+ if not item.is_free_item:
+ continue
+
+ free_item_data = args.get((item.item_code, item.pricing_rules))
+ if free_item_data:
+ free_item_data.pop("item_name")
+ free_item_data.pop("description")
+ item.update(free_item_data)
+ args.pop((item.item_code, item.pricing_rules))
+
+ for free_item in args.values():
+ doc.append("items", free_item)
def get_pricing_rule_items(pr_doc, other_items=False) -> list:
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 6281400..54caf6f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -1426,6 +1426,7 @@
},
{
"default": "0",
+ "depends_on": "apply_tds",
"fieldname": "tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
@@ -1435,12 +1436,13 @@
"read_only": 1
},
{
+ "depends_on": "apply_tds",
"fieldname": "base_tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
"label": "Base Tax Withholding Net Total",
"no_copy": 1,
- "options": "currency",
+ "options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -1554,7 +1556,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2022-12-12 18:37:38.142688",
+ "modified": "2023-01-28 19:18:56.586321",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 4729d9c..2f4e45e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1776,6 +1776,8 @@
"width": "50%"
},
{
+ "fetch_from": "sales_partner.commission_rate",
+ "fetch_if_empty": 1,
"fieldname": "commission_rate",
"fieldtype": "Float",
"hide_days": 1,
@@ -2141,7 +2143,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2022-12-12 18:34:33.409895",
+ "modified": "2023-01-28 19:45:47.538163",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index e96847e..0ffd946 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1169,6 +1169,46 @@
frappe.db.sql("delete from `tabPOS Profile`")
+ def test_bin_details_of_packed_item(self):
+ from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ # test Update Items with product bundle
+ if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
+ bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
+ bundle_item.append(
+ "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
+ )
+ bundle_item.save(ignore_permissions=True)
+
+ make_item("_Packed Item New 1", {"is_stock_item": 1})
+ make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
+
+ si = create_sales_invoice(
+ item_code="_Test Product Bundle Item New",
+ update_stock=1,
+ warehouse="_Test Warehouse - _TC",
+ transaction_date=add_days(nowdate(), -1),
+ do_not_submit=1,
+ )
+
+ make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
+
+ bin_details = frappe.db.get_value(
+ "Bin",
+ {"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
+ ["actual_qty", "projected_qty", "ordered_qty"],
+ as_dict=1,
+ )
+
+ si.transaction_date = nowdate()
+ si.save()
+
+ packed_item = si.packed_items[0]
+ self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
+ self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
+ self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
+
def test_pos_si_without_payment(self):
make_pos_profile()
diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
index ce8c0c3..46b430c 100644
--- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
+++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "autoname": "autoincrement",
+ "autoname": "hash",
"creation": "2022-09-13 16:18:59.404842",
"doctype": "DocType",
"editable_grid": 1,
@@ -36,11 +36,11 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-09-13 23:40:41.479208",
+ "modified": "2023-01-13 13:40:41.479208",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withheld Vouchers",
- "naming_rule": "Autoincrement",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index b834d14..2c829b2 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -259,9 +259,7 @@
if tax_deducted:
net_total = inv.tax_withholding_net_total
if ldc:
- tax_amount = get_tds_amount_from_ldc(
- ldc, parties, pan_no, tax_details, posting_date, net_total
- )
+ tax_amount = get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total)
else:
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
@@ -412,12 +410,26 @@
tds_amount = 0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
+ ## for TDS to be deducted on advances
+ payment_entry_filters = {
+ "party_type": "Supplier",
+ "party": ("in", parties),
+ "docstatus": 1,
+ "apply_tax_withholding_amount": 1,
+ "unallocated_amount": (">", 0),
+ "posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
+ "tax_withholding_category": tax_details.get("tax_withholding_category"),
+ }
+
field = "sum(tax_withholding_net_total)"
if cint(tax_details.consider_party_ledger_amount):
invoice_filters.pop("apply_tds", None)
field = "sum(grand_total)"
+ payment_entry_filters.pop("apply_tax_withholding_amount", None)
+ payment_entry_filters.pop("tax_withholding_category", None)
+
supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0
supp_jv_credit_amt = (
@@ -429,14 +441,28 @@
"party": ("in", parties),
"reference_type": ("!=", "Purchase Invoice"),
},
- "sum(credit_in_account_currency)",
+ "sum(credit_in_account_currency - debit_in_account_currency)",
)
or 0.0
)
+ # Get Amount via payment entry
+ payment_entry_amounts = frappe.db.get_all(
+ "Payment Entry",
+ filters=payment_entry_filters,
+ fields=["sum(unallocated_amount) as amount", "payment_type"],
+ group_by="payment_type",
+ )
+
supp_credit_amt += supp_jv_credit_amt
supp_credit_amt += inv.tax_withholding_net_total
+ for type in payment_entry_amounts:
+ if type.payment_type == "Pay":
+ supp_credit_amt += type.amount
+ else:
+ supp_credit_amt -= type.amount
+
threshold = tax_details.get("threshold", 0)
cumulative_threshold = tax_details.get("cumulative_threshold", 0)
@@ -538,7 +564,7 @@
return inv.grand_total - tcs_tax_row_amount
-def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total):
+def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
tds_amount = 0
limit_consumed = frappe.db.get_value(
"Purchase Invoice",
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index 23caac0..1e86cf5 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -16,7 +16,7 @@
def setUpClass(self):
# create relevant supplier, etc
create_records()
- create_tax_with_holding_category()
+ create_tax_withholding_category_records()
def tearDown(self):
cancel_invoices()
@@ -38,7 +38,7 @@
pi = create_purchase_invoice(supplier="Test TDS Supplier")
pi.submit()
- # assert equal tax deduction on total invoice amount uptil now
+ # assert equal tax deduction on total invoice amount until now
self.assertEqual(pi.taxes_and_charges_deducted, 3000)
self.assertEqual(pi.grand_total, 7000)
invoices.append(pi)
@@ -47,7 +47,7 @@
pi = create_purchase_invoice(supplier="Test TDS Supplier", rate=5000)
pi.submit()
- # assert equal tax deduction on total invoice amount uptil now
+ # assert equal tax deduction on total invoice amount until now
self.assertEqual(pi.taxes_and_charges_deducted, 500)
invoices.append(pi)
@@ -130,7 +130,7 @@
invoices.append(si)
# create another invoice whose total when added to previously created invoice,
- # surpasses cumulative threshhold
+ # surpasses cumulative threshold
si = create_sales_invoice(customer="Test TCS Customer", rate=12000)
si.submit()
@@ -329,6 +329,38 @@
for d in reversed(invoices):
d.cancel()
+ def test_tax_withholding_via_payment_entry_for_advances(self):
+ frappe.db.set_value(
+ "Supplier", "Test TDS Supplier7", "tax_withholding_category", "Advance TDS Category"
+ )
+
+ # create payment entry
+ pe1 = create_payment_entry(
+ payment_type="Pay", party_type="Supplier", party="Test TDS Supplier7", paid_amount=4000
+ )
+ pe1.submit()
+
+ self.assertFalse(pe1.get("taxes"))
+
+ pe2 = create_payment_entry(
+ payment_type="Pay", party_type="Supplier", party="Test TDS Supplier7", paid_amount=4000
+ )
+ pe2.submit()
+
+ self.assertFalse(pe2.get("taxes"))
+
+ pe3 = create_payment_entry(
+ payment_type="Pay", party_type="Supplier", party="Test TDS Supplier7", paid_amount=4000
+ )
+ pe3.apply_tax_withholding_amount = 1
+ pe3.save()
+ pe3.submit()
+
+ self.assertEquals(pe3.get("taxes")[0].tax_amount, 1200)
+ pe1.cancel()
+ pe2.cancel()
+ pe3.cancel()
+
def cancel_invoices():
purchase_invoices = frappe.get_all(
@@ -450,6 +482,32 @@
return si
+def create_payment_entry(**args):
+ # return payment entry doc object
+ args = frappe._dict(args)
+ pe = frappe.get_doc(
+ {
+ "doctype": "Payment Entry",
+ "posting_date": today(),
+ "payment_type": args.payment_type,
+ "party_type": args.party_type,
+ "party": args.party,
+ "company": "_Test Company",
+ "paid_from": "Cash - _TC",
+ "paid_to": "Creditors - _TC",
+ "paid_amount": args.paid_amount or 10000,
+ "received_amount": args.paid_amount or 10000,
+ "reference_no": args.reference_no or "12345",
+ "reference_date": today(),
+ "paid_from_account_currency": "INR",
+ "paid_to_account_currency": "INR",
+ }
+ )
+
+ pe.save()
+ return pe
+
+
def create_records():
# create a new suppliers
for name in [
@@ -460,6 +518,7 @@
"Test TDS Supplier4",
"Test TDS Supplier5",
"Test TDS Supplier6",
+ "Test TDS Supplier7",
]:
if frappe.db.exists("Supplier", name):
continue
@@ -530,142 +589,129 @@
).insert()
-def create_tax_with_holding_category():
+def create_tax_withholding_category_records():
fiscal_year = get_fiscal_year(today(), company="_Test Company")
+ from_date = fiscal_year[1]
+ to_date = fiscal_year[2]
+
# Cumulative threshold
- if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"):
- frappe.get_doc(
- {
- "doctype": "Tax Withholding Category",
- "name": "Cumulative Threshold TDS",
- "category_name": "10% TDS",
- "rates": [
- {
- "from_date": fiscal_year[1],
- "to_date": fiscal_year[2],
- "tax_withholding_rate": 10,
- "single_threshold": 0,
- "cumulative_threshold": 30000.00,
- }
- ],
- "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
- }
- ).insert()
+ create_tax_withholding_category(
+ category_name="Cumulative Threshold TDS",
+ rate=10,
+ from_date=from_date,
+ to_date=to_date,
+ account="TDS - _TC",
+ single_threshold=0,
+ cumulative_threshold=30000.00,
+ )
- if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TCS"):
- frappe.get_doc(
- {
- "doctype": "Tax Withholding Category",
- "name": "Cumulative Threshold TCS",
- "category_name": "10% TCS",
- "rates": [
- {
- "from_date": fiscal_year[1],
- "to_date": fiscal_year[2],
- "tax_withholding_rate": 10,
- "single_threshold": 0,
- "cumulative_threshold": 30000.00,
- }
- ],
- "accounts": [{"company": "_Test Company", "account": "TCS - _TC"}],
- }
- ).insert()
+ # Category for TCS
+ create_tax_withholding_category(
+ category_name="Cumulative Threshold TCS",
+ rate=10,
+ from_date=from_date,
+ to_date=to_date,
+ account="TCS - _TC",
+ single_threshold=0,
+ cumulative_threshold=30000.00,
+ )
- # Single thresold
- if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"):
- frappe.get_doc(
- {
- "doctype": "Tax Withholding Category",
- "name": "Single Threshold TDS",
- "category_name": "10% TDS",
- "rates": [
- {
- "from_date": fiscal_year[1],
- "to_date": fiscal_year[2],
- "tax_withholding_rate": 10,
- "single_threshold": 20000.00,
- "cumulative_threshold": 0,
- }
- ],
- "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
- }
- ).insert()
+ # Single threshold
+ create_tax_withholding_category(
+ category_name="Single Threshold TDS",
+ rate=10,
+ from_date=from_date,
+ to_date=to_date,
+ account="TDS - _TC",
+ single_threshold=20000,
+ cumulative_threshold=0,
+ )
- if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
- frappe.get_doc(
- {
- "doctype": "Tax Withholding Category",
- "name": "New TDS Category",
- "category_name": "New TDS Category",
- "round_off_tax_amount": 1,
- "consider_party_ledger_amount": 1,
- "tax_on_excess_amount": 1,
- "rates": [
- {
- "from_date": fiscal_year[1],
- "to_date": fiscal_year[2],
- "tax_withholding_rate": 10,
- "single_threshold": 0,
- "cumulative_threshold": 30000,
- }
- ],
- "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
- }
- ).insert()
+ create_tax_withholding_category(
+ category_name="New TDS Category",
+ rate=10,
+ from_date=from_date,
+ to_date=to_date,
+ account="TDS - _TC",
+ single_threshold=0,
+ cumulative_threshold=30000,
+ round_off_tax_amount=1,
+ consider_party_ledger_amount=1,
+ tax_on_excess_amount=1,
+ )
- if not frappe.db.exists("Tax Withholding Category", "Test Service Category"):
- frappe.get_doc(
- {
- "doctype": "Tax Withholding Category",
- "name": "Test Service Category",
- "category_name": "Test Service Category",
- "rates": [
- {
- "from_date": fiscal_year[1],
- "to_date": fiscal_year[2],
- "tax_withholding_rate": 10,
- "single_threshold": 2000,
- "cumulative_threshold": 2000,
- }
- ],
- "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
- }
- ).insert()
+ create_tax_withholding_category(
+ category_name="Test Service Category",
+ rate=10,
+ from_date=from_date,
+ to_date=to_date,
+ account="TDS - _TC",
+ single_threshold=2000,
+ cumulative_threshold=2000,
+ )
- if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"):
- frappe.get_doc(
- {
- "doctype": "Tax Withholding Category",
- "name": "Test Goods Category",
- "category_name": "Test Goods Category",
- "rates": [
- {
- "from_date": fiscal_year[1],
- "to_date": fiscal_year[2],
- "tax_withholding_rate": 10,
- "single_threshold": 2000,
- "cumulative_threshold": 2000,
- }
- ],
- "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
- }
- ).insert()
+ create_tax_withholding_category(
+ category_name="Test Goods Category",
+ rate=10,
+ from_date=from_date,
+ to_date=to_date,
+ account="TDS - _TC",
+ single_threshold=2000,
+ cumulative_threshold=2000,
+ )
- if not frappe.db.exists("Tax Withholding Category", "Test Multi Invoice Category"):
+ create_tax_withholding_category(
+ category_name="Test Multi Invoice Category",
+ rate=10,
+ from_date=from_date,
+ to_date=to_date,
+ account="TDS - _TC",
+ single_threshold=5000,
+ cumulative_threshold=10000,
+ )
+
+ create_tax_withholding_category(
+ category_name="Advance TDS Category",
+ rate=10,
+ from_date=from_date,
+ to_date=to_date,
+ account="TDS - _TC",
+ single_threshold=5000,
+ cumulative_threshold=10000,
+ consider_party_ledger_amount=1,
+ )
+
+
+def create_tax_withholding_category(
+ category_name,
+ rate,
+ from_date,
+ to_date,
+ account,
+ single_threshold=0,
+ cumulative_threshold=0,
+ round_off_tax_amount=0,
+ consider_party_ledger_amount=0,
+ tax_on_excess_amount=0,
+):
+ if not frappe.db.exists("Tax Withholding Category", category_name):
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
- "name": "Test Multi Invoice Category",
- "category_name": "Test Multi Invoice Category",
+ "name": category_name,
+ "category_name": category_name,
+ "round_off_tax_amount": round_off_tax_amount,
+ "consider_party_ledger_amount": consider_party_ledger_amount,
+ "tax_on_excess_amount": tax_on_excess_amount,
"rates": [
{
- "from_date": fiscal_year[1],
- "to_date": fiscal_year[2],
- "tax_withholding_rate": 10,
- "single_threshold": 5000,
- "cumulative_threshold": 10000,
+ "from_date": from_date,
+ "to_date": to_date,
+ "tax_withholding_rate": rate,
+ "single_threshold": single_threshold,
+ "cumulative_threshold": cumulative_threshold,
}
],
- "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
+ "accounts": [{"company": "_Test Company", "account": account}],
}
).insert()
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index baeed03..b6eb3ed 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -211,7 +211,13 @@
else:
party_details.update(get_company_address(company))
- if doctype and doctype in ["Delivery Note", "Sales Invoice", "Sales Order", "Quotation"]:
+ if doctype and doctype in [
+ "Delivery Note",
+ "Sales Invoice",
+ "Sales Order",
+ "Quotation",
+ "POS Invoice",
+ ]:
if party_details.company_address:
party_details.update(
get_fetch_values(doctype, "company_address", party_details.company_address)
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
index 6cc86c3..3e11643 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
@@ -378,15 +378,14 @@
ret += [{}]
# add total row
- if ret is not []:
- if self.filters.type == "Revenue":
- total_row = frappe._dict({"name": "Total Deferred Income"})
- elif self.filters.type == "Expense":
- total_row = frappe._dict({"name": "Total Deferred Expense"})
+ if self.filters.type == "Revenue":
+ total_row = frappe._dict({"name": "Total Deferred Income"})
+ elif self.filters.type == "Expense":
+ total_row = frappe._dict({"name": "Total Deferred Expense"})
- for idx, period in enumerate(self.period_list, 0):
- total_row[period.key] = self.period_total[idx].total
- ret.append(total_row)
+ for idx, period in enumerate(self.period_list, 0):
+ total_row[period.key] = self.period_total[idx].total
+ ret.append(total_row)
return ret
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index fc23127..27b84c4 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -526,7 +526,7 @@
"options": "GL Entry",
"hidden": 1,
},
- {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 90},
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
{
"label": _("Account"),
"fieldname": "account",
@@ -538,13 +538,13 @@
"label": _("Debit ({0})").format(currency),
"fieldname": "debit",
"fieldtype": "Float",
- "width": 100,
+ "width": 130,
},
{
"label": _("Credit ({0})").format(currency),
"fieldname": "credit",
"fieldtype": "Float",
- "width": 100,
+ "width": 130,
},
{
"label": _("Balance ({0})").format(currency),
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js
index 615804e..e89d429 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.js
+++ b/erpnext/accounts/report/gross_profit/gross_profit.js
@@ -50,6 +50,20 @@
"fieldtype": "Link",
"options": "Sales Person"
},
+ {
+ "fieldname": "warehouse",
+ "label": __("Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "get_query": function () {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: [
+ ["Warehouse", "company", "=", company]
+ ]
+ };
+ },
+ },
],
"tree": true,
"name_field": "parent",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 130b715..e23265b 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -655,10 +655,35 @@
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
+ elif row.sales_order and row.so_detail:
+ incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
+ if incoming_amount:
+ return incoming_amount
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
- return 0.0
+ return flt(row.qty) * self.get_average_buying_rate(row, item_code)
+
+ def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
+ from frappe.query_builder.functions import Sum
+
+ delivery_note = frappe.qb.DocType("Delivery Note")
+ delivery_note_item = frappe.qb.DocType("Delivery Note Item")
+
+ query = (
+ frappe.qb.from_(delivery_note)
+ .inner_join(delivery_note_item)
+ .on(delivery_note.name == delivery_note_item.parent)
+ .select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
+ .where(delivery_note.docstatus == 1)
+ .where(delivery_note_item.item_code == item_code)
+ .where(delivery_note_item.against_sales_order == sales_order)
+ .where(delivery_note_item.so_detail == so_detail)
+ .groupby(delivery_note_item.item_code)
+ )
+
+ incoming_amount = query.run()
+ return flt(incoming_amount[0][0]) if incoming_amount else 0
def get_average_buying_rate(self, row, item_code):
args = row
@@ -750,6 +775,13 @@
if self.filters.get("item_code"):
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
+ if self.filters.get("warehouse"):
+ warehouse_details = frappe.db.get_value(
+ "Warehouse", self.filters.get("warehouse"), ["lft", "rgt"], as_dict=1
+ )
+ if warehouse_details:
+ conditions += f" and `tabSales Invoice Item`.warehouse in (select name from `tabWarehouse` wh where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)"
+
self.si_list = frappe.db.sql(
"""
select
@@ -760,7 +792,8 @@
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
- `tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail,
+ `tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
+ `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py
index fa11a41..21681be 100644
--- a/erpnext/accounts/report/gross_profit/test_gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py
@@ -302,3 +302,82 @@
columns, data = execute(filters=filters)
self.assertGreater(len(data), 0)
+
+ def test_order_connected_dn_and_inv(self):
+ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+
+ """
+ Test gp calculation when invoice and delivery note aren't directly connected.
+ SO -- INV
+ |
+ DN
+ """
+ se = make_stock_entry(
+ company=self.company,
+ item_code=self.item,
+ target=self.warehouse,
+ qty=3,
+ basic_rate=100,
+ do_not_submit=True,
+ )
+ item = se.items[0]
+ se.append(
+ "items",
+ {
+ "item_code": item.item_code,
+ "s_warehouse": item.s_warehouse,
+ "t_warehouse": item.t_warehouse,
+ "qty": 10,
+ "basic_rate": 200,
+ "conversion_factor": item.conversion_factor or 1.0,
+ "transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
+ "serial_no": item.serial_no,
+ "batch_no": item.batch_no,
+ "cost_center": item.cost_center,
+ "expense_account": item.expense_account,
+ },
+ )
+ se = se.save().submit()
+
+ so = make_sales_order(
+ customer=self.customer,
+ company=self.company,
+ warehouse=self.warehouse,
+ item=self.item,
+ qty=4,
+ do_not_save=False,
+ do_not_submit=False,
+ )
+
+ from erpnext.selling.doctype.sales_order.sales_order import (
+ make_delivery_note,
+ make_sales_invoice,
+ )
+
+ make_delivery_note(so.name).submit()
+ sinv = make_sales_invoice(so.name).submit()
+
+ filters = frappe._dict(
+ company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
+ )
+
+ columns, data = execute(filters=filters)
+ expected_entry = {
+ "parent_invoice": sinv.name,
+ "currency": "INR",
+ "sales_invoice": self.item,
+ "customer": self.customer,
+ "posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
+ "item_code": self.item,
+ "item_name": self.item,
+ "warehouse": "Stores - _GP",
+ "qty": 4.0,
+ "avg._selling_rate": 100.0,
+ "valuation_rate": 125.0,
+ "selling_amount": 400.0,
+ "buying_amount": 500.0,
+ "gross_profit": -100.0,
+ "gross_profit_%": -25.0,
+ }
+ gp_entry = [x for x in data if x.parent_invoice == sinv.name]
+ self.assertDictContainsSubset(expected_entry, gp_entry[0])
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index 9883890..bfe2a0f 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -4,6 +4,7 @@
import frappe
from frappe import _
+from frappe.utils import flt
def execute(filters=None):
@@ -65,6 +66,12 @@
else:
total_amount_credited += entry.credit
+ ## Check if ldc is applied and show rate as per ldc
+ actual_rate = (tds_deducted / total_amount_credited) * 100
+
+ if flt(actual_rate) < flt(rate):
+ rate = actual_rate
+
if tds_deducted:
row = {
"pan"
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index b8185c9..8f5b85d 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -135,6 +135,10 @@
}, __("Manage"));
}
+ if (frm.doc.depr_entry_posting_status === "Failed") {
+ frm.trigger("set_depr_posting_failure_alert");
+ }
+
frm.trigger("setup_chart");
}
@@ -145,6 +149,19 @@
}
},
+ set_depr_posting_failure_alert: function (frm) {
+ const alert = `
+ <div class="row">
+ <div class="col-xs-12 col-sm-6">
+ <span class="indicator whitespace-nowrap red">
+ <span>Failed to post depreciation entries</span>
+ </span>
+ </div>
+ </div>`;
+
+ frm.dashboard.set_headline_alert(alert);
+ },
+
toggle_reference_doc: function(frm) {
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
frm.set_df_property('purchase_invoice', 'read_only', 1);
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index 4bac303..8a64a95 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -68,6 +68,7 @@
"column_break_51",
"purchase_receipt_amount",
"default_finance_book",
+ "depr_entry_posting_status",
"amended_from"
],
"fields": [
@@ -473,6 +474,16 @@
"fieldtype": "Int",
"label": "Asset Quantity",
"read_only_depends_on": "eval:!doc.is_existing_asset"
+ },
+ {
+ "fieldname": "depr_entry_posting_status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Depreciation Entry Posting Status",
+ "no_copy": 1,
+ "options": "\nSuccessful\nFailed",
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 72,
@@ -487,7 +498,7 @@
{
"group": "Repair",
"link_doctype": "Asset Repair",
- "link_fieldname": "asset_name"
+ "link_fieldname": "asset"
},
{
"group": "Value",
@@ -500,7 +511,7 @@
"link_fieldname": "asset"
}
],
- "modified": "2022-11-25 12:47:19.689702",
+ "modified": "2023-01-17 00:25:30.387242",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 7686c34..17d4078 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -4,7 +4,18 @@
import frappe
from frappe import _
-from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, nowdate, today
+from frappe.utils import (
+ add_months,
+ cint,
+ flt,
+ get_last_day,
+ get_link_to_form,
+ getdate,
+ is_last_day_of_the_month,
+ nowdate,
+ today,
+)
+from frappe.utils.user import get_users_with_role
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
@@ -18,7 +29,7 @@
)
-def post_depreciation_entries(date=None, commit=True):
+def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled
if not cint(
frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")
@@ -27,13 +38,24 @@
if not date:
date = today()
+
+ failed_asset_names = []
+
for asset_name in get_depreciable_assets(date):
asset_doc = frappe.get_doc("Asset", asset_name)
- make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
-
- if commit:
+ try:
+ make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
frappe.db.commit()
+ except Exception as e:
+ frappe.db.rollback()
+ failed_asset_names.append(asset_name)
+
+ if failed_asset_names:
+ set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
+ notify_depr_entry_posting_error(failed_asset_names)
+
+ frappe.db.commit()
def get_depreciable_assets(date):
@@ -146,6 +168,8 @@
row.value_after_depreciation -= d.depreciation_amount
row.db_update()
+ frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
+
asset.set_status()
return asset_depr_schedule_doc
@@ -209,6 +233,42 @@
return credit_account, debit_account
+def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
+ for asset_name in failed_asset_names:
+ frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
+
+
+def notify_depr_entry_posting_error(failed_asset_names):
+ recipients = get_users_with_role("Accounts Manager")
+
+ if not recipients:
+ recipients = get_users_with_role("System Manager")
+
+ subject = _("Error while posting depreciation entries")
+
+ asset_links = get_comma_separated_asset_links(failed_asset_names)
+
+ message = (
+ _("Hi,")
+ + "<br>"
+ + _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
+ + "."
+ )
+
+ frappe.sendmail(recipients=recipients, subject=subject, message=message)
+
+
+def get_comma_separated_asset_links(asset_names):
+ asset_links = []
+
+ for asset_name in asset_names:
+ asset_links.append(get_link_to_form("Asset", asset_name))
+
+ asset_links = ", ".join(asset_links)
+
+ return asset_links
+
+
@frappe.whitelist()
def scrap_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
@@ -295,12 +355,12 @@
asset_doc, notes, date_of_return=date
)
- modify_depreciation_schedule_for_asset_repairs(asset_doc)
+ modify_depreciation_schedule_for_asset_repairs(asset_doc, notes)
asset_doc.save()
-def modify_depreciation_schedule_for_asset_repairs(asset):
+def modify_depreciation_schedule_for_asset_repairs(asset, notes):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
@@ -309,10 +369,6 @@
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
- notes = _("This schedule was created when Asset {0} went through Asset Repair {1}.").format(
- get_link_to_form(asset.doctype, asset.name),
- get_link_to_form(asset_repair.doctype, asset_repair.name),
- )
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, notes)
@@ -354,6 +410,9 @@
row.depreciation_start_date, schedule_idx * cint(row.frequency_of_depreciation)
)
+ if is_last_day_of_the_month(row.depreciation_start_date):
+ orginal_schedule_date = get_last_day(orginal_schedule_date)
+
if orginal_schedule_date == posting_date_of_disposal:
return True
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index d61ef8e..51a2b52 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -1549,6 +1549,7 @@
"asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1,
"asset_quantity": args.get("asset_quantity") or 1,
+ "depr_entry_posting_status": args.depr_entry_posting_status or "",
}
)
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index 62a3483..821accf 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -430,7 +430,7 @@
if asset.calculate_depreciation:
notes = _(
- "This schedule was created when Asset {0} was consumed when Asset Capitalization {1} was submitted."
+ "This schedule was created when Asset {0} was consumed through Asset Capitalization {1}."
).format(
get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.get("name"))
)
@@ -521,7 +521,7 @@
asset_doc.gross_purchase_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value
notes = _(
- "This schedule was created when target Asset {0} was updated when Asset Capitalization {1} was submitted."
+ "This schedule was created when target Asset {0} was updated through Asset Capitalization {1}."
).format(
get_link_to_form(asset_doc.doctype, asset_doc.name), get_link_to_form(self.doctype, self.name)
)
@@ -537,7 +537,7 @@
if asset.calculate_depreciation:
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
notes = _(
- "This schedule was created when Asset {0} was restored when Asset Capitalization {1} was cancelled."
+ "This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation."
).format(
get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.name)
)
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
index af09cda..898c482 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -159,7 +159,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-01-02 15:38:30.766779",
+ "modified": "2023-01-16 21:08:21.421260",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Depreciation Schedule",
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index b8cd115..9a05a74 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -56,8 +56,11 @@
):
self.modify_depreciation_schedule()
- notes = _("This schedule was created when Asset Repair {0} was submitted.").format(
- get_link_to_form(self.doctype, self.name)
+ notes = _(
+ "This schedule was created when Asset {0} was repaired through Asset Repair {1}."
+ ).format(
+ get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
+ get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
@@ -80,8 +83,9 @@
):
self.revert_depreciation_schedule_on_cancellation()
- notes = _("This schedule was created when Asset Repair {0} was cancelled.").format(
- get_link_to_form(self.doctype, self.name)
+ notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
+ get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
+ get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 262d552..6cfbe53 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -127,12 +127,20 @@
current_asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True
current_asset_depr_schedule_doc.cancel()
- notes = _(
- "This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}."
- ).format(
- get_link_to_form(asset.doctype, asset.name),
- get_link_to_form(self.get("doctype"), self.get("name")),
- )
+ if self.docstatus == 1:
+ notes = _(
+ "This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}."
+ ).format(
+ get_link_to_form(asset.doctype, asset.name),
+ get_link_to_form(self.get("doctype"), self.get("name")),
+ )
+ elif self.docstatus == 2:
+ notes = _(
+ "This schedule was created when Asset {0}'s Asset Value Adjustment {1} was cancelled."
+ ).format(
+ get_link_to_form(asset.doctype, asset.name),
+ get_link_to_form(self.get("doctype"), self.get("name")),
+ )
new_asset_depr_schedule_doc.notes = notes
new_asset_depr_schedule_doc.insert()
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index faffd11..d41069c 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -126,16 +126,18 @@
if not asset.calculate_depreciation:
return flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation)
- finance_book_filter = ["finance_book", "is", "not set"]
- if finance_book:
- finance_book_filter = ["finance_book", "=", finance_book]
-
- return frappe.db.get_value(
+ result = frappe.get_all(
doctype="Asset Finance Book",
- filters=[["parent", "=", asset.asset_id], finance_book_filter],
- fieldname="value_after_depreciation",
+ filters={
+ "parent": asset.asset_id,
+ "finance_book": finance_book or ("is", "not set"),
+ },
+ pluck="value_after_depreciation",
+ limit=1,
)
+ return result[0] if result else 0.0
+
def prepare_chart_data(data, filters):
labels_values_map = {}
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
index 646dba5..c673be8 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
@@ -15,17 +15,6 @@
create_customer()
create_item()
- def test_for_single_record(self):
- so_name = create_so()
- transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
- data = frappe.db.get_list(
- "Sales Invoice",
- filters={"posting_date": date.today(), "customer": "Bulk Customer"},
- fields=["*"],
- )
- if not data:
- self.fail("No Sales Invoice Created !")
-
def test_entry_in_log(self):
so_name = create_so()
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index e1dd679..29afc84 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -1221,6 +1221,7 @@
},
{
"default": "0",
+ "depends_on": "apply_tds",
"fieldname": "tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
@@ -1230,12 +1231,13 @@
"read_only": 1
},
{
+ "depends_on": "apply_tds",
"fieldname": "base_tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
"label": "Base Tax Withholding Net Total",
"no_copy": 1,
- "options": "currency",
+ "options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -1269,7 +1271,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2022-12-25 18:08:59.074182",
+ "modified": "2023-01-28 18:59:16.322824",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 572d9d3..f0360b2 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -889,6 +889,11 @@
self.assertEqual(po.status, "Completed")
self.assertEqual(mr.status, "Received")
+ def test_variant_item_po(self):
+ po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1)
+
+ self.assertRaises(frappe.ValidationError, po.save)
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
@@ -994,8 +999,8 @@
},
)
- po.set_missing_values()
if not args.do_not_save:
+ po.set_missing_values()
po.insert()
if not args.do_not_submit:
if po.is_subcontracted:
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index 019d45b..bd65b0c 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -29,6 +29,7 @@
"message_for_supplier",
"terms_section_break",
"incoterm",
+ "named_place",
"tc_name",
"terms",
"printing_settings",
@@ -278,13 +279,19 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
+ },
+ {
+ "depends_on": "incoterm",
+ "fieldname": "named_place",
+ "fieldtype": "Data",
+ "label": "Named Place"
}
],
"icon": "fa fa-shopping-cart",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-17 17:26:33.770993",
+ "modified": "2023-01-31 23:22:06.684694",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",
diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
index 47a66ad..9b53421 100644
--- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
@@ -15,60 +15,4 @@
class TestProcurementTracker(FrappeTestCase):
- def test_result_for_procurement_tracker(self):
- filters = {"company": "_Test Procurement Company", "cost_center": "Main - _TPC"}
- expected_data = self.generate_expected_data()
- report = execute(filters)
-
- length = len(report[1])
- self.assertEqual(expected_data, report[1][length - 1])
-
- def generate_expected_data(self):
- if not frappe.db.exists("Company", "_Test Procurement Company"):
- frappe.get_doc(
- dict(
- doctype="Company",
- company_name="_Test Procurement Company",
- abbr="_TPC",
- default_currency="INR",
- country="Pakistan",
- )
- ).insert()
- warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
- mr = make_material_request(
- company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC"
- )
- po = make_purchase_order(mr.name)
- po.supplier = "_Test Supplier"
- po.get("items")[0].cost_center = "Main - _TPC"
- po.submit()
- pr = make_purchase_receipt(po.name)
- pr.get("items")[0].cost_center = "Main - _TPC"
- pr.submit()
- date_obj = datetime.date(datetime.now())
-
- po.load_from_db()
-
- expected_data = {
- "material_request_date": date_obj,
- "cost_center": "Main - _TPC",
- "project": None,
- "requesting_site": "_Test Procurement Warehouse - _TPC",
- "requestor": "Administrator",
- "material_request_no": mr.name,
- "item_code": "_Test Item",
- "quantity": 10.0,
- "unit_of_measurement": "_Test UOM",
- "status": "To Bill",
- "purchase_order_date": date_obj,
- "purchase_order": po.name,
- "supplier": "_Test Supplier",
- "estimated_cost": 0.0,
- "actual_cost": 0.0,
- "purchase_order_amt": po.net_total,
- "purchase_order_amt_in_company_currency": po.base_net_total,
- "expected_delivery_date": date_obj,
- "actual_delivery_date": date_obj,
- }
-
- return expected_data
+ pass
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 8bd0998..9fcb769 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -305,7 +305,7 @@
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
# Used retrun against and supplier and is_retrun because there is an index added for it
- data = frappe.db.get_list(
+ data = frappe.get_all(
doctype,
fields=fields,
filters=[
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index cd1168d..8b4d28b 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -22,7 +22,7 @@
def onload(self):
super(SellingController, self).onload()
if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"):
- for item in self.get("items"):
+ for item in self.get("items") + (self.get("packed_items") or []):
item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True))
def validate(self):
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index d497297..dd2a670 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -58,7 +58,7 @@
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
],
["Cancelled", "eval:self.docstatus==2"],
- ["Closed", "eval:self.status=='Closed'"],
+ ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
["On Hold", "eval:self.status=='On Hold'"],
],
"Purchase Order": [
@@ -79,7 +79,7 @@
["Delivered", "eval:self.status=='Delivered'"],
["Cancelled", "eval:self.docstatus==2"],
["On Hold", "eval:self.status=='On Hold'"],
- ["Closed", "eval:self.status=='Closed'"],
+ ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Delivery Note": [
["Draft", None],
@@ -87,7 +87,7 @@
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
- ["Closed", "eval:self.status=='Closed'"],
+ ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Purchase Receipt": [
["Draft", None],
@@ -95,7 +95,7 @@
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
- ["Closed", "eval:self.status=='Closed'"],
+ ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Material Request": [
["Draft", None],
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index 8f8a086..077e7fa 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -312,7 +312,8 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Title",
- "print_hide": 1
+ "print_hide": 1,
+ "read_only": 1
},
{
"fieldname": "language",
@@ -514,11 +515,10 @@
"idx": 5,
"image_field": "image",
"links": [],
- "modified": "2022-10-13 12:42:04.277879",
+ "modified": "2023-01-24 18:20:05.044791",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",
- "name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index b0ff5d4..2a588d8 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -282,6 +282,7 @@
"contact_no": "phone_1",
"fax": "fax_1",
},
+ "field_no_map": ["disabled"],
}
},
target_doc,
@@ -390,7 +391,7 @@
{
"territory": lead.territory,
"customer_name": lead.company_name or lead.lead_name,
- "contact_display": " ".join(filter(None, [lead.salutation, lead.lead_name])),
+ "contact_display": " ".join(filter(None, [lead.lead_name])),
"contact_email": lead.email_id,
"contact_mobile": lead.mobile_no,
"contact_phone": lead.phone,
diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py
index 828c655..bbe04d5 100644
--- a/erpnext/e_commerce/doctype/website_item/test_website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py
@@ -174,7 +174,10 @@
# Website Item Portal Tests Begin
def test_website_item_breadcrumbs(self):
- "Check if breadcrumbs include homepage, product listing navigation page, parent item group(s) and item group."
+ """
+ Check if breadcrumbs include homepage, product listing navigation page,
+ parent item group(s) and item group
+ """
from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
item_code = "Test Breadcrumb Item"
@@ -197,7 +200,7 @@
breadcrumbs = get_parent_item_groups(item.item_group)
self.assertEqual(breadcrumbs[0]["name"], "Home")
- self.assertEqual(breadcrumbs[1]["name"], "Shop by Category")
+ self.assertEqual(breadcrumbs[1]["name"], "All Products")
self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 4dd8205..4304193 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -65,7 +65,21 @@
});
},
- onload_post_render(frm) {
+ validate: function(frm) {
+ if (frm.doc.fg_based_operating_cost && frm.doc.with_operations) {
+ frappe.throw({message: __("Please check either with operations or FG Based Operating Cost."), title: __("Mandatory")});
+ }
+ },
+
+ with_operations: function(frm) {
+ frm.set_df_property("fg_based_operating_cost", "hidden", frm.doc.with_operations ? 1 : 0);
+ },
+
+ fg_based_operating_cost: function(frm) {
+ frm.set_df_property("with_operations", "hidden", frm.doc.fg_based_operating_cost ? 1 : 0);
+ },
+
+ onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
@@ -532,18 +546,25 @@
};
erpnext.bom.calculate_op_cost = function(doc) {
- var op = doc.operations || [];
doc.operating_cost = 0.0;
doc.base_operating_cost = 0.0;
- for(var i=0;i<op.length;i++) {
- var operating_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2);
- var base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
- frappe.model.set_value('BOM Operation',op[i].name, "operating_cost", operating_cost);
- frappe.model.set_value('BOM Operation',op[i].name, "base_operating_cost", base_operating_cost);
+ if(doc.with_operations) {
+ doc.operations.forEach((item) => {
+ let operating_cost = flt(flt(item.hour_rate) * flt(item.time_in_mins) / 60, 2);
+ let base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
+ frappe.model.set_value('BOM Operation',item.name, {
+ "operating_cost": operating_cost,
+ "base_operating_cost": base_operating_cost
+ });
- doc.operating_cost += operating_cost;
- doc.base_operating_cost += base_operating_cost;
+ doc.operating_cost += operating_cost;
+ doc.base_operating_cost += base_operating_cost;
+ });
+ } else if(doc.fg_based_operating_cost) {
+ let total_operating_cost = doc.quantity * flt(doc.operating_cost_per_bom_quantity);
+ doc.operating_cost = total_operating_cost;
+ doc.base_operating_cost = flt(total_operating_cost * doc.conversion_rate, 2);
}
refresh_field(['operating_cost', 'base_operating_cost']);
};
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index c31b69f..c2b331f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -33,6 +33,9 @@
"column_break_23",
"transfer_material_against",
"routing",
+ "fg_based_operating_cost",
+ "fg_based_section_section",
+ "operating_cost_per_bom_quantity",
"operations_section",
"operations",
"materials_section",
@@ -575,7 +578,26 @@
{
"fieldname": "scrap_items_section",
"fieldtype": "Section Break",
+ "hide_border": 1,
"label": "Scrap Items"
+ },
+ {
+ "default": "0",
+ "fieldname": "fg_based_operating_cost",
+ "fieldtype": "Check",
+ "label": "FG based Operating Cost"
+ },
+ {
+ "depends_on": "fg_based_operating_cost",
+ "fieldname": "fg_based_section_section",
+ "fieldtype": "Section Break",
+ "label": "FG Based Operating Cost Section"
+ },
+ {
+ "depends_on": "fg_based_operating_cost",
+ "fieldname": "operating_cost_per_bom_quantity",
+ "fieldtype": "Currency",
+ "label": "Operating Cost Per BOM Quantity"
}
],
"icon": "fa fa-sitemap",
@@ -583,7 +605,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2023-01-03 18:42:27.732107",
+ "modified": "2023-01-10 07:47:08.652616",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 53af28d..8ab79e6 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -614,18 +614,26 @@
"""Update workstation rate and calculates totals"""
self.operating_cost = 0
self.base_operating_cost = 0
- for d in self.get("operations"):
- if d.workstation:
- self.update_rate_and_time(d, update_hour_rate)
+ if self.get("with_operations"):
+ for d in self.get("operations"):
+ if d.workstation:
+ self.update_rate_and_time(d, update_hour_rate)
- operating_cost = d.operating_cost
- base_operating_cost = d.base_operating_cost
- if d.set_cost_based_on_bom_qty:
- operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
- base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
+ operating_cost = d.operating_cost
+ base_operating_cost = d.base_operating_cost
+ if d.set_cost_based_on_bom_qty:
+ operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
+ base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
- self.operating_cost += flt(operating_cost)
- self.base_operating_cost += flt(base_operating_cost)
+ self.operating_cost += flt(operating_cost)
+ self.base_operating_cost += flt(base_operating_cost)
+
+ elif self.get("fg_based_operating_cost"):
+ total_operating_cost = flt(self.get("quantity")) * flt(
+ self.get("operating_cost_per_bom_quantity")
+ )
+ self.operating_cost = total_operating_cost
+ self.base_operating_cost = flt(total_operating_cost * self.conversion_rate, 2)
def update_rate_and_time(self, row, update_hour_rate=False):
if not row.hour_rate or update_hour_rate:
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 16f5c79..d60feb2 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -202,6 +202,33 @@
self.assertEqual(bom.items[0].rate, 20)
+ def test_bom_cost_with_fg_based_operating_cost(self):
+ bom = frappe.copy_doc(test_records[4])
+ bom.insert()
+
+ raw_material_cost = 0.0
+ op_cost = 0.0
+
+ op_cost = bom.quantity * bom.operating_cost_per_bom_quantity
+
+ for row in bom.items:
+ raw_material_cost += row.amount
+
+ base_raw_material_cost = raw_material_cost * flt(
+ bom.conversion_rate, bom.precision("conversion_rate")
+ )
+ base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
+
+ # test amounts in selected currency, almostEqual checks for 7 digits by default
+ self.assertAlmostEqual(bom.operating_cost, op_cost)
+ self.assertAlmostEqual(bom.raw_material_cost, raw_material_cost)
+ self.assertAlmostEqual(bom.total_cost, raw_material_cost + op_cost)
+
+ # test amounts in selected currency
+ self.assertAlmostEqual(bom.base_operating_cost, base_op_cost)
+ self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
+ self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
+
def test_subcontractor_sourced_item(self):
item_code = "_Test Subcontracted FG Item 1"
set_backflush_based_on("Material Transferred for Subcontract")
diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json
index 507d319..e9cbdfe 100644
--- a/erpnext/manufacturing/doctype/bom/test_records.json
+++ b/erpnext/manufacturing/doctype/bom/test_records.json
@@ -162,5 +162,31 @@
"item": "_Test Variant Item",
"quantity": 1.0,
"with_operations": 1
+ },
+ {
+ "items": [
+ {
+ "amount": 5000.0,
+ "doctype": "BOM Item",
+ "item_code": "_Test Item",
+ "parentfield": "items",
+ "qty": 2.0,
+ "rate": 3000.0,
+ "uom": "_Test UOM",
+ "stock_uom": "_Test UOM",
+ "source_warehouse": "_Test Warehouse - _TC",
+ "include_item_in_manufacturing": 1
+ }
+ ],
+ "docstatus": 1,
+ "doctype": "BOM",
+ "is_active": 1,
+ "is_default": 1,
+ "currency": "USD",
+ "item": "_Test Variant Item",
+ "quantity": 1.0,
+ "with_operations": 0,
+ "fg_based_operating_cost": 1,
+ "operating_cost_per_bom_quantity": 140
}
]
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js
index 832be23..67bd24d 100644
--- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js
@@ -13,38 +13,24 @@
reqd: 1
},
{
- fieldname: "fiscal_year",
- label: __("Fiscal Year"),
- fieldtype: "Link",
- options: "Fiscal Year",
- default: frappe.defaults.get_user_default("fiscal_year"),
- reqd: 1,
- on_change: function(query_report) {
- var fiscal_year = query_report.get_values().fiscal_year;
- if (!fiscal_year) {
- return;
- }
- frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
- var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
- frappe.query_report.set_filter_value({
- from_date: fy.year_start_date,
- to_date: fy.year_end_date
- });
- });
- }
+ label: __("Based On"),
+ fieldname:"based_on",
+ fieldtype: "Select",
+ options: "Creation Date\nPlanned Date\nActual Date",
+ default: "Creation Date"
},
{
label: __("From Posting Date"),
fieldname:"from_date",
fieldtype: "Date",
- default: frappe.defaults.get_user_default("year_start_date"),
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -3),
reqd: 1
},
{
label: __("To Posting Date"),
fieldname:"to_date",
fieldtype: "Date",
- default: frappe.defaults.get_user_default("year_end_date"),
+ default: frappe.datetime.get_today(),
reqd: 1,
},
{
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
index b69ad07..97f30ef 100644
--- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
@@ -31,6 +31,7 @@
"sales_order",
"production_item",
"qty",
+ "creation",
"produced_qty",
"planned_start_date",
"planned_end_date",
@@ -47,11 +48,17 @@
if filters.get(field):
query_filters[field] = filters.get(field)
- query_filters["planned_start_date"] = (">=", filters.get("from_date"))
- query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
+ if filters.get("based_on") == "Planned Date":
+ query_filters["planned_start_date"] = (">=", filters.get("from_date"))
+ query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
+ elif filters.get("based_on") == "Actual Date":
+ query_filters["actual_start_date"] = (">=", filters.get("from_date"))
+ query_filters["actual_end_date"] = ("<=", filters.get("to_date"))
+ else:
+ query_filters["creation"] = ("between", [filters.get("from_date"), filters.get("to_date")])
data = frappe.get_all(
- "Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc"
+ "Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc", debug=1
)
res = []
@@ -214,6 +221,12 @@
"width": 90,
},
{
+ "label": _("Created On"),
+ "fieldname": "creation",
+ "fieldtype": "Date",
+ "width": 150,
+ },
+ {
"label": _("Planned Start Date"),
"fieldname": "planned_start_date",
"fieldtype": "Date",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index cba1ecc..698ffac 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -194,7 +194,6 @@
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
erpnext.patches.v13_0.update_payment_terms_outstanding
-erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
erpnext.patches.v13_0.update_vehicle_no_reqd_condition
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
@@ -292,6 +291,7 @@
erpnext.patches.v14_0.delete_amazon_mws_doctype
erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr
erpnext.patches.v13_0.update_accounts_in_loan_docs
+erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v14_0.update_batch_valuation_flag
erpnext.patches.v14_0.delete_non_profit_doctypes
erpnext.patches.v13_0.add_cost_center_in_loans
@@ -324,3 +324,5 @@
erpnext.patches.v14_0.setup_clear_repost_logs
erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
erpnext.patches.v14_0.update_entry_type_for_journal_entry
+erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
+erpnext.patches.v14_0.set_pick_list_status
diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
index 75a5477..c0d7150 100644
--- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
+++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
@@ -7,6 +7,7 @@
def execute():
doctypes_to_reload = [
+ ("setup", "company"),
("stock", "repost_item_valuation"),
("stock", "stock_entry_detail"),
("stock", "purchase_receipt_item"),
diff --git a/erpnext/patches/v14_0/change_autoname_for_tax_withheld_vouchers.py b/erpnext/patches/v14_0/change_autoname_for_tax_withheld_vouchers.py
new file mode 100644
index 0000000..e20ba73
--- /dev/null
+++ b/erpnext/patches/v14_0/change_autoname_for_tax_withheld_vouchers.py
@@ -0,0 +1,12 @@
+import frappe
+
+
+def execute():
+ if (
+ frappe.db.sql(
+ """select data_type FROM information_schema.columns
+ where column_name = 'name' and table_name = 'tabTax Withheld Vouchers'"""
+ )[0][0]
+ == "bigint"
+ ):
+ frappe.db.change_column_type("Tax Withheld Vouchers", "name", "varchar(140)")
diff --git a/erpnext/patches/v14_0/migrate_cost_center_allocations.py b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
index 3bd2693..48f4e6d 100644
--- a/erpnext/patches/v14_0/migrate_cost_center_allocations.py
+++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
@@ -18,9 +18,11 @@
cca = frappe.new_doc("Cost Center Allocation")
cca.main_cost_center = main_cc
cca.valid_from = today()
+ cca._skip_from_date_validation = True
for child_cc, percentage in allocations.items():
cca.append("allocation_percentages", ({"cost_center": child_cc, "percentage": percentage}))
+
cca.save()
cca.submit()
diff --git a/erpnext/patches/v14_0/set_pick_list_status.py b/erpnext/patches/v14_0/set_pick_list_status.py
new file mode 100644
index 0000000..eea5745
--- /dev/null
+++ b/erpnext/patches/v14_0/set_pick_list_status.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: MIT. See LICENSE
+
+
+import frappe
+from pypika.terms import ExistsCriterion
+
+
+def execute():
+ pl = frappe.qb.DocType("Pick List")
+ se = frappe.qb.DocType("Stock Entry")
+ dn = frappe.qb.DocType("Delivery Note")
+
+ (
+ frappe.qb.update(pl).set(
+ pl.status,
+ (
+ frappe.qb.terms.Case()
+ .when(pl.docstatus == 0, "Draft")
+ .when(pl.docstatus == 2, "Cancelled")
+ .else_("Completed")
+ ),
+ )
+ ).run()
+
+ (
+ frappe.qb.update(pl)
+ .set(pl.status, "Open")
+ .where(
+ (
+ ExistsCriterion(
+ frappe.qb.from_(se).select(se.name).where((se.docstatus == 1) & (se.pick_list == pl.name))
+ )
+ | ExistsCriterion(
+ frappe.qb.from_(dn).select(dn.name).where((dn.docstatus == 1) & (dn.pick_list == pl.name))
+ )
+ ).negate()
+ & (pl.docstatus == 1)
+ )
+ ).run()
diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
index 27c8fe4..3df56e6 100644
--- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py
+++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
@@ -10,62 +10,6 @@
class TestHomepageSection(unittest.TestCase):
- def test_homepage_section_card(self):
- try:
- frappe.get_doc(
- {
- "doctype": "Homepage Section",
- "name": "Card Section",
- "section_based_on": "Cards",
- "section_cards": [
- {
- "title": "Card 1",
- "subtitle": "Subtitle 1",
- "content": "This is test card 1",
- "route": "/card-1",
- },
- {
- "title": "Card 2",
- "subtitle": "Subtitle 2",
- "content": "This is test card 2",
- "image": "test.jpg",
- },
- ],
- "no_of_columns": 3,
- }
- ).insert(ignore_if_duplicate=True)
- except frappe.DuplicateEntryError:
- pass
-
- set_request(method="GET", path="home")
- response = get_response()
-
- self.assertEqual(response.status_code, 200)
-
- html = frappe.safe_decode(response.get_data())
-
- soup = BeautifulSoup(html, "html.parser")
- sections = soup.find("main").find_all("section")
- self.assertEqual(len(sections), 3)
-
- homepage_section = sections[2]
- self.assertEqual(homepage_section.h3.text, "Card Section")
-
- cards = homepage_section.find_all(class_="card")
-
- self.assertEqual(len(cards), 2)
- self.assertEqual(cards[0].h5.text, "Card 1")
- self.assertEqual(cards[0].a["href"], "/card-1")
- self.assertEqual(cards[1].p.text, "Subtitle 2")
-
- img = cards[1].find(class_="card-img-top")
-
- self.assertEqual(img["src"], "test.jpg")
- self.assertEqual(img["loading"], "lazy")
-
- # cleanup
- frappe.db.rollback()
-
def test_homepage_section_custom_html(self):
frappe.get_doc(
{
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 2dde542..ce3ae4f 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -80,7 +80,7 @@
if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):
frappe.throw(
_(
- "Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled."
+ "Cannot complete task {0} as its dependant task {1} are not completed / cancelled."
).format(frappe.bold(self.name), frappe.bold(d.task))
)
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 1179364..f3bd09a 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -387,6 +387,9 @@
"timesheets",
{
"time_sheet": timesheet.name,
+ "project_name": time_log.project_name,
+ "from_time": time_log.from_time,
+ "to_time": time_log.to_time,
"billing_hours": time_log.billing_hours,
"billing_amount": time_log.billing_amount,
"timesheet_detail": time_log.name,
diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
index 51664f8..911343d 100644
--- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
@@ -1,7 +1,7 @@
frappe.provide("erpnext.accounts.bank_reconciliation");
erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
- constructor(company, bank_account) {
+ constructor(company, bank_account, bank_statement_from_date, bank_statement_to_date, filter_by_reference_date, from_reference_date, to_reference_date) {
this.bank_account = bank_account;
this.company = company;
this.make_dialog();
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 1f8a5e3..2ce0c7e 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -122,24 +122,16 @@
calculate_item_values() {
var me = this;
if (!this.discount_amount_applied) {
- $.each(this.frm.doc["items"] || [], function(i, item) {
+ for (const item of this.frm.doc.items || []) {
frappe.model.round_floats_in(item);
item.net_rate = item.rate;
-
- if ((!item.qty) && me.frm.doc.is_return) {
- item.amount = flt(item.rate * -1, precision("amount", item));
- } else if ((!item.qty) && me.frm.doc.is_debit_note) {
- item.amount = flt(item.rate, precision("amount", item));
- } else {
- item.amount = flt(item.rate * item.qty, precision("amount", item));
- }
-
- item.net_amount = item.amount;
+ item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
+ item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
item.item_tax_amount = 0.0;
item.total_weight = flt(item.weight_per_unit * item.stock_qty);
me.set_in_company_currency(item, ["price_list_rate", "rate", "amount", "net_rate", "net_amount"]);
- });
+ }
}
}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 5c1c6d1..09f2c5d 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1473,6 +1473,7 @@
"parenttype": d.parenttype,
"parent": d.parent,
"pricing_rules": d.pricing_rules,
+ "is_free_item": d.is_free_item,
"warehouse": d.warehouse,
"serial_no": d.serial_no,
"batch_no": d.batch_no,
@@ -1690,6 +1691,10 @@
var me = this;
var valid = true;
+ if (frappe.flags.ignore_company_party_validation) {
+ return valid;
+ }
+
$.each(["company", "customer"], function(i, fieldname) {
if(frappe.meta.has_field(me.frm.doc.doctype, fieldname) && !["Purchase Order","Purchase Invoice"].includes(me.frm.doc.doctype)) {
if (!me.frm.doc[fieldname]) {
diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js
index 9288f51..a913844 100644
--- a/erpnext/public/js/setup_wizard.js
+++ b/erpnext/public/js/setup_wizard.js
@@ -13,20 +13,12 @@
erpnext.setup.slides_settings = [
{
- // Brand
- name: 'brand',
- icon: "fa fa-bookmark",
- title: __("The Brand"),
- // help: __('Upload your letter head and logo. (you can edit them later).'),
+ // Organization
+ name: 'organization',
+ title: __("Setup your organization"),
+ icon: "fa fa-building",
fields: [
{
- fieldtype: "Attach Image", fieldname: "attach_logo",
- label: __("Attach Logo"),
- description: __("100px by 100px"),
- is_private: 0,
- align: 'center'
- },
- {
fieldname: 'company_name',
label: __('Company Name'),
fieldtype: 'Data',
@@ -35,54 +27,9 @@
{
fieldname: 'company_abbr',
label: __('Company Abbreviation'),
- fieldtype: 'Data'
- }
- ],
- onload: function(slide) {
- this.bind_events(slide);
- },
- bind_events: function (slide) {
- slide.get_input("company_name").on("change", function () {
- var parts = slide.get_input("company_name").val().split(" ");
- var abbr = $.map(parts, function (p) { return p ? p.substr(0, 1) : null }).join("");
- slide.get_field("company_abbr").set_value(abbr.slice(0, 10).toUpperCase());
- }).val(frappe.boot.sysdefaults.company_name || "").trigger("change");
-
- slide.get_input("company_abbr").on("change", function () {
- if (slide.get_input("company_abbr").val().length > 10) {
- frappe.msgprint(__("Company Abbreviation cannot have more than 5 characters"));
- slide.get_field("company_abbr").set_value("");
- }
- });
- },
- validate: function() {
- if ((this.values.company_name || "").toLowerCase() == "company") {
- frappe.msgprint(__("Company Name cannot be Company"));
- return false;
- }
- if (!this.values.company_abbr) {
- return false;
- }
- if (this.values.company_abbr.length > 10) {
- return false;
- }
- return true;
- }
- },
- {
- // Organisation
- name: 'organisation',
- title: __("Your Organization"),
- icon: "fa fa-building",
- fields: [
- {
- fieldname: 'company_tagline',
- label: __('What does it do?'),
fieldtype: 'Data',
- placeholder: __('e.g. "Build tools for builders"'),
- reqd: 1
+ hidden: 1
},
- { fieldname: 'bank_account', label: __('Bank Name'), fieldtype: 'Data', reqd: 1 },
{
fieldname: 'chart_of_accounts', label: __('Chart of Accounts'),
options: "", fieldtype: 'Select'
@@ -94,40 +41,24 @@
],
onload: function (slide) {
- this.load_chart_of_accounts(slide);
this.bind_events(slide);
+ this.load_chart_of_accounts(slide);
this.set_fy_dates(slide);
},
-
validate: function () {
- let me = this;
- let exist;
-
if (!this.validate_fy_dates()) {
return false;
}
- // Validate bank name
- if(me.values.bank_account) {
- frappe.call({
- async: false,
- method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.validate_bank_account",
- args: {
- "coa": me.values.chart_of_accounts,
- "bank_account": me.values.bank_account
- },
- callback: function (r) {
- if(r.message){
- exist = r.message;
- me.get_field("bank_account").set_value("");
- let message = __('Account {0} already exists. Please enter a different name for your bank account.',
- [me.values.bank_account]
- );
- frappe.msgprint(message);
- }
- }
- });
- return !exist; // Return False if exist = true
+ if ((this.values.company_name || "").toLowerCase() == "company") {
+ frappe.msgprint(__("Company Name cannot be Company"));
+ return false;
+ }
+ if (!this.values.company_abbr) {
+ return false;
+ }
+ if (this.values.company_abbr.length > 10) {
+ return false;
}
return true;
@@ -151,15 +82,15 @@
var country = frappe.wizard.values.country;
if (country) {
- var fy = erpnext.setup.fiscal_years[country];
- var current_year = moment(new Date()).year();
- var next_year = current_year + 1;
+ let fy = erpnext.setup.fiscal_years[country];
+ let current_year = moment(new Date()).year();
+ let next_year = current_year + 1;
if (!fy) {
fy = ["01-01", "12-31"];
next_year = current_year;
}
- var year_start_date = current_year + "-" + fy[0];
+ let year_start_date = current_year + "-" + fy[0];
if (year_start_date > frappe.datetime.get_today()) {
next_year = current_year;
current_year -= 1;
@@ -171,7 +102,7 @@
load_chart_of_accounts: function (slide) {
- var country = frappe.wizard.values.country;
+ let country = frappe.wizard.values.country;
if (country) {
frappe.call({
@@ -202,12 +133,25 @@
me.charts_modal(slide, chart_template);
});
+
+ slide.get_input("company_name").on("change", function () {
+ let parts = slide.get_input("company_name").val().split(" ");
+ let abbr = $.map(parts, function (p) { return p ? p.substr(0, 1) : null }).join("");
+ slide.get_field("company_abbr").set_value(abbr.slice(0, 10).toUpperCase());
+ }).val(frappe.boot.sysdefaults.company_name || "").trigger("change");
+
+ slide.get_input("company_abbr").on("change", function () {
+ if (slide.get_input("company_abbr").val().length > 10) {
+ frappe.msgprint(__("Company Abbreviation cannot have more than 5 characters"));
+ slide.get_field("company_abbr").set_value("");
+ }
+ });
},
charts_modal: function(slide, chart_template) {
let parent = __('All Accounts');
- var dialog = new frappe.ui.Dialog({
+ let dialog = new frappe.ui.Dialog({
title: chart_template,
fields: [
{'fieldname': 'expand_all', 'label': __('Expand All'), 'fieldtype': 'Button',
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index d37b7bb..51dcd64 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -491,7 +491,20 @@
const child_meta = frappe.get_meta(`${frm.doc.doctype} Item`);
const get_precision = (fieldname) => child_meta.fields.find(f => f.fieldname == fieldname).precision;
- this.data = [];
+ this.data = frm.doc[opts.child_docname].map((d) => {
+ return {
+ "docname": d.name,
+ "name": d.name,
+ "item_code": d.item_code,
+ "delivery_date": d.delivery_date,
+ "schedule_date": d.schedule_date,
+ "conversion_factor": d.conversion_factor,
+ "qty": d.qty,
+ "rate": d.rate,
+ "uom": d.uom
+ }
+ });
+
const fields = [{
fieldtype:'Data',
fieldname:"docname",
@@ -588,7 +601,7 @@
})
}
- const dialog = new frappe.ui.Dialog({
+ new frappe.ui.Dialog({
title: __("Update Items"),
fields: [
{
@@ -624,24 +637,7 @@
refresh_field("items");
},
primary_action_label: __('Update')
- });
-
- frm.doc[opts.child_docname].forEach(d => {
- dialog.fields_dict.trans_items.df.data.push({
- "docname": d.name,
- "name": d.name,
- "item_code": d.item_code,
- "delivery_date": d.delivery_date,
- "schedule_date": d.schedule_date,
- "conversion_factor": d.conversion_factor,
- "qty": d.qty,
- "rate": d.rate,
- "uom": d.uom
- });
- this.data = dialog.fields_dict.trans_items.df.data;
- dialog.fields_dict.trans_items.grid.refresh();
- })
- dialog.show();
+ }).show();
}
erpnext.utils.map_current_doc = function(opts) {
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index accf5f2..ca6a51a 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -26,7 +26,7 @@
from erpnext.selling.doctype.customer.customer import check_credit_limit
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.doctype.item.item import get_item_defaults
-from erpnext.stock.get_item_details import get_default_bom
+from erpnext.stock.get_item_details import get_default_bom, get_price_list_rate
from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
@@ -590,6 +590,23 @@
target.qty = qty - requested_item_qty.get(source.name, 0)
target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
+ args = target.as_dict().copy()
+ args.update(
+ {
+ "company": source_parent.get("company"),
+ "price_list": frappe.db.get_single_value("Buying Settings", "buying_price_list"),
+ "currency": source_parent.get("currency"),
+ "conversion_rate": source_parent.get("conversion_rate"),
+ }
+ )
+
+ target.rate = flt(
+ get_price_list_rate(args=args, item_doc=frappe.get_cached_doc("Item", target.item_code)).get(
+ "price_list_rate"
+ )
+ )
+ target.amount = target.qty * target.rate
+
doc = get_mapped_doc(
"Sales Order",
source_name,
diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
index 5c4b578..cbc40bb 100644
--- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
+++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
@@ -14,7 +14,6 @@
},
"internal_links": {
"Quotation": ["items", "prevdoc_docname"],
- "Material Request": ["items", "material_request"],
},
"transactions": [
{
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index e777f52..d4d7c58 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -552,6 +552,42 @@
workflow.is_active = 0
workflow.save()
+ def test_bin_details_of_packed_item(self):
+ # test Update Items with product bundle
+ if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
+ bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
+ bundle_item.append(
+ "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
+ )
+ bundle_item.save(ignore_permissions=True)
+
+ make_item("_Packed Item New 1", {"is_stock_item": 1})
+ make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
+
+ so = make_sales_order(
+ item_code="_Test Product Bundle Item New",
+ warehouse="_Test Warehouse - _TC",
+ transaction_date=add_days(nowdate(), -1),
+ do_not_submit=1,
+ )
+
+ make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
+
+ bin_details = frappe.db.get_value(
+ "Bin",
+ {"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
+ ["actual_qty", "projected_qty", "ordered_qty"],
+ as_dict=1,
+ )
+
+ so.transaction_date = nowdate()
+ so.save()
+
+ packed_item = so.packed_items[0]
+ self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
+ self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
+ self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
+
def test_update_child_product_bundle(self):
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Product Bundle Item"):
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 999ddc2..158ac1d 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -17,45 +17,79 @@
def search_by_term(search_term, warehouse, price_list):
result = search_for_serial_or_batch_or_barcode_number(search_term) or {}
- item_code = result.get("item_code") or search_term
- serial_no = result.get("serial_no") or ""
- batch_no = result.get("batch_no") or ""
- barcode = result.get("barcode") or ""
+ item_code = result.get("item_code", search_term)
+ serial_no = result.get("serial_no", "")
+ batch_no = result.get("batch_no", "")
+ barcode = result.get("barcode", "")
- if result:
- item_info = frappe.db.get_value(
- "Item",
- item_code,
- [
- "name as item_code",
- "item_name",
- "description",
- "stock_uom",
- "image as item_image",
- "is_stock_item",
- ],
- as_dict=1,
- )
+ if not result:
+ return
- item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
- price_list_rate, currency = frappe.db.get_value(
- "Item Price",
- {"price_list": price_list, "item_code": item_code},
- ["price_list_rate", "currency"],
- ) or [None, None]
+ item_doc = frappe.get_doc("Item", item_code)
- item_info.update(
+ if not item_doc:
+ return
+
+ item = {
+ "barcode": barcode,
+ "batch_no": batch_no,
+ "description": item_doc.description,
+ "is_stock_item": item_doc.is_stock_item,
+ "item_code": item_doc.name,
+ "item_image": item_doc.image,
+ "item_name": item_doc.item_name,
+ "serial_no": serial_no,
+ "stock_uom": item_doc.stock_uom,
+ "uom": item_doc.stock_uom,
+ }
+
+ if barcode:
+ barcode_info = next(filter(lambda x: x.barcode == barcode, item_doc.get("barcodes", [])), None)
+ if barcode_info and barcode_info.uom:
+ uom = next(filter(lambda x: x.uom == barcode_info.uom, item_doc.uoms), {})
+ item.update(
+ {
+ "uom": barcode_info.uom,
+ "conversion_factor": uom.get("conversion_factor", 1),
+ }
+ )
+
+ item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
+ item_stock_qty = item_stock_qty // item.get("conversion_factor")
+ item.update({"actual_qty": item_stock_qty})
+
+ price = frappe.get_list(
+ doctype="Item Price",
+ filters={
+ "price_list": price_list,
+ "item_code": item_code,
+ },
+ fields=["uom", "stock_uom", "currency", "price_list_rate"],
+ )
+
+ def __sort(p):
+ p_uom = p.get("uom")
+
+ if p_uom == item.get("uom"):
+ return 0
+ elif p_uom == item.get("stock_uom"):
+ return 1
+ else:
+ return 2
+
+ # sort by fallback preference. always pick exact uom match if available
+ price = sorted(price, key=__sort)
+
+ if len(price) > 0:
+ p = price.pop(0)
+ item.update(
{
- "serial_no": serial_no,
- "batch_no": batch_no,
- "barcode": barcode,
- "price_list_rate": price_list_rate,
- "currency": currency,
- "actual_qty": item_stock_qty,
+ "currency": p.get("currency"),
+ "price_list_rate": p.get("price_list_rate"),
}
)
- return {"items": [item_info]}
+ return {"items": [item]}
@frappe.whitelist()
@@ -121,33 +155,43 @@
as_dict=1,
)
- if items_data:
- items = [d.item_code for d in items_data]
- item_prices_data = frappe.get_all(
+ # return (empty) list if there are no results
+ if not items_data:
+ return result
+
+ for item in items_data:
+ uoms = frappe.get_doc("Item", item.item_code).get("uoms", [])
+
+ item.actual_qty, _ = get_stock_availability(item.item_code, warehouse)
+ item.uom = item.stock_uom
+
+ item_price = frappe.get_all(
"Item Price",
- fields=["item_code", "price_list_rate", "currency"],
- filters={"price_list": price_list, "item_code": ["in", items]},
+ fields=["price_list_rate", "currency", "uom"],
+ filters={
+ "price_list": price_list,
+ "item_code": item.item_code,
+ "selling": True,
+ },
)
- item_prices = {}
- for d in item_prices_data:
- item_prices[d.item_code] = d
+ if not item_price:
+ result.append(item)
- for item in items_data:
- item_code = item.item_code
- item_price = item_prices.get(item_code) or {}
- item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
+ for price in item_price:
+ uom = next(filter(lambda x: x.uom == price.uom, uoms), {})
- row = {}
- row.update(item)
- row.update(
+ if price.uom != item.stock_uom and uom and uom.conversion_factor:
+ item.actual_qty = item.actual_qty // uom.conversion_factor
+
+ result.append(
{
- "price_list_rate": item_price.get("price_list_rate"),
- "currency": item_price.get("currency"),
- "actual_qty": item_stock_qty,
+ **item,
+ "price_list_rate": price.get("price_list_rate"),
+ "currency": price.get("currency"),
+ "uom": price.uom or item.uom,
}
)
- result.append(row)
return {"items": result}
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 595b919..c442774 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -542,12 +542,12 @@
if (!this.frm.doc.customer)
return this.raise_customer_selection_alert();
- const { item_code, batch_no, serial_no, rate } = item;
+ const { item_code, batch_no, serial_no, rate, uom } = item;
if (!item_code)
return;
- const new_item = { item_code, batch_no, rate, [field]: value };
+ const new_item = { item_code, batch_no, rate, uom, [field]: value };
if (serial_no) {
await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
@@ -649,6 +649,7 @@
const is_stock_item = resp[1];
frappe.dom.unfreeze();
+ const bold_uom = item_row.stock_uom.bold();
const bold_item_code = item_row.item_code.bold();
const bold_warehouse = warehouse.bold();
const bold_available_qty = available_qty.toString().bold()
@@ -664,7 +665,7 @@
}
} else if (is_stock_item && available_qty < qty_needed) {
frappe.throw({
- message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
+ message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2} {3}.', [bold_item_code, bold_warehouse, bold_available_qty, bold_uom]),
indicator: 'orange'
});
frappe.utils.play_sound("error");
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index e7dd211..12cc629 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -609,7 +609,7 @@
if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) {
return `
<div class="item-qty-rate">
- <div class="item-qty"><span>${item_data.qty || 0}</span></div>
+ <div class="item-qty"><span>${item_data.qty || 0} ${item_data.uom}</span></div>
<div class="item-rate-amount">
<div class="item-rate">${format_currency(item_data.amount, currency)}</div>
<div class="item-amount">${format_currency(item_data.rate, currency)}</div>
@@ -618,7 +618,7 @@
} else {
return `
<div class="item-qty-rate">
- <div class="item-qty"><span>${item_data.qty || 0}</span></div>
+ <div class="item-qty"><span>${item_data.qty || 0} ${item_data.uom}</span></div>
<div class="item-rate-amount">
<div class="item-rate">${format_currency(item_data.rate, currency)}</div>
</div>
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index b5eb048..ec67bdf 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -78,7 +78,7 @@
get_item_html(item) {
const me = this;
// eslint-disable-next-line no-unused-vars
- const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item;
+ const { item_image, serial_no, batch_no, barcode, actual_qty, uom, price_list_rate } = item;
const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0;
let indicator_color;
let qty_to_display = actual_qty;
@@ -118,7 +118,7 @@
return (
`<div class="item-wrapper"
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
- data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
+ data-batch-no="${escape(batch_no)}" data-uom="${escape(uom)}"
data-rate="${escape(price_list_rate || 0)}"
title="${item.item_name}">
@@ -128,7 +128,7 @@
<div class="item-name">
${frappe.ellipsis(item.item_name, 18)}
</div>
- <div class="item-rate">${format_currency(price_list_rate, item.currency, precision) || 0}</div>
+ <div class="item-rate">${format_currency(price_list_rate, item.currency, precision) || 0} / ${uom}</div>
</div>
</div>`
);
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index 40165c3..be75bd6 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -94,7 +94,7 @@
get_item_html(doc, item_data) {
return `<div class="item-row-wrapper">
<div class="item-name">${item_data.item_name}</div>
- <div class="item-qty">${item_data.qty || 0}</div>
+ <div class="item-qty">${item_data.qty || 0} ${item_data.uom}</div>
<div class="item-rate-disc">${get_rate_discount_html()}</div>
</div>`;
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index e10df2a..44c4d54 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -41,8 +41,20 @@
{"label": _("Description"), "fieldtype": "Data", "fieldname": "description", "width": 150},
{"label": _("Quantity"), "fieldtype": "Float", "fieldname": "quantity", "width": 150},
{"label": _("UOM"), "fieldtype": "Link", "fieldname": "uom", "options": "UOM", "width": 100},
- {"label": _("Rate"), "fieldname": "rate", "options": "Currency", "width": 120},
- {"label": _("Amount"), "fieldname": "amount", "options": "Currency", "width": 120},
+ {
+ "label": _("Rate"),
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
+ "label": _("Amount"),
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
{
"label": _("Sales Order"),
"fieldtype": "Link",
@@ -93,8 +105,9 @@
},
{
"label": _("Billed Amount"),
- "fieldtype": "currency",
+ "fieldtype": "Currency",
"fieldname": "billed_amount",
+ "options": "currency",
"width": 120,
},
{
@@ -104,6 +117,13 @@
"options": "Company",
"width": 100,
},
+ {
+ "label": _("Currency"),
+ "fieldtype": "Link",
+ "fieldname": "currency",
+ "options": "Currency",
+ "hidden": 1,
+ },
]
@@ -141,31 +161,12 @@
"billed_amount": flt(record.get("billed_amt")),
"company": record.get("company"),
}
+ row["currency"] = frappe.get_cached_value("Company", row["company"], "default_currency")
data.append(row)
return data
-def get_conditions(filters):
- conditions = ""
- if filters.get("item_group"):
- conditions += "AND so_item.item_group = %s" % frappe.db.escape(filters.item_group)
-
- if filters.get("from_date"):
- conditions += "AND so.transaction_date >= '%s'" % filters.from_date
-
- if filters.get("to_date"):
- conditions += "AND so.transaction_date <= '%s'" % filters.to_date
-
- if filters.get("item_code"):
- conditions += "AND so_item.item_code = %s" % frappe.db.escape(filters.item_code)
-
- if filters.get("customer"):
- conditions += "AND so.customer = %s" % frappe.db.escape(filters.customer)
-
- return conditions
-
-
def get_customer_details():
details = frappe.get_all("Customer", fields=["name", "customer_name", "customer_group"])
customer_details = {}
@@ -187,29 +188,50 @@
def get_sales_order_details(company_list, filters):
- conditions = get_conditions(filters)
+ db_so = frappe.qb.DocType("Sales Order")
+ db_so_item = frappe.qb.DocType("Sales Order Item")
- return frappe.db.sql(
- """
- SELECT
- so_item.item_code, so_item.description, so_item.qty,
- so_item.uom, so_item.base_rate, so_item.base_amount,
- so.name, so.transaction_date, so.customer,so.territory,
- so.project, so_item.delivered_qty,
- so_item.billed_amt, so.company
- FROM
- `tabSales Order` so, `tabSales Order Item` so_item
- WHERE
- so.name = so_item.parent
- AND so.company in ({0})
- AND so.docstatus = 1 {1}
- """.format(
- ",".join(["%s"] * len(company_list)), conditions
- ),
- tuple(company_list),
- as_dict=1,
+ query = (
+ frappe.qb.from_(db_so)
+ .inner_join(db_so_item)
+ .on(db_so_item.parent == db_so.name)
+ .select(
+ db_so.name,
+ db_so.customer,
+ db_so.transaction_date,
+ db_so.territory,
+ db_so.project,
+ db_so.company,
+ db_so_item.item_code,
+ db_so_item.description,
+ db_so_item.qty,
+ db_so_item.uom,
+ db_so_item.base_rate,
+ db_so_item.base_amount,
+ db_so_item.delivered_qty,
+ (db_so_item.billed_amt * db_so.conversion_rate).as_("billed_amt"),
+ )
+ .where(db_so.docstatus == 1)
+ .where(db_so.company.isin(tuple(company_list)))
)
+ if filters.get("item_group"):
+ query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_group))
+
+ if filters.get("from_date"):
+ query = query.where(db_so.transaction_date >= filters.from_date)
+
+ if filters.get("to_date"):
+ query = query.where(db_so.transaction_date <= filters.to_date)
+
+ if filters.get("item_code"):
+ query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_code))
+
+ if filters.get("customer"):
+ query = query.where(db_so.customer == filters.customer)
+
+ return query.run(as_dict=1)
+
def get_chart_data(data):
item_wise_sales_map = {}
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 95bbf84..2fdfcf6 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -148,12 +148,12 @@
def get_parent_item_groups(item_group_name, from_item=False):
- base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"}
+ base_nav_page = {"name": _("All Products"), "route": "/all-products"}
if from_item and frappe.request.environ.get("HTTP_REFERER"):
# base page after 'Home' will vary on Item page
last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
- if last_page and last_page in ("shop-by-category", "all-products"):
+ if last_page and last_page == "shop-by-category":
base_nav_page_title = " ".join(last_page.split("-")).title()
base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}
diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py
index aadc989..ace5cca 100644
--- a/erpnext/setup/setup_wizard/operations/company_setup.py
+++ b/erpnext/setup/setup_wizard/operations/company_setup.py
@@ -4,7 +4,6 @@
import frappe
from frappe import _
from frappe.utils import cstr, getdate
-from .default_website import website_maker
def create_fiscal_year_and_company(args):
@@ -48,83 +47,6 @@
).insert()
-def create_email_digest():
- from frappe.utils.user import get_system_managers
-
- system_managers = get_system_managers(only_name=True)
-
- if not system_managers:
- return
-
- recipients = []
- for d in system_managers:
- recipients.append({"recipient": d})
-
- companies = frappe.db.sql_list("select name FROM `tabCompany`")
- for company in companies:
- if not frappe.db.exists("Email Digest", "Default Weekly Digest - " + company):
- edigest = frappe.get_doc(
- {
- "doctype": "Email Digest",
- "name": "Default Weekly Digest - " + company,
- "company": company,
- "frequency": "Weekly",
- "recipients": recipients,
- }
- )
-
- for df in edigest.meta.get("fields", {"fieldtype": "Check"}):
- if df.fieldname != "scheduler_errors":
- edigest.set(df.fieldname, 1)
-
- edigest.insert()
-
- # scheduler errors digest
- if companies:
- edigest = frappe.new_doc("Email Digest")
- edigest.update(
- {
- "name": "Scheduler Errors",
- "company": companies[0],
- "frequency": "Daily",
- "recipients": recipients,
- "scheduler_errors": 1,
- "enabled": 1,
- }
- )
- edigest.insert()
-
-
-def create_logo(args):
- if args.get("attach_logo"):
- attach_logo = args.get("attach_logo").split(",")
- if len(attach_logo) == 3:
- filename, filetype, content = attach_logo
- _file = frappe.get_doc(
- {
- "doctype": "File",
- "file_name": filename,
- "attached_to_doctype": "Website Settings",
- "attached_to_name": "Website Settings",
- "decode": True,
- }
- )
- _file.save()
- fileurl = _file.file_url
- frappe.db.set_value(
- "Website Settings",
- "Website Settings",
- "brand_html",
- "<img src='{0}' style='max-width: 40px; max-height: 25px;'> {1}".format(
- fileurl, args.get("company_name")
- ),
- )
-
-
-def create_website(args):
- website_maker(args)
-
-
def get_fy_details(fy_start_date, fy_end_date):
start_year = getdate(fy_start_date).year
if start_year == getdate(fy_end_date).year:
diff --git a/erpnext/setup/setup_wizard/operations/default_website.py b/erpnext/setup/setup_wizard/operations/default_website.py
deleted file mode 100644
index 40b02b3..0000000
--- a/erpnext/setup/setup_wizard/operations/default_website.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-from frappe import _
-from frappe.utils import nowdate
-
-
-class website_maker(object):
- def __init__(self, args):
- self.args = args
- self.company = args.company_name
- self.tagline = args.company_tagline
- self.user = args.get("email")
- self.make_web_page()
- self.make_website_settings()
- self.make_blog()
-
- def make_web_page(self):
- # home page
- homepage = frappe.get_doc("Homepage", "Homepage")
- homepage.company = self.company
- homepage.tag_line = self.tagline
- homepage.setup_items()
- homepage.save()
-
- def make_website_settings(self):
- # update in home page in settings
- website_settings = frappe.get_doc("Website Settings", "Website Settings")
- website_settings.home_page = "home"
- website_settings.brand_html = self.company
- website_settings.copyright = self.company
- website_settings.top_bar_items = []
- website_settings.append(
- "top_bar_items", {"doctype": "Top Bar Item", "label": "Contact", "url": "/contact"}
- )
- website_settings.append(
- "top_bar_items", {"doctype": "Top Bar Item", "label": "Blog", "url": "/blog"}
- )
- website_settings.append(
- "top_bar_items", {"doctype": "Top Bar Item", "label": _("Products"), "url": "/all-products"}
- )
- website_settings.save()
-
- def make_blog(self):
- blog_category = frappe.get_doc(
- {"doctype": "Blog Category", "category_name": "general", "published": 1, "title": _("General")}
- ).insert()
-
- if not self.user:
- # Admin setup
- return
-
- blogger = frappe.new_doc("Blogger")
- user = frappe.get_doc("User", self.user)
- blogger.user = self.user
- blogger.full_name = user.first_name + (" " + user.last_name if user.last_name else "")
- blogger.short_name = user.first_name.lower()
- blogger.avatar = user.user_image
- blogger.insert()
-
- frappe.get_doc(
- {
- "doctype": "Blog Post",
- "title": "Welcome",
- "published": 1,
- "published_on": nowdate(),
- "blogger": blogger.name,
- "blog_category": blog_category.name,
- "blog_intro": "My First Blog",
- "content": frappe.get_template("setup/setup_wizard/data/sample_blog_post.html").render(),
- }
- ).insert()
-
-
-def test():
- frappe.delete_doc("Web Page", "test-company")
- frappe.delete_doc("Blog Post", "welcome")
- frappe.delete_doc("Blogger", "administrator")
- frappe.delete_doc("Blog Category", "general")
- website_maker(
- {
- "company": "Test Company",
- "company_tagline": "Better Tools for Everyone",
- "name": "Administrator",
- }
- )
- frappe.db.commit()
diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py
index bd86a5b..65b268e 100644
--- a/erpnext/setup/setup_wizard/setup_wizard.py
+++ b/erpnext/setup/setup_wizard/setup_wizard.py
@@ -5,7 +5,6 @@
import frappe
from frappe import _
-from .operations import company_setup
from .operations import install_fixtures as fixtures
@@ -35,7 +34,6 @@
"fail_msg": "Failed to set defaults",
"tasks": [
{"fn": setup_defaults, "args": args, "fail_msg": _("Failed to setup defaults")},
- {"fn": stage_four, "args": args, "fail_msg": _("Failed to create website")},
],
},
{
@@ -60,12 +58,6 @@
fixtures.install_defaults(frappe._dict(args))
-def stage_four(args):
- company_setup.create_website(args)
- company_setup.create_email_digest()
- company_setup.create_logo(args)
-
-
def fin(args):
frappe.local.message_log = []
login_as_first_user(args)
@@ -81,5 +73,4 @@
stage_fixtures(args)
setup_company(args)
setup_defaults(args)
- stage_four(args)
fin(args)
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index 54bd8c3..bab57fe 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -81,6 +81,11 @@
if entries:
return flt(entries[0].exchange_rate)
+ if frappe.get_cached_value(
+ "Currency Exchange Settings", "Currency Exchange Settings", "disabled"
+ ):
+ return 0.00
+
try:
cache = frappe.cache()
key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency)
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 9f409d4..72654e6 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -159,13 +159,18 @@
last_sle_qty = (
frappe.qb.from_(sle)
.select(sle.qty_after_transaction)
- .where((sle.item_code == args.get("item_code")) & (sle.warehouse == args.get("warehouse")))
+ .where(
+ (sle.item_code == args.get("item_code"))
+ & (sle.warehouse == args.get("warehouse"))
+ & (sle.is_cancelled == 0)
+ )
.orderby(CombineDatetime(sle.posting_date, sle.posting_time), order=Order.desc)
.orderby(sle.creation, order=Order.desc)
.limit(1)
.run()
)
+ actual_qty = 0.0
if last_sle_qty:
actual_qty = last_sle_qty[0][0]
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index a1df764..9f9f5cb 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -228,6 +228,7 @@
def on_submit(self):
self.validate_packed_qty()
+ self.update_pick_list_status()
# Check for Approving Authority
frappe.get_doc("Authorization Control").validate_approving_authority(
@@ -313,6 +314,11 @@
if has_error:
raise frappe.ValidationError
+ def update_pick_list_status(self):
+ from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status
+
+ update_pick_list_status(self.pick_list)
+
def check_next_docstatus(self):
submit_rv = frappe.db.sql(
"""select t1.name
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index d747383..903e2af 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -490,6 +490,46 @@
self.assertEqual(gle_warehouse_amount, 1400)
+ def test_bin_details_of_packed_item(self):
+ from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ # test Update Items with product bundle
+ if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
+ bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
+ bundle_item.append(
+ "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
+ )
+ bundle_item.save(ignore_permissions=True)
+
+ make_item("_Packed Item New 1", {"is_stock_item": 1})
+ make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
+
+ si = create_delivery_note(
+ item_code="_Test Product Bundle Item New",
+ update_stock=1,
+ warehouse="_Test Warehouse - _TC",
+ transaction_date=add_days(nowdate(), -1),
+ do_not_submit=1,
+ )
+
+ make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
+
+ bin_details = frappe.db.get_value(
+ "Bin",
+ {"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
+ ["actual_qty", "projected_qty", "ordered_qty"],
+ as_dict=1,
+ )
+
+ si.transaction_date = nowdate()
+ si.save()
+
+ packed_item = si.packed_items[0]
+ self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
+ self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
+ self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
+
def test_return_for_serialized_items(self):
se = make_serialized_item()
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
@@ -650,6 +690,11 @@
update_delivery_note_status(dn.name, "Closed")
self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed")
+ # Check cancelling closed delivery note
+ dn.load_from_db()
+ dn.cancel()
+ self.assertEqual(dn.status, "Cancelled")
+
def test_dn_billing_status_case1(self):
# SO -> DN -> SI
so = make_sales_order()
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
index ba1023a..0310682 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
@@ -37,7 +37,7 @@
if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger
&& frm.doc.__onload.has_stock_ledger.length) {
let allow_to_edit_fields = ['disabled', 'fetch_from_parent',
- 'type_of_transaction', 'condition'];
+ 'type_of_transaction', 'condition', 'mandatory_depends_on'];
frm.fields.forEach((field) => {
if (!in_list(allow_to_edit_fields, field.df.fieldname)) {
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
index 4397e11..eb6102a 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
@@ -24,6 +24,9 @@
"istable",
"applicable_condition_example_section",
"condition",
+ "conditional_mandatory_section",
+ "reqd",
+ "mandatory_depends_on",
"conditional_rule_examples_section",
"html_19"
],
@@ -153,11 +156,28 @@
"fieldname": "conditional_rule_examples_section",
"fieldtype": "Section Break",
"label": "Conditional Rule Examples"
+ },
+ {
+ "description": "To apply condition on parent field use parent.field_name and to apply condition on child table use doc.field_name. Here field_name could be based on the actual column name of the respective field.",
+ "fieldname": "mandatory_depends_on",
+ "fieldtype": "Small Text",
+ "label": "Mandatory Depends On"
+ },
+ {
+ "fieldname": "conditional_mandatory_section",
+ "fieldtype": "Section Break",
+ "label": "Mandatory Section"
+ },
+ {
+ "default": "0",
+ "fieldname": "reqd",
+ "fieldtype": "Check",
+ "label": "Mandatory"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-11-15 15:50:16.767105",
+ "modified": "2023-01-31 13:44:38.507698",
"modified_by": "Administrator",
"module": "Stock",
"name": "Inventory Dimension",
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
index 009548a..db2b5d0 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
@@ -126,6 +126,8 @@
insert_after="inventory_dimension",
options=self.reference_document,
label=self.dimension_name,
+ reqd=self.reqd,
+ mandatory_depends_on=self.mandatory_depends_on,
),
]
@@ -142,6 +144,8 @@
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
) and not field_exists("Stock Ledger Entry", self.target_fieldname):
dimension_field = dimension_fields[1]
+ dimension_field["mandatory_depends_on"] = ""
+ dimension_field["reqd"] = 0
dimension_field["fieldname"] = self.target_fieldname
custom_fields["Stock Ledger Entry"] = dimension_field
diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
index edff3fd..28b1ed9 100644
--- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
@@ -85,6 +85,9 @@
condition="parent.purpose == 'Material Issue'",
)
+ inv_dim1.reqd = 0
+ inv_dim1.save()
+
create_inventory_dimension(
reference_document="Shelf",
type_of_transaction="Inward",
@@ -205,6 +208,48 @@
)
)
+ def test_check_mandatory_dimensions(self):
+ doc = create_inventory_dimension(
+ reference_document="Pallet",
+ type_of_transaction="Outward",
+ dimension_name="Pallet",
+ apply_to_all_doctypes=0,
+ document_type="Stock Entry Detail",
+ )
+
+ doc.reqd = 1
+ doc.save()
+
+ self.assertTrue(
+ frappe.db.get_value(
+ "Custom Field", {"fieldname": "pallet", "dt": "Stock Entry Detail", "reqd": 1}, "name"
+ )
+ )
+
+ doc.load_from_db
+ doc.reqd = 0
+ doc.save()
+
+ def test_check_mandatory_depends_on_dimensions(self):
+ doc = create_inventory_dimension(
+ reference_document="Pallet",
+ type_of_transaction="Outward",
+ dimension_name="Pallet",
+ apply_to_all_doctypes=0,
+ document_type="Stock Entry Detail",
+ )
+
+ doc.mandatory_depends_on = "t_warehouse"
+ doc.save()
+
+ self.assertTrue(
+ frappe.db.get_value(
+ "Custom Field",
+ {"fieldname": "pallet", "dt": "Stock Entry Detail", "mandatory_depends_on": "t_warehouse"},
+ "name",
+ )
+ )
+
def prepare_test_data():
if not frappe.db.exists("DocType", "Shelf"):
@@ -251,6 +296,22 @@
create_warehouse("Rack Warehouse")
+ if not frappe.db.exists("DocType", "Pallet"):
+ frappe.get_doc(
+ {
+ "doctype": "DocType",
+ "name": "Pallet",
+ "module": "Stock",
+ "custom": 1,
+ "naming_rule": "By fieldname",
+ "autoname": "field:pallet_name",
+ "fields": [{"label": "Pallet Name", "fieldname": "pallet_name", "fieldtype": "Data"}],
+ "permissions": [
+ {"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
+ ],
+ }
+ ).insert(ignore_permissions=True)
+
def create_inventory_dimension(**args):
args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index e61f0f5..5bcb05a 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -894,6 +894,12 @@
new_child_doc.uom = frm.doc.stock_uom;
new_child_doc.description = frm.doc.description;
- frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
+ frappe.run_serially([
+ () => frappe.ui.form.make_quick_entry(doctype, null, null, new_doc),
+ () => {
+ frappe.flags.ignore_company_party_validation = true;
+ frappe.model.trigger("item_code", frm.doc.name, new_child_doc);
+ }
+ ])
});
}
diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py
index 391ff06..ac4c313 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -74,11 +74,10 @@
def validate_duplication(self):
values, abbrs = [], []
for d in self.item_attribute_values:
- d.abbr = d.abbr.upper()
- if d.attribute_value in values:
- frappe.throw(_("{0} must appear only once").format(d.attribute_value))
+ if d.attribute_value.lower() in map(str.lower, values):
+ frappe.throw(_("Attribute value: {0} must appear only once").format(d.attribute_value.title()))
values.append(d.attribute_value)
- if d.abbr in abbrs:
- frappe.throw(_("{0} must appear only once").format(d.abbr))
+ if d.abbr.lower() in map(str.lower, abbrs):
+ frappe.throw(_("Abbreviation: {0} must appear only once").format(d.abbr.title()))
abbrs.append(d.abbr)
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 5f05de6..156e591 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -366,10 +366,11 @@
frappe.ui.form.on("Material Request Item", {
qty: function (frm, doctype, name) {
- var d = locals[doctype][name];
- if (flt(d.qty) < flt(d.min_order_qty)) {
+ const item = locals[doctype][name];
+ if (flt(item.qty) < flt(item.min_order_qty)) {
frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
}
+ frm.events.get_item_data(frm, item, false);
},
from_warehouse: function(frm, doctype, name) {
diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json
index e1c3f0f..7259dc0 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.json
+++ b/erpnext/stock/doctype/pick_list/pick_list.json
@@ -26,7 +26,8 @@
"locations",
"amended_from",
"print_settings_section",
- "group_same_items"
+ "group_same_items",
+ "status"
],
"fields": [
{
@@ -168,11 +169,26 @@
"fieldtype": "Data",
"label": "Customer Name",
"read_only": 1
+ },
+ {
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "in_standard_filter": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Draft\nOpen\nCompleted\nCancelled",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1,
+ "reqd": 1,
+ "search_index": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2022-07-19 11:03:04.442174",
+ "modified": "2023-01-24 10:33:43.244476",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
@@ -244,4 +260,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 9e6aead..bf3b5dd 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -4,14 +4,15 @@
import json
from collections import OrderedDict, defaultdict
from itertools import groupby
-from typing import Dict, List, Set
+from typing import Dict, List
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import map_child_doc
from frappe.query_builder import Case
-from frappe.query_builder.functions import Locate
+from frappe.query_builder.custom import GROUP_CONCAT
+from frappe.query_builder.functions import Coalesce, IfNull, Locate, Replace, Sum
from frappe.utils import cint, floor, flt, today
from frappe.utils.nestedset import get_descendants_of
@@ -41,7 +42,9 @@
)
def before_submit(self):
- update_sales_orders = set()
+ self.validate_picked_items()
+
+ def validate_picked_items(self):
for item in self.locations:
if self.scan_mode and item.picked_qty < item.stock_qty:
frappe.throw(
@@ -50,17 +53,14 @@
).format(item.idx, item.stock_qty - item.picked_qty, item.stock_uom),
title=_("Pick List Incomplete"),
)
- elif not self.scan_mode and item.picked_qty == 0:
+
+ if not self.scan_mode and item.picked_qty == 0:
# if the user has not entered any picked qty, set it to stock_qty, before submit
item.picked_qty = item.stock_qty
- if item.sales_order_item:
- # update the picked_qty in SO Item
- self.update_sales_order_item(item, item.picked_qty, item.item_code)
- update_sales_orders.add(item.sales_order)
-
if not frappe.get_cached_value("Item", item.item_code, "has_serial_no"):
continue
+
if not item.serial_no:
frappe.throw(
_("Row #{0}: {1} does not have any available serial numbers in {2}").format(
@@ -68,63 +68,119 @@
),
title=_("Serial Nos Required"),
)
- if len(item.serial_no.split("\n")) == item.picked_qty:
- continue
- frappe.throw(
- _(
- "For item {0} at row {1}, count of serial numbers does not match with the picked quantity"
- ).format(frappe.bold(item.item_code), frappe.bold(item.idx)),
- title=_("Quantity Mismatch"),
- )
- self.update_bundle_picked_qty()
- self.update_sales_order_picking_status(update_sales_orders)
-
- def before_cancel(self):
- """Deduct picked qty on cancelling pick list"""
- updated_sales_orders = set()
-
- for item in self.get("locations"):
- if item.sales_order_item:
- self.update_sales_order_item(item, -1 * item.picked_qty, item.item_code)
- updated_sales_orders.add(item.sales_order)
-
- self.update_bundle_picked_qty()
- self.update_sales_order_picking_status(updated_sales_orders)
-
- def update_sales_order_item(self, item, picked_qty, item_code):
- item_table = "Sales Order Item" if not item.product_bundle_item else "Packed Item"
- stock_qty_field = "stock_qty" if not item.product_bundle_item else "qty"
-
- already_picked, actual_qty = frappe.db.get_value(
- item_table,
- item.sales_order_item,
- ["picked_qty", stock_qty_field],
- for_update=True,
- )
-
- if self.docstatus == 1:
- if (((already_picked + picked_qty) / actual_qty) * 100) > (
- 100 + flt(frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance"))
- ):
+ if len(item.serial_no.split("\n")) != item.picked_qty:
frappe.throw(
_(
- "You are picking more than required quantity for {}. Check if there is any other pick list created for {}"
- ).format(item_code, item.sales_order)
+ "For item {0} at row {1}, count of serial numbers does not match with the picked quantity"
+ ).format(frappe.bold(item.item_code), frappe.bold(item.idx)),
+ title=_("Quantity Mismatch"),
)
- frappe.db.set_value(item_table, item.sales_order_item, "picked_qty", already_picked + picked_qty)
+ def on_submit(self):
+ self.update_status()
+ self.update_bundle_picked_qty()
+ self.update_reference_qty()
+ self.update_sales_order_picking_status()
- @staticmethod
- def update_sales_order_picking_status(sales_orders: Set[str]) -> None:
+ def on_cancel(self):
+ self.update_status()
+ self.update_bundle_picked_qty()
+ self.update_reference_qty()
+ self.update_sales_order_picking_status()
+
+ def update_status(self, status=None, update_modified=True):
+ if not status:
+ if self.docstatus == 0:
+ status = "Draft"
+ elif self.docstatus == 1:
+ if self.status == "Draft":
+ status = "Open"
+ elif target_document_exists(self.name, self.purpose):
+ status = "Completed"
+ elif self.docstatus == 2:
+ status = "Cancelled"
+
+ if status:
+ frappe.db.set_value("Pick List", self.name, "status", status, update_modified=update_modified)
+
+ def update_reference_qty(self):
+ packed_items = []
+ so_items = []
+
+ for item in self.locations:
+ if item.product_bundle_item:
+ packed_items.append(item.sales_order_item)
+ elif item.sales_order_item:
+ so_items.append(item.sales_order_item)
+
+ if packed_items:
+ self.update_packed_items_qty(packed_items)
+
+ if so_items:
+ self.update_sales_order_item_qty(so_items)
+
+ def update_packed_items_qty(self, packed_items):
+ picked_items = get_picked_items_qty(packed_items)
+ self.validate_picked_qty(picked_items)
+
+ picked_qty = frappe._dict()
+ for d in picked_items:
+ picked_qty[d.sales_order_item] = d.picked_qty
+
+ for packed_item in packed_items:
+ frappe.db.set_value(
+ "Packed Item",
+ packed_item,
+ "picked_qty",
+ flt(picked_qty.get(packed_item)),
+ update_modified=False,
+ )
+
+ def update_sales_order_item_qty(self, so_items):
+ picked_items = get_picked_items_qty(so_items)
+ self.validate_picked_qty(picked_items)
+
+ picked_qty = frappe._dict()
+ for d in picked_items:
+ picked_qty[d.sales_order_item] = d.picked_qty
+
+ for so_item in so_items:
+ frappe.db.set_value(
+ "Sales Order Item",
+ so_item,
+ "picked_qty",
+ flt(picked_qty.get(so_item)),
+ update_modified=False,
+ )
+
+ def update_sales_order_picking_status(self) -> None:
+ sales_orders = []
+ for row in self.locations:
+ if row.sales_order and row.sales_order not in sales_orders:
+ sales_orders.append(row.sales_order)
+
for sales_order in sales_orders:
- if sales_order:
- frappe.get_doc("Sales Order", sales_order, for_update=True).update_picking_status()
+ frappe.get_doc("Sales Order", sales_order, for_update=True).update_picking_status()
+
+ def validate_picked_qty(self, data):
+ over_delivery_receipt_allowance = 100 + flt(
+ frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
+ )
+
+ for row in data:
+ if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance:
+ frappe.throw(
+ _(
+ f"You are picking more than required quantity for the item {row.item_code}. Check if there is any other pick list created for the sales order {row.sales_order}."
+ )
+ )
@frappe.whitelist()
def set_item_locations(self, save=False):
self.validate_for_qty()
items = self.aggregate_item_qty()
+ picked_items_details = self.get_picked_items_details(items)
self.item_location_map = frappe._dict()
from_warehouses = None
@@ -143,7 +199,11 @@
self.item_location_map.setdefault(
item_code,
get_available_item_locations(
- item_code, from_warehouses, self.item_count_map.get(item_code), self.company
+ item_code,
+ from_warehouses,
+ self.item_count_map.get(item_code),
+ self.company,
+ picked_item_details=picked_items_details.get(item_code),
),
)
@@ -272,6 +332,56 @@
already_picked + (picked_qty * (1 if self.docstatus == 1 else -1)),
)
+ def get_picked_items_details(self, items):
+ picked_items = frappe._dict()
+
+ if items:
+ pi = frappe.qb.DocType("Pick List")
+ pi_item = frappe.qb.DocType("Pick List Item")
+ query = (
+ frappe.qb.from_(pi)
+ .inner_join(pi_item)
+ .on(pi.name == pi_item.parent)
+ .select(
+ pi_item.item_code,
+ pi_item.warehouse,
+ pi_item.batch_no,
+ Sum(Case().when(pi_item.picked_qty > 0, pi_item.picked_qty).else_(pi_item.stock_qty)).as_(
+ "picked_qty"
+ ),
+ Replace(GROUP_CONCAT(pi_item.serial_no), ",", "\n").as_("serial_no"),
+ )
+ .where(
+ (pi_item.item_code.isin([x.item_code for x in items]))
+ & ((pi_item.picked_qty > 0) | (pi_item.stock_qty > 0))
+ & (pi.status != "Completed")
+ & (pi_item.docstatus != 2)
+ )
+ .groupby(
+ pi_item.item_code,
+ pi_item.warehouse,
+ pi_item.batch_no,
+ )
+ )
+
+ if self.name:
+ query = query.where(pi_item.parent != self.name)
+
+ items_data = query.run(as_dict=True)
+
+ for item_data in items_data:
+ key = (item_data.warehouse, item_data.batch_no) if item_data.batch_no else item_data.warehouse
+ serial_no = [x for x in item_data.serial_no.split("\n") if x] if item_data.serial_no else None
+ data = {"picked_qty": item_data.picked_qty}
+ if serial_no:
+ data["serial_no"] = serial_no
+ if item_data.item_code not in picked_items:
+ picked_items[item_data.item_code] = {key: data}
+ else:
+ picked_items[item_data.item_code][key] = data
+
+ return picked_items
+
def _get_product_bundles(self) -> Dict[str, str]:
# Dict[so_item_row: item_code]
product_bundles = {}
@@ -309,6 +419,32 @@
return int(flt(min(possible_bundles), precision or 6))
+def update_pick_list_status(pick_list):
+ if pick_list:
+ doc = frappe.get_doc("Pick List", pick_list)
+ doc.run_method("update_status")
+
+
+def get_picked_items_qty(items) -> List[Dict]:
+ pi_item = frappe.qb.DocType("Pick List Item")
+ return (
+ frappe.qb.from_(pi_item)
+ .select(
+ pi_item.sales_order_item,
+ pi_item.item_code,
+ pi_item.sales_order,
+ Sum(pi_item.stock_qty).as_("stock_qty"),
+ Sum(pi_item.picked_qty).as_("picked_qty"),
+ )
+ .where((pi_item.docstatus == 1) & (pi_item.sales_order_item.isin(items)))
+ .groupby(
+ pi_item.sales_order_item,
+ pi_item.sales_order,
+ )
+ .for_update()
+ ).run(as_dict=True)
+
+
def validate_item_locations(pick_list):
if not pick_list.locations:
frappe.throw(_("Add items in the Item Locations table"))
@@ -372,31 +508,38 @@
def get_available_item_locations(
- item_code, from_warehouses, required_qty, company, ignore_validation=False
+ item_code,
+ from_warehouses,
+ required_qty,
+ company,
+ ignore_validation=False,
+ picked_item_details=None,
):
locations = []
+ total_picked_qty = (
+ sum([v.get("picked_qty") for k, v in picked_item_details.items()]) if picked_item_details else 0
+ )
has_serial_no = frappe.get_cached_value("Item", item_code, "has_serial_no")
has_batch_no = frappe.get_cached_value("Item", item_code, "has_batch_no")
if has_batch_no and has_serial_no:
locations = get_available_item_locations_for_serial_and_batched_item(
- item_code, from_warehouses, required_qty, company
+ item_code, from_warehouses, required_qty, company, total_picked_qty
)
elif has_serial_no:
locations = get_available_item_locations_for_serialized_item(
- item_code, from_warehouses, required_qty, company
+ item_code, from_warehouses, required_qty, company, total_picked_qty
)
elif has_batch_no:
locations = get_available_item_locations_for_batched_item(
- item_code, from_warehouses, required_qty, company
+ item_code, from_warehouses, required_qty, company, total_picked_qty
)
else:
locations = get_available_item_locations_for_other_item(
- item_code, from_warehouses, required_qty, company
+ item_code, from_warehouses, required_qty, company, total_picked_qty
)
total_qty_available = sum(location.get("qty") for location in locations)
-
remaining_qty = required_qty - total_qty_available
if remaining_qty > 0 and not ignore_validation:
@@ -407,25 +550,60 @@
title=_("Insufficient Stock"),
)
+ if picked_item_details:
+ for location in list(locations):
+ key = (
+ (location["warehouse"], location["batch_no"])
+ if location.get("batch_no")
+ else location["warehouse"]
+ )
+
+ if key in picked_item_details:
+ picked_detail = picked_item_details[key]
+
+ if picked_detail.get("serial_no") and location.get("serial_no"):
+ location["serial_no"] = list(
+ set(location["serial_no"]).difference(set(picked_detail["serial_no"]))
+ )
+ location["qty"] = len(location["serial_no"])
+ else:
+ location["qty"] -= picked_detail.get("picked_qty")
+
+ if location["qty"] < 1:
+ locations.remove(location)
+
+ total_qty_available = sum(location.get("qty") for location in locations)
+ remaining_qty = required_qty - total_qty_available
+
+ if remaining_qty > 0 and not ignore_validation:
+ frappe.msgprint(
+ _("{0} units of Item {1} is picked in another Pick List.").format(
+ remaining_qty, frappe.get_desk_link("Item", item_code)
+ ),
+ title=_("Already Picked"),
+ )
+
return locations
def get_available_item_locations_for_serialized_item(
- item_code, from_warehouses, required_qty, company
+ item_code, from_warehouses, required_qty, company, total_picked_qty=0
):
- filters = frappe._dict({"item_code": item_code, "company": company, "warehouse": ["!=", ""]})
+ sn = frappe.qb.DocType("Serial No")
+ query = (
+ frappe.qb.from_(sn)
+ .select(sn.name, sn.warehouse)
+ .where((sn.item_code == item_code) & (sn.company == company))
+ .orderby(sn.purchase_date)
+ .limit(cint(required_qty + total_picked_qty))
+ )
if from_warehouses:
- filters.warehouse = ["in", from_warehouses]
+ query = query.where(sn.warehouse.isin(from_warehouses))
+ else:
+ query = query.where(Coalesce(sn.warehouse, "") != "")
- serial_nos = frappe.get_all(
- "Serial No",
- fields=["name", "warehouse"],
- filters=filters,
- limit=required_qty,
- order_by="purchase_date",
- as_list=1,
- )
+ serial_nos = query.run(as_list=True)
warehouse_serial_nos_map = frappe._dict()
for serial_no, warehouse in serial_nos:
@@ -439,94 +617,88 @@
def get_available_item_locations_for_batched_item(
- item_code, from_warehouses, required_qty, company
+ item_code, from_warehouses, required_qty, company, total_picked_qty=0
):
- warehouse_condition = "and warehouse in %(warehouses)s" if from_warehouses else ""
- batch_locations = frappe.db.sql(
- """
- SELECT
- sle.`warehouse`,
- sle.`batch_no`,
- SUM(sle.`actual_qty`) AS `qty`
- FROM
- `tabStock Ledger Entry` sle, `tabBatch` batch
- WHERE
- sle.batch_no = batch.name
- and sle.`item_code`=%(item_code)s
- and sle.`company` = %(company)s
- and batch.disabled = 0
- and sle.is_cancelled=0
- and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
- {warehouse_condition}
- GROUP BY
- sle.`warehouse`,
- sle.`batch_no`,
- sle.`item_code`
- HAVING `qty` > 0
- ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`, sle.`batch_no`, sle.`warehouse`
- """.format(
- warehouse_condition=warehouse_condition
- ),
- { # nosec
- "item_code": item_code,
- "company": company,
- "today": today(),
- "warehouses": from_warehouses,
- },
- as_dict=1,
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+ batch = frappe.qb.DocType("Batch")
+
+ query = (
+ frappe.qb.from_(sle)
+ .from_(batch)
+ .select(sle.warehouse, sle.batch_no, Sum(sle.actual_qty).as_("qty"))
+ .where(
+ (sle.batch_no == batch.name)
+ & (sle.item_code == item_code)
+ & (sle.company == company)
+ & (batch.disabled == 0)
+ & (sle.is_cancelled == 0)
+ & (IfNull(batch.expiry_date, "2200-01-01") > today())
+ )
+ .groupby(sle.warehouse, sle.batch_no, sle.item_code)
+ .having(Sum(sle.actual_qty) > 0)
+ .orderby(IfNull(batch.expiry_date, "2200-01-01"), batch.creation, sle.batch_no, sle.warehouse)
+ .limit(cint(required_qty + total_picked_qty))
)
- return batch_locations
+ if from_warehouses:
+ query = query.where(sle.warehouse.isin(from_warehouses))
+
+ return query.run(as_dict=True)
def get_available_item_locations_for_serial_and_batched_item(
- item_code, from_warehouses, required_qty, company
+ item_code, from_warehouses, required_qty, company, total_picked_qty=0
):
# Get batch nos by FIFO
locations = get_available_item_locations_for_batched_item(
item_code, from_warehouses, required_qty, company
)
- filters = frappe._dict(
- {"item_code": item_code, "company": company, "warehouse": ["!=", ""], "batch_no": ""}
- )
+ if locations:
+ sn = frappe.qb.DocType("Serial No")
+ conditions = (sn.item_code == item_code) & (sn.company == company)
- # Get Serial Nos by FIFO for Batch No
- for location in locations:
- filters.batch_no = location.batch_no
- filters.warehouse = location.warehouse
- location.qty = (
- required_qty if location.qty > required_qty else location.qty
- ) # if extra qty in batch
+ for location in locations:
+ location.qty = (
+ required_qty if location.qty > required_qty else location.qty
+ ) # if extra qty in batch
- serial_nos = frappe.get_list(
- "Serial No", fields=["name"], filters=filters, limit=location.qty, order_by="purchase_date"
- )
+ serial_nos = (
+ frappe.qb.from_(sn)
+ .select(sn.name)
+ .where(
+ (conditions) & (sn.batch_no == location.batch_no) & (sn.warehouse == location.warehouse)
+ )
+ .orderby(sn.purchase_date)
+ .limit(cint(location.qty + total_picked_qty))
+ ).run(as_dict=True)
- serial_nos = [sn.name for sn in serial_nos]
- location.serial_no = serial_nos
+ serial_nos = [sn.name for sn in serial_nos]
+ location.serial_no = serial_nos
+ location.qty = len(serial_nos)
return locations
-def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company):
- # gets all items available in different warehouses
- warehouses = [x.get("name") for x in frappe.get_list("Warehouse", {"company": company}, "name")]
-
- filters = frappe._dict(
- {"item_code": item_code, "warehouse": ["in", warehouses], "actual_qty": [">", 0]}
+def get_available_item_locations_for_other_item(
+ item_code, from_warehouses, required_qty, company, total_picked_qty=0
+):
+ bin = frappe.qb.DocType("Bin")
+ query = (
+ frappe.qb.from_(bin)
+ .select(bin.warehouse, bin.actual_qty.as_("qty"))
+ .where((bin.item_code == item_code) & (bin.actual_qty > 0))
+ .orderby(bin.creation)
+ .limit(cint(required_qty + total_picked_qty))
)
if from_warehouses:
- filters.warehouse = ["in", from_warehouses]
+ query = query.where(bin.warehouse.isin(from_warehouses))
+ else:
+ wh = frappe.qb.DocType("Warehouse")
+ query = query.from_(wh).where((bin.warehouse == wh.name) & (wh.company == company))
- item_locations = frappe.get_all(
- "Bin",
- fields=["warehouse", "actual_qty as qty"],
- filters=filters,
- limit=required_qty,
- order_by="creation",
- )
+ item_locations = query.run(as_dict=True)
return item_locations
diff --git a/erpnext/stock/doctype/pick_list/pick_list_list.js b/erpnext/stock/doctype/pick_list/pick_list_list.js
new file mode 100644
index 0000000..ad88b0a
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list/pick_list_list.js
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.listview_settings['Pick List'] = {
+ get_indicator: function (doc) {
+ const status_colors = {
+ "Draft": "grey",
+ "Open": "orange",
+ "Completed": "green",
+ "Cancelled": "red",
+ };
+ return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
+ },
+};
\ No newline at end of file
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 43acdf0..1254fe3 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -414,6 +414,7 @@
pick_list.submit()
delivery_note = create_delivery_note(pick_list.name)
+ pick_list.load_from_db()
self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty)
self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty)
@@ -663,3 +664,147 @@
self.assertEqual(dn.items[0].rate, 42)
so.reload()
self.assertEqual(so.per_delivered, 100)
+
+ def test_pick_list_status(self):
+ warehouse = "_Test Warehouse - _TC"
+ item = make_item(properties={"maintain_stock": 1}).name
+ make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
+
+ so = make_sales_order(item_code=item, qty=10, rate=100)
+
+ pl = create_pick_list(so.name)
+ pl.save()
+ pl.reload()
+ self.assertEqual(pl.status, "Draft")
+
+ pl.submit()
+ pl.reload()
+ self.assertEqual(pl.status, "Open")
+
+ dn = create_delivery_note(pl.name)
+ dn.save()
+ pl.reload()
+ self.assertEqual(pl.status, "Open")
+
+ dn.submit()
+ pl.reload()
+ self.assertEqual(pl.status, "Completed")
+
+ dn.cancel()
+ pl.reload()
+ self.assertEqual(pl.status, "Completed")
+
+ pl.cancel()
+ pl.reload()
+ self.assertEqual(pl.status, "Cancelled")
+
+ def test_consider_existing_pick_list(self):
+ def create_items(items_properties):
+ items = []
+
+ for properties in items_properties:
+ properties.update({"maintain_stock": 1})
+ item_code = make_item(properties=properties).name
+ properties.update({"item_code": item_code})
+ items.append(properties)
+
+ return items
+
+ def create_stock_entries(items):
+ warehouses = ["Stores - _TC", "Finished Goods - _TC"]
+
+ for item in items:
+ for warehouse in warehouses:
+ se = make_stock_entry(
+ item=item.get("item_code"),
+ to_warehouse=warehouse,
+ qty=5,
+ )
+
+ def get_item_list(items, qty, warehouse="All Warehouses - _TC"):
+ return [
+ {
+ "item_code": item.get("item_code"),
+ "qty": qty,
+ "warehouse": warehouse,
+ }
+ for item in items
+ ]
+
+ def get_picked_items_details(pick_list_doc):
+ items_data = {}
+
+ for location in pick_list_doc.locations:
+ key = (location.warehouse, location.batch_no) if location.batch_no else location.warehouse
+ serial_no = [x for x in location.serial_no.split("\n") if x] if location.serial_no else None
+ data = {"picked_qty": location.picked_qty}
+ if serial_no:
+ data["serial_no"] = serial_no
+ if location.item_code not in items_data:
+ items_data[location.item_code] = {key: data}
+ else:
+ items_data[location.item_code][key] = data
+
+ return items_data
+
+ # Step - 1: Setup - Create Items and Stock Entries
+ items_properties = [
+ {
+ "valuation_rate": 100,
+ },
+ {
+ "valuation_rate": 200,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ },
+ {
+ "valuation_rate": 300,
+ "has_serial_no": 1,
+ "serial_no_series": "SNO.###",
+ },
+ {
+ "valuation_rate": 400,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "has_serial_no": 1,
+ "serial_no_series": "SNO.###",
+ },
+ ]
+
+ items = create_items(items_properties)
+ create_stock_entries(items)
+
+ # Step - 2: Create Sales Order [1]
+ so1 = make_sales_order(item_list=get_item_list(items, qty=6))
+
+ # Step - 3: Create and Submit Pick List [1] for Sales Order [1]
+ pl1 = create_pick_list(so1.name)
+ pl1.submit()
+
+ # Step - 4: Create Sales Order [2] with same Item(s) as Sales Order [1]
+ so2 = make_sales_order(item_list=get_item_list(items, qty=4))
+
+ # Step - 5: Create Pick List [2] for Sales Order [2]
+ pl2 = create_pick_list(so2.name)
+ pl2.save()
+
+ # Step - 6: Assert
+ picked_items_details = get_picked_items_details(pl1)
+
+ for location in pl2.locations:
+ key = (location.warehouse, location.batch_no) if location.batch_no else location.warehouse
+ item_data = picked_items_details.get(location.item_code, {}).get(key, {})
+ picked_qty = item_data.get("picked_qty", 0)
+ picked_serial_no = picked_items_details.get("serial_no", [])
+ bin_actual_qty = frappe.db.get_value(
+ "Bin", {"item_code": location.item_code, "warehouse": location.warehouse}, "actual_qty"
+ )
+
+ # Available Qty to pick should be equal to [Actual Qty - Picked Qty]
+ self.assertEqual(location.stock_qty, bin_actual_qty - picked_qty)
+
+ # Serial No should not be in the Picked Serial No list
+ if location.serial_no:
+ a = set(picked_serial_no)
+ b = set([x for x in location.serial_no.split("\n") if x])
+ self.assertSetEqual(b, b.difference(a))
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 3739cb8..af0d148 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -888,7 +888,7 @@
# Update Billing % based on pending accepted qty
total_amount, total_billed_amount = 0, 0
for item in pr_doc.items:
- return_data = frappe.db.get_list(
+ return_data = frappe.get_all(
"Purchase Receipt",
fields=["sum(abs(`tabPurchase Receipt Item`.qty)) as qty"],
filters=[
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 9321c2c..2a9f091 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -221,7 +221,7 @@
def item_query(doctype, txt, searchfield, start, page_len, filters):
from frappe.desk.reportview import get_match_cond
- from_doctype = cstr(filters.get("doctype"))
+ from_doctype = cstr(filters.get("from"))
if not from_doctype or not frappe.db.exists("DocType", from_doctype):
return []
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 897fca3..fb1f77a 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -169,6 +169,8 @@
},
refresh: function(frm) {
+ frm.trigger("get_items_from_transit_entry");
+
if(!frm.doc.docstatus) {
frm.trigger('validate_purpose_consumption');
frm.add_custom_button(__('Material Request'), function() {
@@ -337,6 +339,28 @@
}
},
+ get_items_from_transit_entry: function(frm) {
+ if (frm.doc.docstatus===0) {
+ frm.add_custom_button(__('Transit Entry'), function() {
+ erpnext.utils.map_current_doc({
+ method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry",
+ source_doctype: "Stock Entry",
+ target: frm,
+ date_field: "posting_date",
+ setters: {
+ stock_entry_type: "Material Transfer",
+ purpose: "Material Transfer",
+ },
+ get_query_filters: {
+ docstatus: 1,
+ purpose: "Material Transfer",
+ add_to_transit: 1,
+ }
+ })
+ }, __("Get Items From"));
+ }
+ },
+
before_save: function(frm) {
frm.doc.items.forEach((item) => {
item.uom = item.uom || item.stock_uom;
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 1755f28..8c20ca0 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -158,6 +158,7 @@
self.validate_subcontract_order()
self.update_subcontract_order_supplied_items()
self.update_subcontracting_order_status()
+ self.update_pick_list_status()
self.make_gl_entries()
@@ -2276,6 +2277,11 @@
update_subcontracting_order_status(self.subcontracting_order)
+ def update_pick_list_status(self):
+ from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status
+
+ update_pick_list_status(self.pick_list)
+
def set_missing_values(self):
"Updates rate and availability of all the items of mapped doc."
self.set_transfer_qty()
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
index 41a3b89..0f90013 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
@@ -117,6 +117,7 @@
args.item = "_Test Item"
s.company = args.company or erpnext.get_default_company()
+ s.add_to_transit = args.add_to_transit or 0
s.purchase_receipt_no = args.purchase_receipt_no
s.delivery_note_no = args.delivery_note_no
s.sales_invoice_no = args.sales_invoice_no
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index b574b71..38bf0a5 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -17,6 +17,7 @@
from erpnext.stock.doctype.serial_no.serial_no import * # noqa
from erpnext.stock.doctype.stock_entry.stock_entry import (
FinishedGoodError,
+ make_stock_in_entry,
move_sample_to_retention_warehouse,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -160,6 +161,53 @@
self.assertTrue(item_code in items)
+ def test_add_to_transit_entry(self):
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+ item_code = "_Test Transit Item"
+ company = "_Test Company"
+
+ create_warehouse("Test From Warehouse")
+ create_warehouse("Test Transit Warehouse")
+ create_warehouse("Test To Warehouse")
+
+ create_item(
+ item_code=item_code,
+ is_stock_item=1,
+ is_purchase_item=1,
+ company=company,
+ )
+
+ # create inward stock entry
+ make_stock_entry(
+ item_code=item_code,
+ target="Test From Warehouse - _TC",
+ qty=10,
+ basic_rate=100,
+ expense_account="Stock Adjustment - _TC",
+ cost_center="Main - _TC",
+ )
+
+ transit_entry = make_stock_entry(
+ item_code=item_code,
+ source="Test From Warehouse - _TC",
+ target="Test Transit Warehouse - _TC",
+ add_to_transit=1,
+ stock_entry_type="Material Transfer",
+ purpose="Material Transfer",
+ qty=10,
+ basic_rate=100,
+ expense_account="Stock Adjustment - _TC",
+ cost_center="Main - _TC",
+ )
+
+ end_transit_entry = make_stock_in_entry(transit_entry.name)
+ self.assertEqual(transit_entry.name, end_transit_entry.outgoing_stock_entry)
+ self.assertEqual(transit_entry.name, end_transit_entry.items[0].against_stock_entry)
+ self.assertEqual(transit_entry.items[0].name, end_transit_entry.items[0].ste_detail)
+
+ # create add to transit
+
def test_material_receipt_gl_entry(self):
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 363dc0a..5af1441 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -236,8 +236,10 @@
validate_end_of_life(item.name, item.end_of_life, item.disabled)
- if args.transaction_type == "selling" and cint(item.has_variants):
- throw(_("Item {0} is a template, please select one of its variants").format(item.name))
+ if cint(item.has_variants):
+ msg = f"Item {item.name} is a template, please select one of its variants"
+
+ throw(_(msg), title=_("Template Item Selected"))
elif args.transaction_type == "buying" and args.doctype != "Material Request":
if args.get("is_subcontracted"):
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 5d75bfd..d8b12ed 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1179,7 +1179,7 @@
def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
return frappe.db.get_value(
"Stock Ledger Entry",
- {"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle]},
+ {"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle], "is_cancelled": 0},
[
"item_code",
"warehouse",
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
index aab2fc9..7ca1264 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
@@ -11,6 +11,7 @@
"Partial Material Transferred": "purple",
"Material Transferred": "blue",
"Closed": "red",
+ "Cancelled": "red",
};
return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
},
diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
index 3675a4e..d77e774 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
@@ -1,352 +1,353 @@
{
- "actions": [],
- "autoname": "hash",
- "creation": "2022-04-01 19:26:31.475015",
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "item_code",
- "item_name",
- "bom",
- "include_exploded_items",
- "column_break_3",
- "schedule_date",
- "expected_delivery_date",
- "description_section",
- "description",
- "column_break_8",
- "image",
- "image_view",
- "quantity_and_rate_section",
- "qty",
- "received_qty",
- "returned_qty",
- "column_break_13",
- "stock_uom",
- "conversion_factor",
- "section_break_16",
- "rate",
- "amount",
- "column_break_19",
- "rm_cost_per_qty",
- "service_cost_per_qty",
- "additional_cost_per_qty",
- "warehouse_section",
- "warehouse",
- "accounting_details_section",
- "expense_account",
- "manufacture_section",
- "manufacturer",
- "manufacturer_part_no",
- "accounting_dimensions_section",
- "cost_center",
- "dimension_col_break",
- "project",
- "section_break_34",
- "page_break"
- ],
- "fields": [
- {
- "bold": 1,
- "columns": 2,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Item Code",
- "options": "Item",
- "read_only": 1,
- "reqd": 1,
- "search_index": 1
- },
- {
- "fetch_from": "item_code.item_name",
- "fetch_if_empty": 1,
- "fieldname": "item_name",
- "fieldtype": "Data",
- "in_global_search": 1,
- "label": "Item Name",
- "print_hide": 1,
- "reqd": 1
- },
- {
- "fieldname": "column_break_3",
- "fieldtype": "Column Break"
- },
- {
- "bold": 1,
- "columns": 2,
- "fieldname": "schedule_date",
- "fieldtype": "Date",
- "label": "Required By",
- "print_hide": 1,
- "read_only": 1
- },
- {
- "allow_on_submit": 1,
- "bold": 1,
- "fieldname": "expected_delivery_date",
- "fieldtype": "Date",
- "label": "Expected Delivery Date",
- "search_index": 1
- },
- {
- "collapsible": 1,
- "fieldname": "description_section",
- "fieldtype": "Section Break",
- "label": "Description"
- },
- {
- "fetch_from": "item_code.description",
- "fetch_if_empty": 1,
- "fieldname": "description",
- "fieldtype": "Text Editor",
- "label": "Description",
- "print_width": "300px",
- "reqd": 1,
- "width": "300px"
- },
- {
- "fieldname": "column_break_8",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "image",
- "fieldtype": "Attach",
- "hidden": 1,
- "label": "Image"
- },
- {
- "fieldname": "image_view",
- "fieldtype": "Image",
- "label": "Image View",
- "options": "image",
- "print_hide": 1
- },
- {
- "fieldname": "quantity_and_rate_section",
- "fieldtype": "Section Break",
- "label": "Quantity and Rate"
- },
- {
- "bold": 1,
- "columns": 1,
- "default": "1",
- "fieldname": "qty",
- "fieldtype": "Float",
- "in_list_view": 1,
- "label": "Quantity",
- "print_width": "60px",
- "read_only": 1,
- "reqd": 1,
- "width": "60px"
- },
- {
- "fieldname": "column_break_13",
- "fieldtype": "Column Break",
- "print_hide": 1
- },
- {
- "fieldname": "stock_uom",
- "fieldtype": "Link",
- "label": "Stock UOM",
- "options": "UOM",
- "print_width": "100px",
- "read_only": 1,
- "reqd": 1,
- "width": "100px"
- },
- {
- "default": "1",
- "fieldname": "conversion_factor",
- "fieldtype": "Float",
- "hidden": 1,
- "label": "Conversion Factor",
- "read_only": 1
- },
- {
- "fieldname": "section_break_16",
- "fieldtype": "Section Break"
- },
- {
- "bold": 1,
- "columns": 2,
- "fetch_from": "item_code.standard_rate",
- "fetch_if_empty": 1,
- "fieldname": "rate",
- "fieldtype": "Currency",
- "in_list_view": 1,
- "label": "Rate",
- "options": "currency",
- "read_only": 1,
- "reqd": 1
- },
- {
- "fieldname": "column_break_19",
- "fieldtype": "Column Break"
- },
- {
- "columns": 2,
- "fieldname": "amount",
- "fieldtype": "Currency",
- "in_list_view": 1,
- "label": "Amount",
- "options": "currency",
- "read_only": 1,
- "reqd": 1
- },
- {
- "fieldname": "warehouse_section",
- "fieldtype": "Section Break",
- "label": "Warehouse Details"
- },
- {
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "label": "Warehouse",
- "options": "Warehouse",
- "print_hide": 1,
- "reqd": 1
- },
- {
- "collapsible": 1,
- "fieldname": "accounting_details_section",
- "fieldtype": "Section Break",
- "label": "Accounting Details"
- },
- {
- "fieldname": "expense_account",
- "fieldtype": "Link",
- "label": "Expense Account",
- "options": "Account",
- "print_hide": 1
- },
- {
- "collapsible": 1,
- "fieldname": "manufacture_section",
- "fieldtype": "Section Break",
- "label": "Manufacture"
- },
- {
- "fieldname": "manufacturer",
- "fieldtype": "Link",
- "label": "Manufacturer",
- "options": "Manufacturer"
- },
- {
- "fieldname": "manufacturer_part_no",
- "fieldtype": "Data",
- "label": "Manufacturer Part Number"
- },
- {
- "depends_on": "item_code",
- "fetch_from": "item_code.default_bom",
- "fieldname": "bom",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "BOM",
- "options": "BOM",
- "print_hide": 1,
- "reqd": 1
- },
- {
- "default": "0",
- "fieldname": "include_exploded_items",
- "fieldtype": "Check",
- "label": "Include Exploded Items",
- "print_hide": 1
- },
- {
- "fieldname": "service_cost_per_qty",
- "fieldtype": "Currency",
- "label": "Service Cost Per Qty",
- "read_only": 1,
- "reqd": 1
- },
- {
- "default": "0",
- "fieldname": "additional_cost_per_qty",
- "fieldtype": "Currency",
- "label": "Additional Cost Per Qty",
- "read_only": 1
- },
- {
- "fieldname": "rm_cost_per_qty",
- "fieldtype": "Currency",
- "label": "Raw Material Cost Per Qty",
- "no_copy": 1,
- "read_only": 1
- },
- {
- "allow_on_submit": 1,
- "default": "0",
- "fieldname": "page_break",
- "fieldtype": "Check",
- "label": "Page Break",
- "no_copy": 1,
- "print_hide": 1
- },
- {
- "fieldname": "section_break_34",
- "fieldtype": "Section Break"
- },
- {
- "depends_on": "received_qty",
- "fieldname": "received_qty",
- "fieldtype": "Float",
- "label": "Received Qty",
- "no_copy": 1,
- "print_hide": 1,
- "read_only": 1
- },
- {
- "depends_on": "returned_qty",
- "fieldname": "returned_qty",
- "fieldtype": "Float",
- "label": "Returned Qty",
- "no_copy": 1,
- "print_hide": 1,
- "read_only": 1
- },
- {
- "collapsible": 1,
- "fieldname": "accounting_dimensions_section",
- "fieldtype": "Section Break",
- "label": "Accounting Dimensions"
- },
- {
- "fieldname": "cost_center",
- "fieldtype": "Link",
- "label": "Cost Center",
- "options": "Cost Center"
- },
- {
- "fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "project",
- "fieldtype": "Link",
- "label": "Project",
- "options": "Project"
- }
- ],
- "idx": 1,
- "index_web_pages_for_search": 1,
- "istable": 1,
- "links": [],
- "modified": "2022-08-15 14:25:45.177703",
- "modified_by": "Administrator",
- "module": "Subcontracting",
- "name": "Subcontracting Order Item",
- "naming_rule": "Random",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "search_fields": "item_name",
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": [],
- "track_changes": 1
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2022-04-01 19:26:31.475015",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_code",
+ "item_name",
+ "bom",
+ "include_exploded_items",
+ "column_break_3",
+ "schedule_date",
+ "expected_delivery_date",
+ "description_section",
+ "description",
+ "column_break_8",
+ "image",
+ "image_view",
+ "quantity_and_rate_section",
+ "qty",
+ "received_qty",
+ "returned_qty",
+ "column_break_13",
+ "stock_uom",
+ "conversion_factor",
+ "section_break_16",
+ "rate",
+ "amount",
+ "column_break_19",
+ "rm_cost_per_qty",
+ "service_cost_per_qty",
+ "additional_cost_per_qty",
+ "warehouse_section",
+ "warehouse",
+ "accounting_details_section",
+ "expense_account",
+ "manufacture_section",
+ "manufacturer",
+ "manufacturer_part_no",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break",
+ "project",
+ "section_break_34",
+ "page_break"
+ ],
+ "fields": [
+ {
+ "bold": 1,
+ "columns": 2,
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "options": "Item",
+ "read_only": 1,
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fetch_from": "item_code.item_name",
+ "fetch_if_empty": 1,
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "label": "Item Name",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "bold": 1,
+ "columns": 2,
+ "fieldname": "schedule_date",
+ "fieldtype": "Date",
+ "label": "Required By",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "bold": 1,
+ "fieldname": "expected_delivery_date",
+ "fieldtype": "Date",
+ "label": "Expected Delivery Date",
+ "search_index": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "description_section",
+ "fieldtype": "Section Break",
+ "label": "Description"
+ },
+ {
+ "fetch_from": "item_code.description",
+ "fetch_if_empty": 1,
+ "fieldname": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description",
+ "print_width": "300px",
+ "reqd": 1,
+ "width": "300px"
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "image",
+ "fieldtype": "Attach",
+ "hidden": 1,
+ "label": "Image"
+ },
+ {
+ "fieldname": "image_view",
+ "fieldtype": "Image",
+ "label": "Image View",
+ "options": "image",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "quantity_and_rate_section",
+ "fieldtype": "Section Break",
+ "label": "Quantity and Rate"
+ },
+ {
+ "bold": 1,
+ "columns": 1,
+ "default": "1",
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Quantity",
+ "print_width": "60px",
+ "read_only": 1,
+ "reqd": 1,
+ "width": "60px"
+ },
+ {
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "Stock UOM",
+ "options": "UOM",
+ "print_width": "100px",
+ "read_only": 1,
+ "reqd": 1,
+ "width": "100px"
+ },
+ {
+ "default": "1",
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "hidden": 1,
+ "label": "Conversion Factor",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_16",
+ "fieldtype": "Section Break"
+ },
+ {
+ "bold": 1,
+ "columns": 2,
+ "fetch_from": "item_code.standard_rate",
+ "fetch_if_empty": 1,
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Rate",
+ "options": "currency",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_19",
+ "fieldtype": "Column Break"
+ },
+ {
+ "columns": 2,
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Amount",
+ "options": "currency",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "warehouse_section",
+ "fieldtype": "Section Break",
+ "label": "Warehouse Details"
+ },
+ {
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "label": "Warehouse",
+ "options": "Warehouse",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting_details_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Details"
+ },
+ {
+ "fieldname": "expense_account",
+ "fieldtype": "Link",
+ "label": "Expense Account",
+ "options": "Account",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "manufacture_section",
+ "fieldtype": "Section Break",
+ "label": "Manufacture"
+ },
+ {
+ "fieldname": "manufacturer",
+ "fieldtype": "Link",
+ "label": "Manufacturer",
+ "options": "Manufacturer"
+ },
+ {
+ "fieldname": "manufacturer_part_no",
+ "fieldtype": "Data",
+ "label": "Manufacturer Part Number"
+ },
+ {
+ "depends_on": "item_code",
+ "fetch_from": "item_code.default_bom",
+ "fetch_if_empty": 1,
+ "fieldname": "bom",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "BOM",
+ "options": "BOM",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "include_exploded_items",
+ "fieldtype": "Check",
+ "label": "Include Exploded Items",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "service_cost_per_qty",
+ "fieldtype": "Currency",
+ "label": "Service Cost Per Qty",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "additional_cost_per_qty",
+ "fieldtype": "Currency",
+ "label": "Additional Cost Per Qty",
+ "read_only": 1
+ },
+ {
+ "fieldname": "rm_cost_per_qty",
+ "fieldtype": "Currency",
+ "label": "Raw Material Cost Per Qty",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "page_break",
+ "fieldtype": "Check",
+ "label": "Page Break",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "section_break_34",
+ "fieldtype": "Section Break"
+ },
+ {
+ "depends_on": "received_qty",
+ "fieldname": "received_qty",
+ "fieldtype": "Float",
+ "label": "Received Qty",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "depends_on": "returned_qty",
+ "fieldname": "returned_qty",
+ "fieldtype": "Float",
+ "label": "Returned Qty",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ }
+ ],
+ "idx": 1,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2023-01-20 23:25:45.363281",
+ "modified_by": "Administrator",
+ "module": "Subcontracting",
+ "name": "Subcontracting Order Item",
+ "naming_rule": "Random",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "search_fields": "item_name",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index e8faa48..f4fd4de 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -262,15 +262,17 @@
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import process_gl_map
+ if not erpnext.is_perpetual_inventory_enabled(self.company):
+ return []
+
gl_entries = []
self.make_item_gl_entries(gl_entries, warehouse_account)
return process_gl_map(gl_entries)
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
- if erpnext.is_perpetual_inventory_enabled(self.company):
- stock_rbnb = self.get_company_default("stock_received_but_not_billed")
- expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
+ stock_rbnb = self.get_company_default("stock_received_but_not_billed")
+ expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
warehouse_with_no_account = []
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index e1e12bd..185ec66 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -38,22 +38,27 @@
if not frappe.has_website_permission(context.doc):
frappe.throw(_("Not Permitted"), frappe.PermissionError)
- # check for the loyalty program of the customer
- customer_loyalty_program = frappe.db.get_value(
- "Customer", context.doc.customer, "loyalty_program"
- )
- if customer_loyalty_program:
- from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
- get_loyalty_program_details_with_points,
+ context.available_loyalty_points = 0.0
+ if context.doc.get("customer"):
+ # check for the loyalty program of the customer
+ customer_loyalty_program = frappe.db.get_value(
+ "Customer", context.doc.customer, "loyalty_program"
)
- loyalty_program_details = get_loyalty_program_details_with_points(
- context.doc.customer, customer_loyalty_program
- )
- context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
+ if customer_loyalty_program:
+ from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
+ get_loyalty_program_details_with_points,
+ )
- # show Make Purchase Invoice button based on permission
- context.show_make_pi_button = frappe.has_permission("Purchase Invoice", "create")
+ loyalty_program_details = get_loyalty_program_details_with_points(
+ context.doc.customer, customer_loyalty_program
+ )
+ context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
+
+ context.show_make_pi_button = False
+ if context.doc.get("supplier"):
+ # show Make Purchase Invoice button based on permission
+ context.show_make_pi_button = frappe.has_permission("Purchase Invoice", "create")
def get_attachments(dt, dn):
diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv
index 45435d8..265e85c 100644
--- a/erpnext/translations/af.csv
+++ b/erpnext/translations/af.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Reëls vir die toepassing van verskillende promosieskemas.,
Shift,verskuiwing,
Show {0},Wys {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Spesiale karakters behalwe "-", "#", ".", "/", "{" En "}" word nie toegelaat in die naamreekse nie",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Spesiale karakters behalwe "-", "#", ".", "/", "{{" En "}}" word nie toegelaat in die naamreekse nie {0}",
Target Details,Teikenbesonderhede,
{0} already has a Parent Procedure {1}.,{0} het reeds 'n ouerprosedure {1}.,
API,API,
diff --git a/erpnext/translations/am.csv b/erpnext/translations/am.csv
index 554b0a5..d131404 100644
--- a/erpnext/translations/am.csv
+++ b/erpnext/translations/am.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,የተለያዩ የማስተዋወቂያ ዘዴዎችን ለመተግበር ህጎች።,
Shift,ቀይር,
Show {0},አሳይ {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",ከ "-" ፣ "#" ፣ "፣" ፣ "/" ፣ "{" እና "}" በስተቀር ልዩ ቁምፊዎች ከመለያ መሰየሚያ አይፈቀድም,
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",ከ "-" ፣ "#" ፣ "፣" ፣ "/" ፣ "{{" እና "}}" በስተቀር ልዩ ቁምፊዎች ከመለያ መሰየሚያ አይፈቀድም {0},
Target Details,የ Detailsላማ ዝርዝሮች።,
{0} already has a Parent Procedure {1}.,{0} ቀድሞውኑ የወላጅ አሰራር ሂደት አለው {1}።,
API,ኤ ፒ አይ,
diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv
index e62f61a..c0da1c4 100644
--- a/erpnext/translations/ar.csv
+++ b/erpnext/translations/ar.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,قواعد تطبيق المخططات الترويجية المختلفة.,
Shift,تحول,
Show {0},عرض {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",الأحرف الخاصة باستثناء "-" ، "#" ، "." ، "/" ، "{" و "}" غير مسموح في سلسلة التسمية,
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",{0} الأحرف الخاصة باستثناء "-" ، "#" ، "." ، "/" ، "{{" و "}}" غير مسموح في سلسلة التسمية,
Target Details,تفاصيل الهدف,
{0} already has a Parent Procedure {1}.,{0} يحتوي بالفعل على إجراء الأصل {1}.,
API,API,
diff --git a/erpnext/translations/bg.csv b/erpnext/translations/bg.csv
index 15278a6..ac6dc78 100644
--- a/erpnext/translations/bg.csv
+++ b/erpnext/translations/bg.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Правила за прилагане на различни промоционални схеми.,
Shift,изместване,
Show {0},Показване на {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Специални символи, с изключение на "-", "#", ".", "/", "{" И "}" не са позволени в именуването на серии",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Специални символи, с изключение на "-", "#", ".", "/", "{{" И "}}" не са позволени в именуването на серии {0}",
Target Details,Детайли за целта,
{0} already has a Parent Procedure {1}.,{0} вече има родителска процедура {1}.,
API,API,
diff --git a/erpnext/translations/bn.csv b/erpnext/translations/bn.csv
index cf09716..52f7b1c 100644
--- a/erpnext/translations/bn.csv
+++ b/erpnext/translations/bn.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,বিভিন্ন প্রচারমূলক স্কিম প্রয়োগ করার নিয়ম।,
Shift,পরিবর্তন,
Show {0},{0} দেখান,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","নামকরণ সিরিজে "-", "#", "।", "/", "{" এবং "}" ব্যতীত বিশেষ অক্ষর অনুমোদিত নয়",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","নামকরণ সিরিজে "-", "#", "।", "/", "{{" এবং "}}" ব্যতীত বিশেষ অক্ষর অনুমোদিত নয় {0}",
Target Details,টার্গেটের বিশদ,
{0} already has a Parent Procedure {1}.,{0} ইতিমধ্যে একটি মূল পদ্ধতি আছে {1}।,
API,এপিআই,
diff --git a/erpnext/translations/bs.csv b/erpnext/translations/bs.csv
index 6ef445a..267434f 100644
--- a/erpnext/translations/bs.csv
+++ b/erpnext/translations/bs.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Pravila za primjenu različitih promotivnih shema.,
Shift,Shift,
Show {0},Prikaži {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Posebni znakovi osim "-", "#", ".", "/", "{" I "}" nisu dozvoljeni u imenovanju serija",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Posebni znakovi osim "-", "#", ".", "/", "{{" I "}}" nisu dozvoljeni u imenovanju serija {0}",
Target Details,Detalji cilja,
{0} already has a Parent Procedure {1}.,{0} već ima roditeljsku proceduru {1}.,
API,API,
diff --git a/erpnext/translations/ca.csv b/erpnext/translations/ca.csv
index 18fa52a..d8c2ef6 100644
--- a/erpnext/translations/ca.csv
+++ b/erpnext/translations/ca.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Normes per aplicar diferents règims promocionals.,
Shift,Majúscules,
Show {0},Mostra {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Caràcters especials, excepte "-", "#", ".", "/", "{" I "}" no estan permesos en nomenar sèries",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Caràcters especials, excepte "-", "#", ".", "/", "{{" I "}}" no estan permesos en nomenar sèries {0}",
Target Details,Detalls de l'objectiu,
{0} already has a Parent Procedure {1}.,{0} ja té un procediment progenitor {1}.,
API,API,
diff --git a/erpnext/translations/cs.csv b/erpnext/translations/cs.csv
index 705e471..7d570bb 100644
--- a/erpnext/translations/cs.csv
+++ b/erpnext/translations/cs.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Pravidla pro uplatňování různých propagačních programů.,
Shift,Posun,
Show {0},Zobrazit {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Zvláštní znaky kromě "-", "#", ".", "/", "{" A "}" nejsou v názvových řadách povoleny",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Zvláštní znaky kromě "-", "#", ".", "/", "{{" A "}}" nejsou v názvových řadách povoleny {0}",
Target Details,Podrobnosti o cíli,
{0} already has a Parent Procedure {1}.,{0} již má rodičovský postup {1}.,
API,API,
diff --git a/erpnext/translations/da.csv b/erpnext/translations/da.csv
index c0d0146..16b2e87 100644
--- a/erpnext/translations/da.csv
+++ b/erpnext/translations/da.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Regler for anvendelse af forskellige salgsfremmende ordninger.,
Shift,Flytte,
Show {0},Vis {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Specialtegn undtagen "-", "#", ".", "/", "{" Og "}" er ikke tilladt i navngivningsserier",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Specialtegn undtagen "-", "#", ".", "/", "{{" Og "}}" er ikke tilladt i navngivningsserier {0}",
Target Details,Måldetaljer,
{0} already has a Parent Procedure {1}.,{0} har allerede en overordnet procedure {1}.,
API,API,
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 1014e27..5a0a863 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -3546,7 +3546,7 @@
Rules for applying different promotional schemes.,Regeln für die Anwendung verschiedener Werbemaßnahmen.,
Shift,Verschiebung,
Show {0},{0} anzeigen,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Sonderzeichen außer "-", "#", ".", "/", "{" Und "}" sind bei der Benennung von Serien nicht zulässig",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Sonderzeichen außer "-", "#", ".", "/", "{{" Und "}}" sind bei der Benennung von Serien nicht zulässig {0}",
Target Details,Zieldetails,
{0} already has a Parent Procedure {1}.,{0} hat bereits eine übergeordnete Prozedur {1}.,
API,API,
diff --git a/erpnext/translations/el.csv b/erpnext/translations/el.csv
index acf5db5..06b8060 100644
--- a/erpnext/translations/el.csv
+++ b/erpnext/translations/el.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Κανόνες εφαρμογής διαφορετικών προγραμμάτων προώθησης.,
Shift,Βάρδια,
Show {0},Εμφάνιση {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Ειδικοί χαρακτήρες εκτός από "-", "#", ".", "/", "" Και "}" δεν επιτρέπονται στη σειρά ονομασίας",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Ειδικοί χαρακτήρες εκτός από "-", "#", ".", "/", "{" Και "}}" δεν επιτρέπονται στη σειρά ονομασίας {0}",
Target Details,Στοιχεία στόχου,
{0} already has a Parent Procedure {1}.,{0} έχει ήδη μια διαδικασία γονέα {1}.,
API,API,
diff --git a/erpnext/translations/es.csv b/erpnext/translations/es.csv
index 0f2259d..b216b86 100644
--- a/erpnext/translations/es.csv
+++ b/erpnext/translations/es.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Reglas para aplicar diferentes esquemas promocionales.,
Shift,Cambio,
Show {0},Mostrar {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Caracteres especiales excepto "-", "#", ".", "/", "{" Y "}" no están permitidos en las series de nombres",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Caracteres especiales excepto "-", "#", ".", "/", "{{" Y "}}" no están permitidos en las series de nombres {0}",
Target Details,Detalles del objetivo,
{0} already has a Parent Procedure {1}.,{0} ya tiene un Procedimiento principal {1}.,
API,API,
diff --git a/erpnext/translations/et.csv b/erpnext/translations/et.csv
index ba32187..5d67d81 100644
--- a/erpnext/translations/et.csv
+++ b/erpnext/translations/et.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Erinevate reklaamiskeemide rakenduseeskirjad.,
Shift,Vahetus,
Show {0},Kuva {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Erimärgid, välja arvatud "-", "#", ".", "/", "{" Ja "}" pole sarjade nimetamisel lubatud",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Erimärgid, välja arvatud "-", "#", ".", "/", "{{" Ja "}}" pole sarjade nimetamisel lubatud {0}",
Target Details,Sihtkoha üksikasjad,
{0} already has a Parent Procedure {1}.,{0} juba on vanemamenetlus {1}.,
API,API,
diff --git a/erpnext/translations/fa.csv b/erpnext/translations/fa.csv
index 4a7c979..040034d 100644
--- a/erpnext/translations/fa.csv
+++ b/erpnext/translations/fa.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,قوانین استفاده از طرح های تبلیغاتی مختلف.,
Shift,تغییر مکان,
Show {0},نمایش {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",کاراکترهای خاص به جز "-" ، "#" ، "." ، "/" ، "{" و "}" در سریال نامگذاری مجاز نیستند,
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",{0} کاراکترهای خاص به جز "-" ، "#" ، "." ، "/" ، "{{" و "}}" در سریال نامگذاری مجاز نیستند,
Target Details,جزئیات هدف,
{0} already has a Parent Procedure {1}.,{0} در حال حاضر یک روش والدین {1} دارد.,
API,API,
diff --git a/erpnext/translations/fi.csv b/erpnext/translations/fi.csv
index 29eb567..27ea3b8 100644
--- a/erpnext/translations/fi.csv
+++ b/erpnext/translations/fi.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Säännöt erilaisten myynninedistämisjärjestelmien soveltamisesta.,
Shift,Siirtää,
Show {0},Näytä {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Erikoismerkit paitsi "-", "#", ".", "/", "{" Ja "}" eivät ole sallittuja nimeämissarjoissa",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Erikoismerkit paitsi "-", "#", ".", "/", "{{" Ja "}}" eivät ole sallittuja nimeämissarjoissa {0}",
Target Details,Kohteen yksityiskohdat,
{0} already has a Parent Procedure {1}.,{0}: llä on jo vanhempainmenettely {1}.,
API,API,
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 3ba5ade..8367afd 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Règles d'application de différents programmes promotionnels.,
Shift,Décalage,
Show {0},Montrer {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Caractères spéciaux sauf "-", "#", ".", "/", "{" Et "}" non autorisés dans les séries de nommage",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Caractères spéciaux sauf "-", "#", ".", "/", "{{" Et "}}" non autorisés dans les séries de nommage {0}",
Target Details,Détails de la cible,
{0} already has a Parent Procedure {1}.,{0} a déjà une procédure parent {1}.,
API,API,
diff --git a/erpnext/translations/gu.csv b/erpnext/translations/gu.csv
index 5c2b520..97adac9 100644
--- a/erpnext/translations/gu.csv
+++ b/erpnext/translations/gu.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,વિવિધ પ્રમોશનલ યોજનાઓ લાગુ કરવાના નિયમો.,
Shift,પાળી,
Show {0},બતાવો {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", ".", "/", "{" અને "}" સિવાયના વિશેષ અક્ષરો નામકરણ શ્રેણીમાં મંજૂરી નથી",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", ".", "/", "{{" અને "}}" સિવાયના વિશેષ અક્ષરો નામકરણ શ્રેણીમાં મંજૂરી નથી {0}",
Target Details,લક્ષ્યાંક વિગતો,
{0} already has a Parent Procedure {1}.,{0} પાસે પહેલેથી જ પિતૃ કાર્યવાહી છે {1}.,
API,API,
diff --git a/erpnext/translations/he.csv b/erpnext/translations/he.csv
index 29e6f6a..22b2522 100644
--- a/erpnext/translations/he.csv
+++ b/erpnext/translations/he.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,כללים ליישום תוכניות קידום מכירות שונות.,
Shift,מִשׁמֶרֶת,
Show {0},הצג את {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","תווים מיוחדים למעט "-", "#", ".", "/", "{" ו- "}" אינם מורשים בסדרות שמות",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","{0} תווים מיוחדים למעט "-", "#", ".", "/", "{{" ו- "}}" אינם מורשים בסדרות שמות",
Target Details,פרטי יעד,
{0} already has a Parent Procedure {1}.,{0} כבר יש נוהל הורים {1}.,
API,ממשק API,
diff --git a/erpnext/translations/hi.csv b/erpnext/translations/hi.csv
index c385fc6..ca41cf3 100644
--- a/erpnext/translations/hi.csv
+++ b/erpnext/translations/hi.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,विभिन्न प्रचार योजनाओं को लागू करने के लिए नियम।,
Shift,खिसक जाना,
Show {0},{0} दिखाएं,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", "।", "/", "{" और "}" को छोड़कर विशेष वर्ण श्रृंखला में अनुमति नहीं है",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", "।", "/", "{{" और "}}" को छोड़कर विशेष वर्ण श्रृंखला {0} में अनुमति नहीं है",
Target Details,लक्ष्य विवरण,
{0} already has a Parent Procedure {1}.,{0} पहले से ही एक पेरेंट प्रोसीजर {1} है।,
API,एपीआई,
diff --git a/erpnext/translations/hr.csv b/erpnext/translations/hr.csv
index a544e98..319b80b 100644
--- a/erpnext/translations/hr.csv
+++ b/erpnext/translations/hr.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Pravila za primjenu različitih promotivnih shema.,
Shift,smjena,
Show {0},Prikaži {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Posebni znakovi osim "-", "#", ".", "/", "{" I "}" nisu dopušteni u imenovanju serija",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Posebni znakovi osim "-", "#", ".", "/", "{{" I "}}" nisu dopušteni u imenovanju serija {0}",
Target Details,Pojedinosti cilja,
{0} already has a Parent Procedure {1}.,{0} već ima roditeljski postupak {1}.,
API,API,
diff --git a/erpnext/translations/hu.csv b/erpnext/translations/hu.csv
index 29f347e..0664728 100644
--- a/erpnext/translations/hu.csv
+++ b/erpnext/translations/hu.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Különböző promóciós rendszerek alkalmazásának szabályai.,
Shift,Váltás,
Show {0},Mutasd {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Speciális karakterek, kivéve "-", "#", ".", "/", "{" És "}", a sorozatok elnevezése nem megengedett",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Speciális karakterek, kivéve "-", "#", ".", "/", "{{" És "}}", a sorozatok elnevezése nem megengedett {0}",
Target Details,Cél részletei,
{0} already has a Parent Procedure {1}.,A (z) {0} már rendelkezik szülői eljárással {1}.,
API,API,
diff --git a/erpnext/translations/id.csv b/erpnext/translations/id.csv
index 7175ad2..1e50747 100644
--- a/erpnext/translations/id.csv
+++ b/erpnext/translations/id.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Aturan untuk menerapkan berbagai skema promosi.,
Shift,Bergeser,
Show {0},Tampilkan {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Karakter Khusus kecuali "-", "#", ".", "/", "{" Dan "}" tidak diizinkan dalam rangkaian penamaan",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Karakter Khusus kecuali "-", "#", ".", "/", "{{" Dan "}}" tidak diizinkan dalam rangkaian penamaan {0}",
Target Details,Detail Target,
{0} already has a Parent Procedure {1}.,{0} sudah memiliki Prosedur Induk {1}.,
API,API,
diff --git a/erpnext/translations/is.csv b/erpnext/translations/is.csv
index 5f56aff..c20c21e 100644
--- a/erpnext/translations/is.csv
+++ b/erpnext/translations/is.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Reglur um beitingu mismunandi kynningarkerfa.,
Shift,Vakt,
Show {0},Sýna {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Sérstafir nema "-", "#", ".", "/", "{" Og "}" ekki leyfðar í nafngiftiröð",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Sérstafir nema "-", "#", ".", "/", "{{" Og "}}" ekki leyfðar í nafngiftiröð {0}",
Target Details,Upplýsingar um markmið,
{0} already has a Parent Procedure {1}.,{0} er þegar með foreldraferli {1}.,
API,API,
diff --git a/erpnext/translations/it.csv b/erpnext/translations/it.csv
index 3a1d73f..3d15d55 100644
--- a/erpnext/translations/it.csv
+++ b/erpnext/translations/it.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Regole per l'applicazione di diversi schemi promozionali.,
Shift,Cambio,
Show {0},Mostra {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Caratteri speciali tranne "-", "#", ".", "/", "{" E "}" non consentiti nelle serie di nomi",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Caratteri speciali tranne "-", "#", ".", "/", "{{" E "}}" non consentiti nelle serie di nomi {0}",
Target Details,Dettagli target,
{0} already has a Parent Procedure {1}.,{0} ha già una procedura padre {1}.,
API,API,
diff --git a/erpnext/translations/ja.csv b/erpnext/translations/ja.csv
index 6e2eaae..a11a9a1 100644
--- a/erpnext/translations/ja.csv
+++ b/erpnext/translations/ja.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,さまざまなプロモーションスキームを適用するための規則。,
Shift,シフト,
Show {0},{0}を表示,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series"," - "、 "#"、 "。"、 "/"、 "{"、および "}"以外の特殊文字は、一連の名前付けでは使用できません,
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}"," - "、 "#"、 "。"、 "/"、 "{{"、および "}}"以外の特殊文字は、一連の名前付けでは使用できません {0},
Target Details,ターゲット詳細,
{0} already has a Parent Procedure {1}.,{0}にはすでに親プロシージャー{1}があります。,
API,API,
diff --git a/erpnext/translations/km.csv b/erpnext/translations/km.csv
index e2a528c..bd70595 100644
--- a/erpnext/translations/km.csv
+++ b/erpnext/translations/km.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,វិធានសម្រាប់អនុវត្តគម្រោងផ្សព្វផ្សាយផ្សេងៗគ្នា។,
Shift,ប្តូរ។,
Show {0},បង្ហាញ {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","តួអក្សរពិសេសលើកលែងតែ "-", "#", "។ ", "/", "{" និង "}" មិនត្រូវបានអនុញ្ញាតក្នុងស៊េរីដាក់ឈ្មោះ",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","តួអក្សរពិសេសលើកលែងតែ "-", "#", "។ ", "/", "{{" និង "}}" មិនត្រូវបានអនុញ្ញាតក្នុងស៊េរីដាក់ឈ្មោះ {0}",
Target Details,ព័ត៌មានលម្អិតគោលដៅ។,
{0} already has a Parent Procedure {1}.,{0} មាននីតិវិធីឪពុកម្តាយរួចហើយ {1} ។,
API,API,
diff --git a/erpnext/translations/kn.csv b/erpnext/translations/kn.csv
index 4a9173d..7572a09 100644
--- a/erpnext/translations/kn.csv
+++ b/erpnext/translations/kn.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,ವಿಭಿನ್ನ ಪ್ರಚಾರ ಯೋಜನೆಗಳನ್ನು ಅನ್ವಯಿಸುವ ನಿಯಮಗಳು.,
Shift,ಶಿಫ್ಟ್,
Show {0},{0} ತೋರಿಸು,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", ".", "/", "{" ಮತ್ತು "}" ಹೊರತುಪಡಿಸಿ ವಿಶೇಷ ಅಕ್ಷರಗಳನ್ನು ಹೆಸರಿಸುವ ಸರಣಿಯಲ್ಲಿ ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", ".", "/", "{{" ಮತ್ತು "}}" ಹೊರತುಪಡಿಸಿ ವಿಶೇಷ ಅಕ್ಷರಗಳನ್ನು ಹೆಸರಿಸುವ ಸರಣಿಯಲ್ಲಿ ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ {0}",
Target Details,ಗುರಿ ವಿವರಗಳು,
{0} already has a Parent Procedure {1}.,{0} ಈಗಾಗಲೇ ಪೋಷಕ ವಿಧಾನವನ್ನು ಹೊಂದಿದೆ {1}.,
API,API,
diff --git a/erpnext/translations/ko.csv b/erpnext/translations/ko.csv
index c051b07..b873b73 100644
--- a/erpnext/translations/ko.csv
+++ b/erpnext/translations/ko.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,다양한 홍보 계획을 적용하기위한 규칙.,
Shift,시프트,
Show {0},{0} 표시,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","이름 계열에 허용되지 않는 "-", "#", ".", "/", "{"및 "}"을 제외한 특수 문자",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","이름 계열에 허용되지 않는 "-", "#", ".", "/", "{{"및 "}}"을 제외한 특수 문자 {0}",
Target Details,대상 세부 정보,
{0} already has a Parent Procedure {1}.,{0}에 이미 상위 절차 {1}이 있습니다.,
API,API,
diff --git a/erpnext/translations/ku.csv b/erpnext/translations/ku.csv
index 6962ea1..89e12c0 100644
--- a/erpnext/translations/ku.csv
+++ b/erpnext/translations/ku.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Qanûnên ji bo bicihanîna nexşeyên cûda yên danasînê,
Shift,Tarloqî,
Show {0},Show {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Di tîpa navnasî de ji bilî "-", "#", ".", "/", "{" Û "}" tîpên Taybet",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Di tîpa navnasî de ji bilî "-", "#", ".", "/", "{{" Û "}}" tîpên Taybet {0}",
Target Details,Hûrgulên armancê,
{0} already has a Parent Procedure {1}.,{0} ji berê ve heye Parent Procedure {1}.,
API,API,
diff --git a/erpnext/translations/lo.csv b/erpnext/translations/lo.csv
index b61476c..778a59b 100644
--- a/erpnext/translations/lo.csv
+++ b/erpnext/translations/lo.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,ກົດລະບຽບໃນການ ນຳ ໃຊ້ແຜນການໂຄສະນາທີ່ແຕກຕ່າງກັນ.,
Shift,ປ່ຽນ,
Show {0},ສະແດງ {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","ຕົວລະຄອນພິເສດຍົກເວັ້ນ "-", "#", ".", "/", "{" ແລະ "}" ບໍ່ໄດ້ຖືກອະນຸຍາດໃນຊຸດຊື່",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","ຕົວລະຄອນພິເສດຍົກເວັ້ນ "-", "#", ".", "/", "{{" ແລະ "}}" ບໍ່ໄດ້ຖືກອະນຸຍາດໃນຊຸດຊື່ {0}",
Target Details,ລາຍລະອຽດເປົ້າ ໝາຍ,
{0} already has a Parent Procedure {1}.,{0} ມີຂັ້ນຕອນການເປັນພໍ່ແມ່ {1} ແລ້ວ.,
API,API,
diff --git a/erpnext/translations/lt.csv b/erpnext/translations/lt.csv
index 78571f9..4721ce4 100644
--- a/erpnext/translations/lt.csv
+++ b/erpnext/translations/lt.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Skirtingų reklamos schemų taikymo taisyklės.,
Shift,Pamaina,
Show {0},Rodyti {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Specialieji simboliai, išskyrus „-“, „#“, „.“, „/“, „{“ Ir „}“, neleidžiami įvardyti serijomis",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Specialieji simboliai, išskyrus "-", "#", "।", "/", "{{" Ir "}}", neleidžiami įvardyti serijomis {0}",
Target Details,Tikslinė informacija,
{0} already has a Parent Procedure {1}.,{0} jau turi tėvų procedūrą {1}.,
API,API,
diff --git a/erpnext/translations/lv.csv b/erpnext/translations/lv.csv
index cbf0485..b8499b2 100644
--- a/erpnext/translations/lv.csv
+++ b/erpnext/translations/lv.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Noteikumi dažādu reklāmas shēmu piemērošanai.,
Shift,Maiņa,
Show {0},Rādīt {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Speciālās rakstzīmes, izņemot "-", "#", ".", "/", "{" Un "}", kas nav atļautas nosaukuma sērijās",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Speciālās rakstzīmes, izņemot "-", "#", ".", "/", "{{" Un "}}", kas nav atļautas nosaukuma sērijās {0}",
Target Details,Mērķa informācija,
{0} already has a Parent Procedure {1}.,{0} jau ir vecāku procedūra {1}.,
API,API,
diff --git a/erpnext/translations/mk.csv b/erpnext/translations/mk.csv
index 7008025..8ecae03 100644
--- a/erpnext/translations/mk.csv
+++ b/erpnext/translations/mk.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Правила за примена на различни промотивни шеми.,
Shift,Смена,
Show {0},Покажи {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Не се дозволени специјални карактери освен "-", "#", ".", "/", "{" И "}" во сериите за именување",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Не се дозволени специјални карактери освен "-", "#", ".", "/", "{{" И "}}" во сериите за именување {0}",
Target Details,Цели детали,
{0} already has a Parent Procedure {1}.,{0} веќе има Матична постапка {1}.,
API,API,
diff --git a/erpnext/translations/ml.csv b/erpnext/translations/ml.csv
index f917969..f649e6c 100644
--- a/erpnext/translations/ml.csv
+++ b/erpnext/translations/ml.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,വ്യത്യസ്ത പ്രമോഷണൽ സ്കീമുകൾ പ്രയോഗിക്കുന്നതിനുള്ള നിയമങ്ങൾ.,
Shift,ഷിഫ്റ്റ്,
Show {0},{0} കാണിക്കുക,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", ".", "/", "{", "}" എന്നിവ ഒഴികെയുള്ള പ്രത്യേക പ്രതീകങ്ങൾ നാമകരണ ശ്രേണിയിൽ അനുവദനീയമല്ല",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", ".", "/", "{{", "}}" എന്നിവ ഒഴികെയുള്ള പ്രത്യേക പ്രതീകങ്ങൾ നാമകരണ ശ്രേണിയിൽ അനുവദനീയമല്ല {0}",
Target Details,ടാർഗെറ്റ് വിശദാംശങ്ങൾ,
{0} already has a Parent Procedure {1}.,{0} ന് ഇതിനകം ഒരു രക്ഷാകർതൃ നടപടിക്രമം ഉണ്ട് {1}.,
API,API,
diff --git a/erpnext/translations/mr.csv b/erpnext/translations/mr.csv
index 9c41ce6..38effc1 100644
--- a/erpnext/translations/mr.csv
+++ b/erpnext/translations/mr.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,वेगवेगळ्या जाहिरात योजना लागू करण्याचे नियम.,
Shift,शिफ्ट,
Show {0},दर्शवा {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", ".", "/", "{" आणि "}" वगळता विशिष्ट वर्णांना नामांकन मालिकेमध्ये परवानगी नाही",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", ".", "/", "{{" आणि "}}" वगळता विशिष्ट वर्णांना नामांकन मालिकेमध्ये परवानगी नाही {0}",
Target Details,लक्ष्य तपशील,
{0} already has a Parent Procedure {1}.,{0} कडे आधीपासूनच पालक प्रक्रिया आहे {1}.,
API,API,
diff --git a/erpnext/translations/ms.csv b/erpnext/translations/ms.csv
index 1483844..4ee650b 100644
--- a/erpnext/translations/ms.csv
+++ b/erpnext/translations/ms.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Kaedah untuk memohon skim promosi yang berbeza.,
Shift,Shift,
Show {0},Tunjukkan {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Watak Khas kecuali "-", "#", ".", "/", "{" Dan "}" tidak dibenarkan dalam siri penamaan",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Watak Khas kecuali "-", "#", ".", "/", "{{" Dan "}}" tidak dibenarkan dalam siri penamaan {0}",
Target Details,Butiran Sasaran,
{0} already has a Parent Procedure {1}.,{0} sudah mempunyai Tatacara Ibu Bapa {1}.,
API,API,
diff --git a/erpnext/translations/my.csv b/erpnext/translations/my.csv
index d15ec1e..f0d216b 100644
--- a/erpnext/translations/my.csv
+++ b/erpnext/translations/my.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,ကွဲပြားခြားနားသောပရိုမိုးရှင်းအစီအစဉ်များလျှောက်ထားမှုအတွက်စည်းကမ်းများ။,
Shift,အဆိုင်း,
Show {0},Show ကို {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","မှလွဲ. အထူးဇာတ်ကောင် "-" "။ ", "#", "/", "{" နှင့် "}" စီးရီးနာမည်အတွက်ခွင့်မပြု",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","မှလွဲ. အထူးဇာတ်ကောင် "-" "။ ", "#", "/", "{{" နှင့် "}}" စီးရီးနာမည်အတွက်ခွင့်မပြု {0}",
Target Details,ပစ်မှတ်အသေးစိတ်,
{0} already has a Parent Procedure {1}.,{0} ပြီးသားမိဘလုပ်ထုံးလုပ်နည်း {1} ရှိပါတယ်။,
API,API ကို,
diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv
index fbadc02..6ec43a0 100644
--- a/erpnext/translations/nl.csv
+++ b/erpnext/translations/nl.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Regels voor het toepassen van verschillende promotieregelingen.,
Shift,Verschuiving,
Show {0},Toon {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Speciale tekens behalve "-", "#", ".", "/", "{" En "}" niet toegestaan in naamgevingsreeks",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Speciale tekens behalve "-", "#", ".", "/", "{{" En "}}" niet toegestaan in naamgevingsreeks {0}",
Target Details,Doelgegevens,
{0} already has a Parent Procedure {1}.,{0} heeft al een ouderprocedure {1}.,
API,API,
diff --git a/erpnext/translations/no.csv b/erpnext/translations/no.csv
index 150e5ca..df87e81 100644
--- a/erpnext/translations/no.csv
+++ b/erpnext/translations/no.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Regler for anvendelse av forskjellige kampanjer.,
Shift,Skifte,
Show {0},Vis {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Spesialtegn unntatt "-", "#", ".", "/", "{" Og "}" ikke tillatt i navneserier",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Spesialtegn unntatt "-", "#", ".", "/", "{{" Og "}}" ikke tillatt i navneserier {0}",
Target Details,Måldetaljer,
{0} already has a Parent Procedure {1}.,{0} har allerede en foreldreprosedyre {1}.,
API,API,
diff --git a/erpnext/translations/pl.csv b/erpnext/translations/pl.csv
index 8340b72..be81e29 100644
--- a/erpnext/translations/pl.csv
+++ b/erpnext/translations/pl.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Zasady stosowania różnych programów promocyjnych.,
Shift,Przesunięcie,
Show {0},Pokaż {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Znaki specjalne z wyjątkiem „-”, „#”, „.”, „/”, „{” I „}” niedozwolone w serii nazw",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Znaki specjalne z wyjątkiem "-", "#", "।", "/", "{{" I "}}" niedozwolone w serii nazw {0}",
Target Details,Szczegóły celu,
{0} already has a Parent Procedure {1}.,{0} ma już procedurę nadrzędną {1}.,
API,API,
diff --git a/erpnext/translations/ps.csv b/erpnext/translations/ps.csv
index 1dcaf48..68add60 100644
--- a/erpnext/translations/ps.csv
+++ b/erpnext/translations/ps.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,د مختلف پروموشنل سکیمونو پلي کولو قواعد.,
Shift,شفټ,
Show {0},ښودل {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",ځانګړي نومونه د "-" ، "#" ، "." ، "/" ، "{" او "}" نوم لیکلو کې اجازه نه لري,
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",{0} ځانګړي نومونه د "-" ، "#" ، "." ، "/" ، "{{" او "}}" نوم لیکلو کې اجازه نه لري,
Target Details,د هدف توضیحات,
{0} already has a Parent Procedure {1}.,{0} د مخه د والدین پروسیجر {1} لري.,
API,API,
diff --git a/erpnext/translations/pt-BR.csv b/erpnext/translations/pt-BR.csv
index 957cb75..cc1c6af 100644
--- a/erpnext/translations/pt-BR.csv
+++ b/erpnext/translations/pt-BR.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Regras para aplicar diferentes esquemas promocionais.,
Shift,Mudança,
Show {0},Mostrar {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Caracteres especiais, exceto "-", "#", ".", "/", "{" e "}" não permitidos na série de nomenclatura",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Caracteres especiais, exceto "-", "#", ".", "/", "{{" e "}}" não permitidos na série de nomenclatura {0}",
Target Details,Detalhes do Alvo,
{0} already has a Parent Procedure {1}.,{0} já tem um procedimento pai {1}.,
API,API,
diff --git a/erpnext/translations/pt.csv b/erpnext/translations/pt.csv
index 3b8a0a0..43bf227 100644
--- a/erpnext/translations/pt.csv
+++ b/erpnext/translations/pt.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Regras para aplicar diferentes esquemas promocionais.,
Shift,Mudança,
Show {0},Mostrar {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Caracteres especiais, exceto "-", "#", ".", "/", "{" E "}" não permitidos na série de nomenclatura",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Caracteres especiais, exceto "-", "#", ".", "/", "{{" E "}}" não permitidos na série de nomenclatura {0}",
Target Details,Detalhes do Alvo,
{0} already has a Parent Procedure {1}.,{0} já tem um procedimento pai {1}.,
API,API,
diff --git a/erpnext/translations/ro.csv b/erpnext/translations/ro.csv
index 643b8c5..3aab431 100644
--- a/erpnext/translations/ro.csv
+++ b/erpnext/translations/ro.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Reguli pentru aplicarea diferitelor scheme promoționale.,
Shift,Schimb,
Show {0},Afișați {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Caractere speciale, cu excepția "-", "#", ".", "/", "{" Și "}" nu sunt permise în numirea seriei",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Caractere speciale, cu excepția "-", "#", ".", "/", "{{" Și "}}" nu sunt permise în numirea seriei {0}",
Target Details,Detalii despre țintă,
{0} already has a Parent Procedure {1}.,{0} are deja o procedură părinte {1}.,
API,API-ul,
diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv
index 5f3af77..662346f 100644
--- a/erpnext/translations/ru.csv
+++ b/erpnext/translations/ru.csv
@@ -3535,7 +3535,7 @@
Rules for applying different promotional schemes.,Правила применения разных рекламных схем.,
Shift,Сдвиг,
Show {0},Показать {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Специальные символы, кроме ""-"", ""#"", ""."", ""/"", ""{"" и ""}"", не допускаются в серийных номерах",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Специальные символы, кроме "-", "#", "।", "/", "{{" и "}}", не допускаются в серийных номерах {0}",
Target Details,Детали цели,
{0} already has a Parent Procedure {1}.,{0} уже имеет родительскую процедуру {1}.,
API,API,
diff --git a/erpnext/translations/rw.csv b/erpnext/translations/rw.csv
index 6459139..6c2b5dd 100644
--- a/erpnext/translations/rw.csv
+++ b/erpnext/translations/rw.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Amategeko yo gukoresha gahunda zitandukanye zo kwamamaza.,
Shift,Shift,
Show {0},Erekana {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Inyuguti zidasanzwe usibye "-", "#", ".", "/", "{" Na "}" ntibyemewe mu ruhererekane rwo kwita izina",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Inyuguti zidasanzwe usibye "-", "#", ".", "/", "{{" Na "}}" ntibyemewe mu ruhererekane rwo kwita izina {0}",
Target Details,Intego Ibisobanuro,
{0} already has a Parent Procedure {1}.,{0} isanzwe ifite uburyo bwababyeyi {1}.,
API,API,
diff --git a/erpnext/translations/si.csv b/erpnext/translations/si.csv
index 690c473..5b78235 100644
--- a/erpnext/translations/si.csv
+++ b/erpnext/translations/si.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,විවිධ ප්රවර්ධන යෝජනා ක්රම යෙදීම සඳහා නීති.,
Shift,මාරුව,
Show {0},{0 Show පෙන්වන්න,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", ".", "/", "{" සහ "}" හැර විශේෂ අක්ෂර නම් කිරීමේ ශ්රේණියේ අවසර නැත",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", ".", "/", "{{" සහ "}}" හැර විශේෂ අක්ෂර නම් කිරීමේ ශ්රේණියේ අවසර නැත {0}",
Target Details,ඉලක්ක විස්තර,
{0} already has a Parent Procedure {1}.,{0} දැනටමත් දෙමාපිය ක්රියා පටිපාටියක් ඇත {1}.,
API,API,
diff --git a/erpnext/translations/sk.csv b/erpnext/translations/sk.csv
index cb4a7fe..446e0be 100644
--- a/erpnext/translations/sk.csv
+++ b/erpnext/translations/sk.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Pravidlá uplatňovania rôznych propagačných programov.,
Shift,smena,
Show {0},Zobraziť {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Špeciálne znaky s výnimkou „-“, „#“, „.“, „/“, „{“ A „}“ nie sú v názvových sériách povolené.",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Špeciálne znaky s výnimkou "-", "#", "।", "/", "{{" A "}}" nie sú v názvových sériách povolené {0}.",
Target Details,Podrobnosti o cieli,
{0} already has a Parent Procedure {1}.,{0} už má rodičovský postup {1}.,
API,API,
diff --git a/erpnext/translations/sl.csv b/erpnext/translations/sl.csv
index 8beec6b..8b8ed01 100644
--- a/erpnext/translations/sl.csv
+++ b/erpnext/translations/sl.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Pravila za uporabo različnih promocijskih shem.,
Shift,Shift,
Show {0},Prikaži {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Posebni znaki, razen "-", "#", ".", "/", "{" In "}" v poimenovanju ni dovoljen",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Posebni znaki, razen "-", "#", ".", "/", "{{" In "}}" v poimenovanju ni dovoljen {0}",
Target Details,Podrobnosti cilja,
{0} already has a Parent Procedure {1}.,{0} že ima nadrejeni postopek {1}.,
API,API,
diff --git a/erpnext/translations/sq.csv b/erpnext/translations/sq.csv
index 05aefa3..6f4f8e0 100644
--- a/erpnext/translations/sq.csv
+++ b/erpnext/translations/sq.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Rregulla për aplikimin e skemave të ndryshme promovuese.,
Shift,ndryshim,
Show {0},Trego {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Karaktere speciale përveç "-", "#", ".", "/", "{" Dhe "}" nuk lejohen në seritë emërtuese",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Karaktere speciale përveç "-", "#", ".", "/", "{{" Dhe "}}" nuk lejohen në seritë emërtuese {0}",
Target Details,Detaje të synuara,
{0} already has a Parent Procedure {1}.,{0} tashmë ka një procedurë prindërore {1}.,
API,API,
diff --git a/erpnext/translations/sr.csv b/erpnext/translations/sr.csv
index b507f74..853c6f3 100644
--- a/erpnext/translations/sr.csv
+++ b/erpnext/translations/sr.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Правила за примену различитих промотивних шема.,
Shift,Смена,
Show {0},Прикажи {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Посебни знакови осим "-", "#", ".", "/", "{" И "}" нису дозвољени у именовању серија",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Посебни знакови осим "-", "#", ".", "/", "{{" И "}}" нису дозвољени у именовању серија {0}",
Target Details,Детаљи циља,
{0} already has a Parent Procedure {1}.,{0} већ има родитељску процедуру {1}.,
API,АПИ,
diff --git a/erpnext/translations/sv.csv b/erpnext/translations/sv.csv
index 57e0279..2a4d6b1 100644
--- a/erpnext/translations/sv.csv
+++ b/erpnext/translations/sv.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Regler för tillämpning av olika kampanjprogram.,
Shift,Flytta,
Show {0},Visa {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Specialtecken utom "-", "#", ".", "/", "{" Och "}" är inte tillåtna i namnserien",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Specialtecken utom "-", "#", ".", "/", "{{" Och "}}" är inte tillåtna i namnserien {0}",
Target Details,Måldetaljer,
{0} already has a Parent Procedure {1}.,{0} har redan en överordnad procedur {1}.,
API,API,
diff --git a/erpnext/translations/sw.csv b/erpnext/translations/sw.csv
index 3595727..234d33e 100644
--- a/erpnext/translations/sw.csv
+++ b/erpnext/translations/sw.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Sheria za kutumia miradi tofauti ya uendelezaji.,
Shift,Shift,
Show {0},Onyesha {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Tabia maalum isipokuwa "-", "#", ".", "/", "{" Na "}" hairuhusiwi katika kutaja mfululizo",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Tabia maalum isipokuwa "-", "#", ".", "/", "{{" Na "}}" hairuhusiwi katika kutaja mfululizo {0}",
Target Details,Maelezo ya Lengo,
{0} already has a Parent Procedure {1}.,{0} tayari ina Utaratibu wa Mzazi {1}.,
API,API,
diff --git a/erpnext/translations/ta.csv b/erpnext/translations/ta.csv
index 100f0e9..e7384b3 100644
--- a/erpnext/translations/ta.csv
+++ b/erpnext/translations/ta.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,வெவ்வேறு விளம்பர திட்டங்களைப் பயன்படுத்துவதற்கான விதிகள்.,
Shift,ஷிப்ட்,
Show {0},{0 Show ஐக் காட்டு,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", ".", "/", "{" மற்றும் "}" தவிர சிறப்பு எழுத்துக்கள் பெயரிடும் தொடரில் அனுமதிக்கப்படவில்லை",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", ".", "/", "{{" மற்றும் "}}" தவிர சிறப்பு எழுத்துக்கள் பெயரிடும் தொடரில் அனுமதிக்கப்படவில்லை {0}",
Target Details,இலக்கு விவரங்கள்,
{0} already has a Parent Procedure {1}.,{0} ஏற்கனவே பெற்றோர் நடைமுறை {1 has ஐக் கொண்டுள்ளது.,
API,ஏபிஐ,
diff --git a/erpnext/translations/te.csv b/erpnext/translations/te.csv
index 047d077..cd14a77 100644
--- a/erpnext/translations/te.csv
+++ b/erpnext/translations/te.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,విభిన్న ప్రచార పథకాలను వర్తింపజేయడానికి నియమాలు.,
Shift,మార్పు,
Show {0},{0 Show చూపించు,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", ".", "/", "{" మరియు "}" మినహా ప్రత్యేక అక్షరాలు పేరు పెట్టే సిరీస్లో అనుమతించబడవు",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", ".", "/", "{{" మరియు "}}" మినహా ప్రత్యేక అక్షరాలు పేరు పెట్టే సిరీస్లో అనుమతించబడవు {0}",
Target Details,లక్ష్య వివరాలు,
{0} already has a Parent Procedure {1}.,{0} ఇప్పటికే తల్లిదండ్రుల విధానం {1 has ను కలిగి ఉంది.,
API,API,
diff --git a/erpnext/translations/th.csv b/erpnext/translations/th.csv
index 71233ec..4ab59bc 100644
--- a/erpnext/translations/th.csv
+++ b/erpnext/translations/th.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,กฎสำหรับการใช้รูปแบบการส่งเสริมการขายต่าง ๆ,
Shift,เปลี่ยน,
Show {0},แสดง {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","ห้ามใช้อักขระพิเศษยกเว้น "-", "#", ".", "/", "{" และ "}" ในซีรี่ส์",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","ห้ามใช้อักขระพิเศษยกเว้น "-", "#", ".", "/", "{{" และ "}}" ในซีรี่ส์ {0}",
Target Details,รายละเอียดเป้าหมาย,
{0} already has a Parent Procedure {1}.,{0} มี parent Parent {1} อยู่แล้ว,
API,API,
diff --git a/erpnext/translations/tr.csv b/erpnext/translations/tr.csv
index 9e7ba4d..b65494c 100644
--- a/erpnext/translations/tr.csv
+++ b/erpnext/translations/tr.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Farklı promosyon programlarını uygulama kuralları.,
Shift,vardiya,
Show {0},{0} göster,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", ".", "/", "{" Ve "}" dışındaki Özel Karakterler, seri dizisine izin verilmez",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", ".", "/", "{{" Ve "}}" dışındaki Özel Karakterler, seri dizisine izin verilmez {0}",
Target Details,Hedef Detayları,
{0} already has a Parent Procedure {1}.,{0} zaten bir {1} veli prosedürüne sahip.,
API,API,
diff --git a/erpnext/translations/uk.csv b/erpnext/translations/uk.csv
index 53e2df5..4e2f63f 100644
--- a/erpnext/translations/uk.csv
+++ b/erpnext/translations/uk.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Правила застосування різних рекламних схем.,
Shift,Зміна,
Show {0},Показати {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Спеціальні символи, окрім "-", "#", ".", "/", "{" Та "}", не дозволяються в іменуванні серій",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Спеціальні символи, окрім "-", "#", ".", "/", "{{" Та "}}", не дозволяються в іменуванні серій {0}",
Target Details,Деталі цілі,
{0} already has a Parent Procedure {1}.,{0} вже має батьківську процедуру {1}.,
API,API,
diff --git a/erpnext/translations/ur.csv b/erpnext/translations/ur.csv
index aaaef58..db6518e 100644
--- a/erpnext/translations/ur.csv
+++ b/erpnext/translations/ur.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,مختلف پروموشنل اسکیموں کو لاگو کرنے کے قواعد۔,
Shift,شفٹ۔,
Show {0},دکھائیں {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","-" ، "#" ، "." ، "/" ، "{" اور "}" سوائے خصوصی حروف کی نام بندی سیریز میں اجازت نہیں ہے,
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",{0} "-" ، "#" ، "." ، "/" ، "{{" اور "}}" سوائے خصوصی حروف کی نام بندی سیریز میں اجازت نہیں ہے,
Target Details,ہدف کی تفصیلات۔,
{0} already has a Parent Procedure {1}.,{0} پہلے سے ہی والدین کا طریقہ کار {1} ہے.,
API,API,
diff --git a/erpnext/translations/uz.csv b/erpnext/translations/uz.csv
index c983797..bb64a15 100644
--- a/erpnext/translations/uz.csv
+++ b/erpnext/translations/uz.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Turli reklama sxemalarini qo'llash qoidalari.,
Shift,Shift,
Show {0},{0} ni ko'rsatish,
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",""-", "#", ".", "/", "{" Va "}" belgilaridan tashqari maxsus belgilarga ruxsat berilmaydi.",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}",""-", "#", ".", "/", "{{" Va "}}" belgilaridan tashqari maxsus belgilarga ruxsat berilmaydi {0}.",
Target Details,Maqsad tafsilotlari,
{0} already has a Parent Procedure {1}.,{0} allaqachon Ota-ona tartibiga ega {1}.,
API,API,
diff --git a/erpnext/translations/vi.csv b/erpnext/translations/vi.csv
index 03ff2cc..7317b4b 100644
--- a/erpnext/translations/vi.csv
+++ b/erpnext/translations/vi.csv
@@ -3537,7 +3537,7 @@
Rules for applying different promotional schemes.,Quy tắc áp dụng các chương trình khuyến mãi khác nhau.,
Shift,Ca,
Show {0},Hiển thị {0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series","Các ký tự đặc biệt ngoại trừ "-", "#", ".", "/", "{" Và "}" không được phép trong chuỗi đặt tên",
+"Special Characters except '-', '#', '.', '/', '{{' and '}}' not allowed in naming series {0}","Các ký tự đặc biệt ngoại trừ "-", "#", ".", "/", "{{" Và "}}" không được phép trong chuỗi đặt tên {0}",
Target Details,Chi tiết mục tiêu,
{0} already has a Parent Procedure {1}.,{0} đã có Quy trình dành cho phụ huynh {1}.,
API,API,
diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv
index d1f1b07..2337bcb 100644
--- a/erpnext/translations/zh.csv
+++ b/erpnext/translations/zh.csv
@@ -3537,7 +3537,6 @@
Rules for applying different promotional schemes.,适用不同促销计划的规则。,
Shift,转移,
Show {0},显示{0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",命名系列中不允许使用除“ - ”,“#”,“。”,“/”,“{”和“}”之外的特殊字符,
Target Details,目标细节,
{0} already has a Parent Procedure {1}.,{0}已有父程序{1}。,
API,应用程序界面,
diff --git a/erpnext/translations/zh_tw.csv b/erpnext/translations/zh_tw.csv
index 1cc7d87..1b7e186 100644
--- a/erpnext/translations/zh_tw.csv
+++ b/erpnext/translations/zh_tw.csv
@@ -3311,7 +3311,6 @@
Rules for applying different promotional schemes.,適用不同促銷計劃的規則。,
Shift,轉移,
Show {0},顯示{0},
-"Special Characters except ""-"", ""#"", ""."", ""/"", ""{"" and ""}"" not allowed in naming series",命名系列中不允許使用除“ - ”,“#”,“。”,“/”,“{”和“}”之外的特殊字符,
Target Details,目標細節,
API,API,
Annual,年刊,