diff --git a/.travis.yml b/.travis.yml index beab4e119a..a70062fea3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,6 +53,9 @@ before_script: script: - set -e - - bench --verbose run-tests + - bench run-tests - sleep 5 - - bench --verbose run-ui-tests --app erpnext + - bench reinstall --yes + - bench execute erpnext.setup.setup_wizard.utils.complete + - bench execute erpnext.setup.utils.enable_all_roles_and_domains + - bench run-ui-tests --app erpnext diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 48296e7e33..cf6ca02e7f 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from erpnext.hooks import regional_overrides -__version__ = '8.9.2' +__version__ = '8.10.0' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index 0489f9a5f0..8de923f2e0 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -13,6 +13,7 @@ "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -42,6 +43,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -70,6 +72,7 @@ "width": "50%" }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -100,6 +103,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -130,6 +134,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -161,6 +166,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -190,6 +196,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -219,6 +226,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -250,6 +258,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -278,6 +287,7 @@ "width": "50%" }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -309,6 +319,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -341,6 +352,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -372,6 +384,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -404,6 +417,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -433,6 +447,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -461,6 +476,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -489,6 +505,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -528,7 +545,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-04-21 17:22:41.150984", + "modified": "2017-08-11 15:28:35.855809", "modified_by": "Administrator", "module": "Accounts", "name": "Account", @@ -641,6 +658,6 @@ "search_fields": "", "show_name_in_global_search": 1, "sort_order": "ASC", - "track_changes": 0, + "track_changes": 1, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/tw_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tw_chart_of_accounts.json new file mode 100644 index 0000000000..a79283a40a --- /dev/null +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tw_chart_of_accounts.json @@ -0,0 +1,722 @@ +{ + "country_code": "tw", + "name": "Taiwan - Chart of Accounts", + "tree": { + "1-\u8cc7\u7522": { + "11~12-\u6d41\u52d5\u8cc7\u7522": { + "111-\u73fe\u91d1\u53ca\u7d04\u7576\u73fe\u91d1": { + "1111-\u5eab\u5b58\u73fe\u91d1": { + "account_type": "Cash" + }, + "1112-\u96f6\u7528\u91d1/\u9031\u8f49\u91d1": { + "account_type": "Cash" + }, + "1113-\u9280\u884c\u5b58\u6b3e": { + "account_type": "Bank", + "\u4e2d\u570b\u4fe1\u8a17": { + "account_type": "Bank" + }, + "\u53f0\u5317\u5bcc\u90a6": { + "account_type": "Bank" + } + }, + "1116-\u5728\u9014\u73fe\u91d1": { + "account_type": "Cash" + }, + "1117-\u7d04\u7576\u73fe\u91d1": { + "account_type": "Cash" + }, + "1118-\u5176\u4ed6\u73fe\u91d1\u53ca\u7d04\u7576\u73fe\u91d1": { + "account_type": "Cash" + }, + "account_type": "Cash" + }, + "112-\u77ed\u671f\u6295\u8cc7": { + "1121-\u77ed\u671f\u6295\u8cc7 \u2014\u80a1\u7968": {} + }, + "113-\u61c9\u6536\u7968\u64da": { + "1131-\u61c9\u6536\u7968\u64da": { + "account_type": "Receivable" + }, + "1132-\u61c9\u6536\u7968\u64da\u8cbc\u73fe ": { + "account_type": "Receivable" + }, + "1138-\u5176\u4ed6\u61c9\u6536\u7968\u64da ": { + "account_type": "Receivable" + }, + "1139-\u5099\u62b5\u5446\u5e33 \uff0d\u61c9\u6536\u7968\u64da ": { + "account_type": "Receivable" + }, + "account_type": "Receivable" + }, + "114-\u61c9\u6536\u5e33\u6b3e": { + "1141-\u61c9\u6536\u5e33\u6b3e ": { + "account_type": "Receivable" + }, + "1142-\u61c9\u6536\u5206\u671f\u5e33\u6b3e ": { + "account_type": "Receivable" + }, + "1149-\u5099\u62b5\u5446\u5e33 \uff0d\u61c9\u6536\u5e33\u6b3e ": { + "account_type": "Receivable" + }, + "account_type": "Receivable" + }, + "118-\u5176\u4ed6\u61c9\u6536\u6b3e": { + "1184-\u61c9\u6536\u6536\u76ca": { + "account_type": "Receivable" + }, + "1185-\u61c9\u6536\u9000\u7a05\u6b3e": { + "account_type": "Receivable" + }, + "1189-\u5099\u62b5\u5446\u5e33 \u2014 \u5176\u4ed6\u61c9\u6536\u6b3e ": { + "account_type": "Receivable" + }, + "account_type": "Receivable" + }, + "121~122-\u5b58\u8ca8": { + "1219-\u5099\u62b5\u5b58\u8ca8\u8dcc\u50f9\u640d\u5931": {}, + "1229-\u5099\u62b5\u5b58\u8ca8\u8dcc\u50f9\u640d\u5931": {}, + "account_type": "Stock", + "is_group": 1 + }, + "125-\u9810\u4ed8\u8cbb\u7528": { + "1251-\u9810\u4ed8\u85aa\u8cc7": {}, + "1252-\u9810\u4ed8\u79df\u91d1": {}, + "1253-\u9810\u4ed8\u4fdd\u96aa\u8cbb": {}, + "1254-\u7528\u54c1\u76e4\u5b58": {}, + "1255-\u9810\u4ed8\u6240\u5f97\u7a05": {}, + "1258-\u5176\u4ed6\u9810\u4ed8\u8cbb\u7528": {} + }, + "126-\u9810\u4ed8\u6b3e\u9805": { + "1261-\u9810\u4ed8\u8ca8\u6b3e": {}, + "1268-\u5176\u4ed6\u9810\u4ed8\u6b3e\u9805": {} + }, + "128~129-\u5176\u4ed6\u6d41\u52d5\u8cc7\u7522": { + "1281-\u9032\u9805\u7a05\u984d": {}, + "1282-\u7559\u62b5\u7a05\u984d": {}, + "1283-\u66ab\u4ed8\u6b3e": {}, + "1284-\u4ee3\u4ed8\u6b3e": {}, + "1285-\u54e1\u5de5\u501f\u652f": {} + } + }, + "13-\u57fa\u91d1\u53ca\u9577\u671f\u6295\u8cc7": { + "131-\u57fa\u91d1": { + "1311-\u511f\u50b5\u57fa\u91d1": {}, + "1313-\u610f\u5916\u640d\u5931\u6e96\u5099\u57fa\u91d1": {}, + "1314-\u9000\u4f11\u57fa\u91d1": {}, + "1318-\u5176\u4ed6\u57fa\u91d1": {} + }, + "132-\u9577\u671f\u6295\u8cc7": { + "1321-\u9577\u671f\u80a1\u6b0a\u6295\u8cc7": {}, + "1322-\u9577\u671f\u50b5\u5238\u6295\u8cc7": {}, + "1323-\u9577\u671f\u4e0d\u52d5\u7522\u6295\u8cc7": {}, + "1328-\u5176\u4ed6\u9577\u671f\u6295\u8cc7": {} + } + }, + "14~15-\u56fa\u5b9a\u8cc7\u7522": { + "141-\u571f\u5730": { + "1411-\u571f\u5730": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "142-\u571f\u5730\u6539\u826f\u7269": { + "1421-\u571f\u5730\u6539\u826f\u7269": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "143-\u623f\u5c4b\u53ca\u5efa\u7269": { + "1431-\u623f\u5c4b\u53ca\u5efa\u7269": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "144~146-\u6a5f(\u5668)\u5177\u53ca\u8a2d\u5099": { + "1441-\u6a5f(\u5668)\u5177": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "151-\u79df\u8cc3\u8cc7\u7522": { + "1511-\u79df\u8cc3\u8cc7\u7522": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "152-\u79df\u8cc3\u6b0a\u76ca\u6539\u826f": { + "1521-\u79df\u8cc3\u6b0a\u76ca\u6539\u826f": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "156-\u672a\u5b8c\u5de5\u7a0b\u53ca\u9810\u4ed8\u8cfc\u7f6e\u8a2d\u5099\u6b3e": { + "1561-\u672a\u5b8c\u5de5\u7a0b": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "158-\u96dc\u9805\u56fa\u5b9a\u8cc7\u7522": { + "1581-\u96dc\u9805\u56fa\u5b9a\u8cc7\u7522": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "16-\u905e\u8017\u8cc7\u7522": { + "161-\u905e\u8017\u8cc7\u7522": { + "is_group": 1 + } + }, + "17-\u7121\u5f62\u8cc7\u7522": { + "171-\u5546\u6a19\u6b0a": { + "1711-\u5546\u6a19\u6b0a": {} + }, + "172-\u5c08\u5229\u6b0a": { + "1721-\u5c08\u5229\u6b0a": {} + }, + "176-\u5546\u8b7d": { + "1761-\u5546\u8b7d": {} + }, + "177-\u958b\u8fa6\u8cbb": { + "1771-\u958b\u8fa6\u8cbb": {} + }, + "178-\u5176\u4ed6\u7121\u5f62\u8cc7\u7522": { + "1781-\u905e\u5ef6\u9000\u4f11\u91d1\u6210\u672c": {} + } + }, + "18-\u5176\u4ed6\u8cc7\u7522": { + "181-\u905e\u5ef6\u8cc7\u7522": { + "1811-\u50b5\u5238\u767c\u884c\u6210\u672c": {}, + "1812-\u9577\u671f\u9810\u4ed8\u79df\u91d1": {}, + "1813-\u9577\u671f\u9810\u4ed8\u4fdd\u96aa\u8cbb": {}, + "1814-\u905e\u5ef6\u6240\u5f97\u7a05\u8cc7\u7522": {}, + "1815-\u9810\u4ed8\u9000\u4f11\u91d1": {}, + "1818-\u5176\u4ed6\u905e\u5ef6\u8cc7\u7522": {} + }, + "182-\u9592\u7f6e\u8cc7\u7522": { + "1821-\u9592\u7f6e\u8cc7\u7522": {} + }, + "184-\u9577\u671f\u61c9\u6536\u7968\u64da\u53ca\u6b3e\u9805\u8207\u50ac\u6536\u5e33\u6b3e": { + "1841-\u9577\u671f\u61c9\u6536\u7968\u64da": { + "account_type": "Receivable" + }, + "1842-\u9577\u671f\u61c9\u6536\u5e33\u6b3e": { + "account_type": "Receivable" + }, + "1843-\u50ac\u6536\u5e33\u6b3e": { + "account_type": "Receivable" + }, + "1848-\u5176\u4ed6\u9577\u671f\u61c9\u6536\u6b3e\u9805": { + "account_type": "Receivable" + }, + "1849-\u5099\u62b5\u5446\u5e33\u2014\u9577\u671f\u61c9\u6536\u7968\u64da\u53ca\u6b3e\u9805\u8207\u50ac\u6536\u5e33\u6b3e": { + "account_type": "Receivable" + }, + "account_type": "Receivable" + }, + "185-\u51fa\u79df\u8cc7\u7522": { + "1851-\u51fa\u79df\u8cc7\u7522": {}, + "1858-\u51fa\u79df\u8cc7\u7522 \u2014\u91cd\u4f30\u589e\u503c": {}, + "1859-\u7d2f\u7a4d\u6298\u820a \u2014\u51fa\u79df\u8cc7\u7522": { + "account_type": "Accumulated Depreciation" + } + }, + "186-\u5b58\u51fa\u4fdd\u8b49\u91d1": { + "1861-\u5b58\u51fa\u4fdd\u8b49\u91d1": {} + }, + "188-\u96dc\u9805\u8cc7\u7522": { + "1881-\u53d7\u9650\u5236\u5b58\u6b3e": {}, + "1888-\u96dc\u9805\u8cc7\u7522 \u2014\u5176\u4ed6": {} + } + }, + "Temporary Accounts": { + "Temporary Opening": { + "account_type": "Temporary" + }, + "account_type": "Temporary" + }, + "root_type": "Asset" + }, + "2-\u8ca0\u50b5": { + "21~22-\u6d41\u52d5\u8ca0\u50b5": { + "211-\u77ed\u671f\u501f\u6b3e": { + "2111-\u9280\u884c\u900f\u652f": {}, + "2112-\u9280\u884c\u501f\u6b3e": {} + }, + "212-\u61c9\u4ed8\u77ed\u671f\u7968\u5238": { + "2121-\u61c9\u4ed8\u5546\u696d\u672c\u7968": { + "account_type": "Payable" + }, + "2122-\u9280\u884c\u627f\u514c\u532f\u7968": { + "account_type": "Payable" + }, + "account_type": "Payable" + }, + "213-\u61c9\u4ed8\u7968\u64da": { + "2131-\u61c9\u4ed8\u7968\u64da": { + "account_type": "Payable" + }, + "account_type": "Payable" + }, + "214-\u61c9\u4ed8\u5e33\u6b3e": { + "2141-\u61c9\u4ed8\u5e33\u6b3e": { + "account_type": "Payable" + }, + "account_type": "Payable" + }, + "216-\u61c9\u4ed8\u6240\u5f97\u7a05": { + "2161-\u61c9\u4ed8\u6240\u5f97\u7a05": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "account_type": "Tax", + "tax_rate": 5.0 + }, + "217-\u61c9\u4ed8\u8cbb\u7528": { + "2171-\u61c9\u4ed8\u85aa\u5de5": {}, + "2172-\u61c9\u4ed8\u79df\u91d1": {}, + "2173-\u61c9\u4ed8\u5229\u606f": {}, + "2174-\u61c9\u4ed8\u71df\u696d\u7a05": {}, + "2175-\u61c9\u4ed8\u7a05\u6350 \u2014\u5176\u4ed6": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "2178-\u5176\u4ed6\u61c9\u4ed8\u8cbb\u7528": {} + }, + "218~219-\u5176\u4ed6\u61c9\u4ed8\u6b3e": { + "2184-\u61c9\u4ed8\u571f\u5730\u623f\u5c4b\u6b3e": {}, + "2185-\u61c9\u4ed8\u8a2d\u5099\u6b3e": {}, + "2192-\u61c9\u4ed8\u80a1\u5229": {} + }, + "226-\u9810\u6536\u6b3e\u9805": { + "2261-\u9810\u6536\u8ca8\u6b3e": {}, + "2262-\u9810\u6536\u6536\u5165": {}, + "2268-\u5176\u4ed6\u9810\u6536\u6b3e": {} + }, + "227-\u4e00\u5e74\u6216\u4e00\u71df\u696d\u9031\u671f\u5167\u5230\u671f\u9577\u671f\u8ca0\u50b5": { + "is_group": 1 + }, + "228~229-\u5176\u4ed6\u6d41\u52d5\u8ca0\u50b5": { + "2281-\u92b7\u9805\u7a05\u984d": {}, + "2283-\u66ab\u6536\u6b3e ": {}, + "2284-\u4ee3\u6536\u6b3e": {}, + "2285-\u4f30\u8a08\u552e\u5f8c\u670d\u52d9/\u4fdd\u56fa\u8ca0\u50b5": {}, + "2291-\u905e\u5ef6\u6240\u5f97\u7a05\u8ca0\u50b5": {}, + "2292-\u905e\u5ef6\u514c\u63db\u5229\u76ca": {} + } + }, + "23-\u9577\u671f\u8ca0\u50b5": { + "231-\u61c9\u4ed8\u516c\u53f8\u50b5": { + "2311-\u61c9\u4ed8\u516c\u53f8\u50b5": {}, + "2319-\u61c9\u4ed8\u516c\u53f8\u50b5\u6ea2(\u6298)\u50f9": {} + }, + "232-\u9577\u671f\u501f\u6b3e": { + "2321-\u9577\u671f\u9280\u884c\u501f\u6b3e": {}, + "2324-\u9577\u671f\u501f\u6b3e \u2014\u696d\u4e3b": {}, + "2325-\u9577\u671f\u501f\u6b3e \u2014\u54e1\u5de5": {}, + "2327-\u9577\u671f\u501f\u6b3e \u2014\u95dc\u4fc2\u4eba": {}, + "2328-\u9577\u671f\u501f\u6b3e \u2014\u5176\u4ed6": {} + }, + "233-\u9577\u671f\u61c9\u4ed8\u7968\u64da\u53ca\u6b3e\u9805": { + "2331-\u9577\u671f\u61c9\u4ed8\u7968\u64da": { + "account_type": "Payable" + }, + "2332-\u9577\u671f\u61c9\u4ed8\u5e33\u6b3e": { + "account_type": "Payable" + }, + "2333-\u9577\u671f\u61c9\u4ed8\u79df\u8cc3\u8ca0\u50b5": { + "account_type": "Payable" + }, + "account_type": "Payable" + }, + "234-\u4f30\u8a08\u61c9\u4ed8\u571f\u5730\u589e\u503c\u7a05": { + "2341-\u4f30\u8a08\u61c9\u4ed8\u571f\u5730\u589e\u503c\u7a05": {} + }, + "235-\u61c9\u8a08\u9000\u4f11\u91d1\u8ca0\u50b5": { + "2351-\u61c9\u8a08\u9000\u4f11\u91d1\u8ca0\u50b5": {} + }, + "238-\u5176\u4ed6\u9577\u671f\u8ca0\u50b5": { + "2388-\u5176\u4ed6\u9577\u671f\u8ca0\u50b5\u2014\u5176\u4ed6": {} + } + }, + "28-\u5176\u4ed6\u8ca0\u50b5": { + "281-\u905e\u5ef6\u8ca0\u50b5": { + "2811-\u905e\u5ef6\u6536\u5165": {}, + "2814-\u905e\u5ef6\u6240\u5f97\u7a05\u8ca0\u50b5": {}, + "2818-\u5176\u4ed6\u905e\u5ef6\u8ca0\u50b5": {} + }, + "286-\u5b58\u5165\u4fdd\u8b49\u91d1": { + "2861-\u5b58\u5165\u4fdd\u8b49\u91d1": {} + }, + "288-\u96dc\u9805\u8ca0\u50b5": { + "2888-\u96dc\u9805\u8ca0\u50b5 \u2014\u5176\u4ed6": {} + } + }, + "Stock Received But Not Billed": { + "account_type": "Stock Received But Not Billed" + }, + "root_type": "Liability" + }, + "3-\u696d\u4e3b\u6b0a\u76ca": { + "31-\u8cc7\u672c": { + "311-\u8cc7\u672c\uff08\u80a1\u672c\uff09 ": { + "3111-\u666e\u901a\u80a1\u80a1\u672c": {}, + "3112-\u7279\u5225\u80a1\u80a1\u672c": {}, + "3113-\u9810\u6536\u80a1\u672c": {}, + "3114-\u5f85\u5206\u914d\u80a1\u7968\u80a1\u5229": {}, + "3115-\u8cc7\u672c": {} + } + }, + "32-\u8cc7\u672c\u516c\u7a4d": { + "321-\u80a1\u7968\u6ea2\u50f9": { + "3211-\u666e\u901a\u80a1\u80a1\u7968\u6ea2\u50f9": {}, + "3212-\u7279\u5225\u80a1\u80a1\u7968\u6ea2\u50f9": {} + }, + "323-\u8cc7\u7522\u91cd\u4f30\u589e\u503c\u6e96\u5099": { + "3231-\u8cc7\u7522\u91cd\u4f30\u589e\u503c\u6e96\u5099": {} + }, + "324-\u8655\u5206\u8cc7\u7522\u6ea2\u50f9\u516c\u7a4d": { + "3241-\u8655\u5206\u8cc7\u7522\u6ea2\u50f9\u516c\u7a4d": {} + }, + "325-\u5408\u4f75\u516c\u7a4d": { + "3251-\u5408\u4f75\u516c\u7a4d": {} + }, + "326-\u53d7\u8d08\u516c\u7a4d": { + "3261-\u53d7\u8d08\u516c\u7a4d": {} + }, + "328-\u5176\u4ed6\u8cc7\u672c\u516c\u7a4d": { + "3281-\u6b0a\u76ca\u6cd5\u9577\u671f\u80a1\u6b0a\u6295\u8cc7\u8cc7\u672c\u516c\u7a4d": {}, + "3282-\u8cc7\u672c\u516c\u7a4d\u2014 \u5eab\u85cf\u80a1\u7968\u4ea4\u6613": {} + } + }, + "33-\u4fdd\u7559\u76c8\u9918(\u7d2f\u7a4d\u8667\u640d)": { + "331-\u6cd5\u5b9a\u76c8\u9918\u516c\u7a4d": { + "3311-\u6cd5\u5b9a\u76c8\u9918\u516c\u7a4d": {} + }, + "332-\u7279\u5225\u76c8\u9918\u516c\u7a4d": { + "3321-\u610f\u5916\u640d\u5931\u6e96\u5099": {}, + "3322-\u6539\u826f\u64f4\u5145\u6e96\u5099": {}, + "3323-\u511f\u50b5\u6e96\u5099": {}, + "3328-\u5176\u4ed6\u7279\u5225\u76c8\u9918\u516c\u7a4d": {} + }, + "335-\u672a\u5206\u914d\u76c8\u9918(\u7d2f\u7a4d\u8667\u640d) ": { + "is_group": 1 + } + }, + "34-\u6b0a\u76ca\u8abf\u6574": { + "341-\u9577\u671f\u80a1\u6b0a\u6295\u8cc7\u672a\u5be6\u73fe\u8dcc\u50f9\u640d\u5931": { + "3411-\u9577\u671f\u80a1\u6b0a\u6295\u8cc7\u672a\u5be6\u73fe\u8dcc\u50f9\u640d\u5931": {} + }, + "342-\u7d2f\u7a4d\u63db\u7b97\u8abf\u6574\u6578": { + "3421-\u7d2f\u7a4d\u63db\u7b97\u8abf\u6574\u6578": {} + }, + "343-\u672a\u8a8d\u5217\u70ba\u9000\u4f11\u91d1\u6210\u672c\u4e4b\u6de8\u640d\u5931": { + "3431-\u672a\u8a8d\u5217\u70ba\u9000\u4f11\u91d1\u6210\u672c\u4e4b\u6de8\u640d\u5931": {} + } + }, + "35-\u5eab\u85cf\u80a1": { + "351-\u5eab\u85cf\u80a1": { + "3511-\u5eab\u85cf\u80a1": {} + } + }, + "36-\u5c11\u6578\u80a1\u6b0a": { + "361-\u5c11\u6578\u80a1\u6b0a": { + "3611-\u5c11\u6578\u80a1\u6b0a": {} + } + }, + "root_type": "Equity" + }, + "4-\u71df\u696d\u6536\u5165": { + "41-\u92b7\u8ca8\u6536\u5165": { + "411-\u92b7\u8ca8\u6536\u5165": { + "4111-\u92b7\u8ca8\u6536\u5165": {}, + "4112-\u5206\u671f\u4ed8\u6b3e\u92b7\u8ca8\u6536\u5165": {} + }, + "417-\u92b7\u8ca8\u9000\u56de": { + "4171-\u92b7\u8ca8\u9000\u56de": {} + }, + "419-\u92b7\u8ca8\u6298\u8b93": { + "4191-\u92b7\u8ca8\u6298\u8b93": {} + } + }, + "46-\u52de\u52d9\u6536\u5165": { + "461-\u52de\u52d9\u6536\u5165": { + "4611-\u52de\u52d9\u6536\u5165": {} + } + }, + "47-\u696d\u52d9\u6536\u5165": { + "471-\u696d\u52d9\u6536\u5165": { + "4711-\u696d\u52d9\u6536\u5165": {} + } + }, + "48-\u5176\u4ed6\u71df\u696d\u6536\u5165": { + "488-\u5176\u4ed6\u71df\u696d\u6536\u5165\u2014\u5176\u4ed6": { + "4888-\u5176\u4ed6\u71df\u696d\u6536\u5165\u2014\u5176\u4ed6": {} + } + }, + "root_type": "Income" + }, + "5-\u71df\u696d\u6210\u672c": { + "51-\u92b7\u8ca8\u6210\u672c": { + "511-\u92b7\u8ca8\u6210\u672c": { + "5111-\u92b7\u8ca8\u6210\u672c": { + "account_type": "Cost of Goods Sold" + }, + "5112-\u5206\u671f\u4ed8\u6b3e\u92b7\u8ca8\u6210\u672c": { + "account_type": "Cost of Goods Sold" + }, + "account_type": "Cost of Goods Sold" + }, + "512-\u9032\u8ca8": { + "5121-\u9032\u8ca8": {}, + "5122-\u9032\u8ca8\u8cbb\u7528": {}, + "5123-\u9032\u8ca8\u9000\u51fa": {}, + "5124-\u9032\u8ca8\u6298\u8b93": {} + }, + "513-\u9032\u6599": { + "5131-\u9032\u6599": {}, + "5132-\u9032\u6599\u8cbb\u7528": {}, + "5133-\u9032\u6599\u9000\u51fa": {}, + "5134-\u9032\u6599\u6298\u8b93": {} + }, + "514-\u76f4\u63a5\u4eba\u5de5": { + "5141-\u76f4\u63a5\u4eba\u5de5": {} + }, + "515~518-\u88fd\u9020\u8cbb\u7528": { + "5151-\u9593\u63a5\u4eba\u5de5": {}, + "5152-\u79df\u91d1\u652f\u51fa": {}, + "5153-\u6587\u5177\u7528\u54c1": {}, + "5154-\u65c5\u8cbb": {}, + "5155-\u904b\u8cbb": {}, + "5156-\u90f5\u96fb\u8cbb": {}, + "5157-\u4fee\u7e55\u8cbb": {}, + "5158-\u5305\u88dd\u8cbb": {}, + "5161-\u6c34\u96fb\u74e6\u65af\u8cbb": {}, + "5162-\u4fdd\u96aa\u8cbb": {}, + "5163-\u52a0\u5de5\u8cbb": {}, + "5166-\u7a05\u6350": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "5168-\u6298\u820a ": { + "account_type": "Depreciation" + }, + "5169-\u5404\u9805\u8017\u7aed\u53ca\u6524\u63d0": {}, + "5172-\u4f19\u98df\u8cbb": {}, + "5173-\u8077\u5de5\u798f\u5229": {}, + "5176-\u8a13\u7df4\u8cbb": {}, + "5177-\u9593\u63a5\u6750\u6599": {}, + "5188-\u5176\u4ed6\u88fd\u9020\u8cbb\u7528": {} + }, + "Expenses Included In Valuation": { + "account_type": "Expenses Included In Valuation" + }, + "account_type": "Cost of Goods Sold" + }, + "56-\u52de\u52d9\u6210\u672c\u88fd": { + "561-\u52de\u52d9\u6210\u672c": { + "5611-\u52de\u52d9\u6210\u672c": {} + } + }, + "57-\u696d\u52d9\u6210\u672c": { + "571-\u696d\u52d9\u6210\u672c": { + "5711-\u696d\u52d9\u6210\u672c": {} + } + }, + "58-\u5176\u4ed6\u71df\u696d\u6210\u672c": { + "588-\u5176\u4ed6\u71df\u696d\u6210\u672c\u2014\u5176\u4ed6 ": { + "5888-\u5176\u4ed6\u71df\u696d\u6210\u672c\u2014\u5176\u4ed6": {} + } + }, + "Stock Adjustment": { + "account_type": "Stock Adjustment" + }, + "root_type": "Expense" + }, + "6-\u71df\u696d\u8cbb\u7528": { + "61-\u63a8\u92b7\u8cbb\u7528": { + "615~618-\u63a8\u92b7\u8cbb\u7528": { + "6151-\u85aa\u8cc7\u652f\u51fa": {}, + "6152-\u79df\u91d1\u652f\u51fa": {}, + "6153-\u6587\u5177\u7528\u54c1": {}, + "6154-\u65c5\u8cbb": {}, + "6155-\u904b\u8cbb": {}, + "6156-\u90f5\u96fb\u8cbb": {}, + "6157-\u4fee\u7e55\u8cbb": {}, + "6159-\u5ee3\u544a\u8cbb": {}, + "6161-\u6c34\u96fb\u74e6\u65af\u8cbb": {}, + "6162-\u4fdd\u96aa\u8cbb": {}, + "6164-\u4ea4\u969b\u8cbb": {}, + "6165-\u6350\u8d08": {}, + "6166-\u7a05\u6350": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "6167-\u5446\u5e33\u640d\u5931": {}, + "6168-\u6298\u820a ": { + "account_type": "Depreciation" + }, + "6169-\u5404\u9805\u8017\u7aed\u53ca\u6524\u63d0": {}, + "6172-\u4f19\u98df\u8cbb": {}, + "6173-\u8077\u5de5\u798f\u5229": {}, + "6175-\u4f63\u91d1\u652f\u51fa": {}, + "6176-\u8a13\u7df4\u8cbb": {}, + "6188-\u5176\u4ed6\u63a8\u92b7\u8cbb\u7528": {} + } + }, + "62-\u7ba1\u7406\u53ca\u7e3d\u52d9\u8cbb\u7528": { + "625~628-\u7ba1\u7406\u53ca\u7e3d\u52d9\u8cbb\u7528": { + "6251-\u85aa\u8cc7\u652f\u51fa": {}, + "6252-\u79df\u91d1\u652f\u51fa": {}, + "6253-\u6587\u5177\u7528\u54c1": {}, + "6254-\u65c5\u8cbb": {}, + "6255-\u904b\u8cbb": {}, + "6256-\u90f5\u96fb\u8cbb": {}, + "6257-\u4fee\u7e55\u8cbb": {}, + "6259-\u5ee3\u544a\u8cbb": {}, + "6261-\u6c34\u96fb\u74e6\u65af\u8cbb": {}, + "6262-\u4fdd\u96aa\u8cbb": {}, + "6264-\u4ea4\u969b\u8cbb": {}, + "6265-\u6350\u8d08": {}, + "6266-\u7a05\u6350": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "6267-\u5446\u5e33\u640d\u5931": {}, + "6268-\u6298\u820a": { + "account_type": "Depreciation" + }, + "6269-\u5404\u9805\u8017\u7aed\u53ca\u6524\u63d0": {}, + "6271-\u5916\u92b7\u640d\u5931": {}, + "6272-\u4f19\u98df\u8cbb": {}, + "6273-\u8077\u5de5\u798f\u5229": {}, + "6274-\u7814\u7a76\u767c\u5c55\u8cbb\u7528": {}, + "6275-\u4f63\u91d1\u652f\u51fa": {}, + "6276-\u8a13\u7df4\u8cbb": {}, + "6278-\u52de\u52d9\u8cbb": {}, + "6288-\u5176\u4ed6\u7ba1\u7406\u53ca\u7e3d\u52d9\u8cbb\u7528": {} + } + }, + "63-\u7814\u7a76\u767c\u5c55\u8cbb\u7528": { + "635~638-\u7814\u7a76\u767c\u5c55\u8cbb\u7528": { + "6351-\u85aa\u8cc7\u652f\u51fa": {}, + "6352-\u79df\u91d1\u652f\u51fa": {}, + "6353-\u6587\u5177\u7528\u54c1": {}, + "6354-\u65c5\u8cbb": {}, + "6355-\u904b\u8cbb": {}, + "6356-\u90f5\u96fb\u8cbb": {}, + "6357-\u4fee\u7e55\u8cbb": {}, + "6361-\u6c34\u96fb\u74e6\u65af\u8cbb": {}, + "6362-\u4fdd\u96aa\u8cbb": {}, + "6364-\u4ea4\u969b\u8cbb": {}, + "6366-\u7a05\u6350": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "6368-\u6298\u820a": { + "account_type": "Depreciation" + }, + "6369-\u5404\u9805\u8017\u7aed\u53ca\u6524\u63d0": {}, + "6372-\u4f19\u98df\u8cbb": {}, + "6373-\u8077\u5de5\u798f\u5229": {}, + "6376-\u8a13\u7df4\u8cbb": {}, + "6378-\u5176\u4ed6\u7814\u7a76\u767c\u5c55\u8cbb\u7528": {} + } + }, + "root_type": "Expense" + }, + "7-\u71df\u696d\u5916\u6536\u5165\u53ca\u8cbb\u7528": { + "71~74-\u71df\u696d\u5916\u6536\u5165": { + "711-\u5229\u606f\u6536\u5165": { + "7111-\u5229\u606f\u6536\u5165": {} + }, + "712-\u6295\u8cc7\u6536\u76ca": { + "7121-\u6b0a\u76ca\u6cd5\u8a8d\u5217\u4e4b\u6295\u8cc7\u6536\u76ca": {}, + "7122-\u80a1\u5229\u6536\u5165": {}, + "7123-\u77ed\u671f\u6295\u8cc7\u5e02\u50f9\u56de\u5347\u5229\u76ca": {} + }, + "713-\u514c\u63db\u5229\u76ca": { + "7131-\u514c\u63db\u5229\u76ca": {} + }, + "714-\u8655\u5206\u6295\u8cc7\u6536\u76ca": { + "7141-\u8655\u5206\u6295\u8cc7\u6536\u76ca": {} + }, + "715-\u8655\u5206\u8cc7\u7522\u6ea2\u50f9\u6536\u5165": { + "7151-\u8655\u5206\u8cc7\u7522\u6ea2\u50f9\u6536\u5165": {} + }, + "748-\u5176\u4ed6\u71df\u696d\u5916\u6536\u5165": { + "7481-\u6350\u8d08\u6536\u5165": {}, + "7482-\u79df\u91d1\u6536\u5165": {}, + "7483-\u4f63\u91d1\u6536\u5165": {}, + "7484-\u51fa\u552e\u4e0b\u8173\u53ca\u5ee2\u6599\u6536\u5165": {}, + "7485-\u5b58\u8ca8\u76e4\u76c8": {}, + "7486-\u5b58\u8ca8\u8dcc\u50f9\u56de\u5347\u5229\u76ca": {}, + "7487-\u58de\u5e33\u8f49\u56de\u5229\u76ca": {}, + "7488-\u5176\u4ed6\u71df\u696d\u5916\u6536\u5165\u2014\u5176\u4ed6": {} + } + }, + "75~78-\u71df\u696d\u5916\u8cbb\u7528": { + "751-\u5229\u606f\u8cbb\u7528": { + "7511-\u5229\u606f\u8cbb\u7528": {} + }, + "752-\u6295\u8cc7\u640d\u5931": { + "7521-\u6b0a\u76ca\u6cd5\u8a8d\u5217\u4e4b\u6295\u8cc7\u640d\u5931": {}, + "7523-\u77ed\u671f\u6295\u8cc7\u672a\u5be6\u73fe\u8dcc\u50f9\u640d\u5931": {} + }, + "753-\u514c\u63db\u640d\u5931": { + "7531-\u514c\u63db\u640d\u5931": {} + }, + "754-\u8655\u5206\u6295\u8cc7\u640d\u5931": { + "7541-\u8655\u5206\u6295\u8cc7\u640d\u5931": {} + }, + "755-\u8655\u5206\u8cc7\u7522\u640d\u5931": { + "7551-\u8655\u5206\u8cc7\u7522\u640d\u5931 ": {} + }, + "788-\u5176\u4ed6\u71df\u696d\u5916\u8cbb\u7528": { + "7881-\u505c\u5de5\u640d\u5931": {}, + "7882-\u707d\u5bb3\u640d\u5931": {}, + "7885-\u5b58\u8ca8\u76e4\u640d": {}, + "7886-\u5b58\u8ca8\u8dcc\u50f9\u53ca\u5446\u6eef\u640d\u5931": {}, + "7888-\u5176\u4ed6\u71df\u696d\u5916\u8cbb\u7528\u2014\u5176\u4ed6": {} + } + }, + "root_type": "Income" + }, + "8-\u6240\u5f97\u7a05\u8cbb\u7528(\u5229\u76ca)": { + "81-\u6240\u5f97\u7a05\u8cbb\u7528(\u5229\u76ca)": { + "811-\u6240\u5f97\u7a05\u8cbb\u7528(\u5229\u76ca) ": { + "8111-\u6240\u5f97\u7a05\u8cbb\u7528(\u5229\u76ca) ": {} + } + }, + "root_type": "Expense" + }, + "9-\u975e\u7d93\u5e38\u71df\u696d\u640d\u76ca": { + "91-\u505c\u696d\u90e8\u9580\u640d\u76ca": { + "911-\u505c\u696d\u90e8\u9580\u640d\u76ca\u2014\u505c\u696d\u524d\u71df\u696d\u640d\u76ca": { + "9111-\u505c\u696d\u90e8\u9580\u640d\u76ca\u2014\u505c\u696d\u524d\u71df\u696d\u640d\u76ca": {} + }, + "912-\u505c\u696d\u90e8\u9580\u640d\u76ca\u2014\u8655\u5206\u640d\u76ca": { + "9121-\u505c\u696d\u90e8\u9580\u640d\u76ca\u2014\u8655\u5206\u640d\u76ca": {} + } + }, + "92-\u975e\u5e38\u640d\u76ca": { + "921-\u975e\u5e38\u640d\u76ca": { + "9211-\u975e\u5e38\u640d\u76ca": {} + } + }, + "93-\u6703\u8a08\u539f\u5247\u8b8a\u52d5\u7d2f\u7a4d\u5f71\u97ff\u6578": { + "931-\u6703\u8a08\u539f\u5247\u8b8a\u52d5\u7d2f\u7a4d\u5f71\u97ff\u6578": { + "9311-\u6703\u8a08\u539f\u5247\u8b8a\u52d5\u7d2f\u7a4d\u5f71\u97ff\u6578": {} + } + }, + "94-\u5c11\u6578\u80a1\u6b0a\u6de8\u5229": { + "941-\u5c11\u6578\u80a1\u6b0a\u6de8\u5229": { + "9411-\u5c11\u6578\u80a1\u6b0a\u6de8\u5229": {} + } + }, + "root_type": "Expense" + } + } +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js new file mode 100644 index 0000000000..6e33e1d7f2 --- /dev/null +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js @@ -0,0 +1,43 @@ +QUnit.module('Purchaes Invoice'); + +QUnit.test("test purchase invoice", function(assert) { + assert.expect(4); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Purchase Invoice', [ + {supplier: 'Test Supplier'}, + {items: [ + [ + {'qty': 5}, + {'item_code': 'Test Product 1'}, + {'rate':100}, + ] + ]}, + {update_stock:1}, + {supplier_address: 'Test1-Billing'}, + {contact_person: 'Contact 3-Test Supplier'}, + {taxes_and_charges: 'TEST In State GST'}, + {tc_name: 'Test Term 1'}, + {terms: 'This is Test'} + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + // get tax details + assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST', "Tax details correct"); + // get tax account head details + assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct"); + // grand_total Calculated + assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct"); + + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js new file mode 100644 index 0000000000..5e357ca2ad --- /dev/null +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js @@ -0,0 +1,26 @@ +QUnit.module('Sales Taxes and Charges Template'); + +QUnit.test("test sales taxes and charges template", function(assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Purchase Taxes and Charges Template', [ + {title: "TEST In State GST"}, + {taxes:[ + [ + {charge_type:"On Net Total"}, + {account_head:"CGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) } + ], + [ + {charge_type:"On Net Total"}, + {account_head:"SGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) } + ] + ]} + ]); + }, + () => {assert.ok(cur_frm.doc.title=='TEST In State GST');}, + () => done() + ]); +}); + diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js new file mode 100644 index 0000000000..35b255875e --- /dev/null +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js @@ -0,0 +1,43 @@ +QUnit.module('Sales Invoice'); + +QUnit.test("test sales Invoice", function(assert) { + assert.expect(4); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Sales Invoice', [ + {customer: 'Test Customer 1'}, + {items: [ + [ + {'qty': 5}, + {'item_code': 'Test Product 1'}, + ] + ]}, + {update_stock:1}, + {customer_address: 'Test1-Billing'}, + {shipping_address_name: 'Test1-Shipping'}, + {contact_person: 'Contact 1-Test Customer 1'}, + {taxes_and_charges: 'TEST In State GST'}, + {tc_name: 'Test Term 1'}, + {terms: 'This is Test'} + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + // get tax details + assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST', "Tax details correct"); + // get tax account head details + assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct"); + // grand_total Calculated + assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct"); + + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice.js new file mode 100644 index 0000000000..35b255875e --- /dev/null +++ b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice.js @@ -0,0 +1,43 @@ +QUnit.module('Sales Invoice'); + +QUnit.test("test sales Invoice", function(assert) { + assert.expect(4); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Sales Invoice', [ + {customer: 'Test Customer 1'}, + {items: [ + [ + {'qty': 5}, + {'item_code': 'Test Product 1'}, + ] + ]}, + {update_stock:1}, + {customer_address: 'Test1-Billing'}, + {shipping_address_name: 'Test1-Shipping'}, + {contact_person: 'Contact 1-Test Customer 1'}, + {taxes_and_charges: 'TEST In State GST'}, + {tc_name: 'Test Term 1'}, + {terms: 'This is Test'} + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + // get tax details + assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST', "Tax details correct"); + // get tax account head details + assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct"); + // grand_total Calculated + assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct"); + + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment.js b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment.js new file mode 100644 index 0000000000..736443e260 --- /dev/null +++ b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment.js @@ -0,0 +1,56 @@ +QUnit.module('Sales Invoice'); + +QUnit.test("test sales Invoice with payment", function(assert) { + assert.expect(4); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Sales Invoice', [ + {customer: 'Test Customer 1'}, + {items: [ + [ + {'qty': 5}, + {'item_code': 'Test Product 1'}, + ] + ]}, + {update_stock:1}, + {customer_address: 'Test1-Billing'}, + {shipping_address_name: 'Test1-Shipping'}, + {contact_person: 'Contact 1-Test Customer 1'}, + {taxes_and_charges: 'TEST In State GST'}, + {tc_name: 'Test Term 1'}, + {terms: 'This is Test'} + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + // get tax details + assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST', "Tax details correct"); + // grand_total Calculated + assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct"); + + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(2), + () => frappe.tests.click_button('Close'), + () => frappe.tests.click_button('Make'), + () => frappe.tests.click_link('Payment'), + () => frappe.timeout(0.2), + () => { cur_frm.set_value('mode_of_payment','Cash');}, + () => { cur_frm.set_value('paid_to','Cash - '+frappe.get_abbr(frappe.defaults.get_default('Company')));}, + () => {cur_frm.set_value('reference_no','TEST1234');}, + () => {cur_frm.set_value('reference_date',frappe.datetime.add_days(frappe.datetime.nowdate(), 0));}, + () => cur_frm.save(), + () => { + // get payment details + assert.ok(cur_frm.doc.paid_amount==590, "Paid Amount Correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => done() + ]); +}); + diff --git a/erpnext/accounts/page/pos/test_pos.js b/erpnext/accounts/page/pos/test_pos.js index 62c6e31eb5..bc5edc9f2a 100644 --- a/erpnext/accounts/page/pos/test_pos.js +++ b/erpnext/accounts/page/pos/test_pos.js @@ -6,11 +6,11 @@ QUnit.test("test:POS Profile", function(assert) { () => { return frappe.tests.make("POS Profile", [ {naming_series: "SINV"}, - {company: "_Test Company"}, + {company: "Test Company"}, {country: "India"}, {currency: "INR"}, - {write_off_account: "Write Off - _TC"}, - {write_off_cost_center: "Main - _TC"}, + {write_off_account: "Write Off - TC"}, + {write_off_cost_center: "Main - TC"}, {payments: [ [ {"default": 1}, @@ -35,15 +35,16 @@ QUnit.test("test:Sales Invoice", function(assert) { frappe.run_serially([ () => { return frappe.tests.make("Sales Invoice", [ - {customer: "_Test Customer 2"}, - {company: "_Test Company"}, + {customer: "Test Customer 2"}, + {company: "Test Company"}, {is_pos: 1}, {posting_date: frappe.datetime.get_today()}, {due_date: frappe.datetime.get_today()}, {items: [ [ - {"item_code": "_Test Item"}, - {"qty": 5} + {"item_code": "Test Product 1"}, + {"qty": 5}, + {"warehouse":'Stores - TC'} ]] } ]); diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js index 8d33524abb..46ed9dab39 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js @@ -7,7 +7,7 @@ frappe.query_reports["Item-wise Purchase Register"] = { "fieldname":"from_date", "label": __("From Date"), "fieldtype": "Date", - "default": frappe.defaults.get_user_default("year_start_date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), "width": "80" }, { diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js index 65cec518fe..b57a7fce9d 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js @@ -7,7 +7,7 @@ frappe.query_reports["Item-wise Sales Register"] = frappe.query_reports["Sales R "fieldname":"from_date", "label": __("From Date"), "fieldtype": "Date", - "default": frappe.defaults.get_default("year_start_date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), "width": "80" }, { diff --git a/erpnext/accounts/report/purchase_register/purchase_register.js b/erpnext/accounts/report/purchase_register/purchase_register.js index cd795310da..42b35c2a99 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.js +++ b/erpnext/accounts/report/purchase_register/purchase_register.js @@ -7,7 +7,7 @@ frappe.query_reports["Purchase Register"] = { "fieldname":"from_date", "label": __("From Date"), "fieldtype": "Date", - "default": frappe.defaults.get_user_default("year_start_date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), "width": "80" }, { diff --git a/erpnext/accounts/report/sales_register/sales_register.js b/erpnext/accounts/report/sales_register/sales_register.js index 2ac4ae8b2a..0495976cf1 100644 --- a/erpnext/accounts/report/sales_register/sales_register.js +++ b/erpnext/accounts/report/sales_register/sales_register.js @@ -7,7 +7,7 @@ frappe.query_reports["Sales Register"] = { "fieldname":"from_date", "label": __("From Date"), "fieldtype": "Date", - "default": frappe.defaults.get_default("year_start_date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), "width": "80" }, { diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js new file mode 100644 index 0000000000..e6529e60db --- /dev/null +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js @@ -0,0 +1,65 @@ +QUnit.module('Buying'); + +QUnit.test("test: purchase order", function(assert) { + assert.expect(11); + let done = assert.async(); + + frappe.run_serially([ + () => { + return frappe.tests.make('Purchase Order', [ + {supplier: 'Test Supplier'}, + {is_subcontracted: 'No'}, + {currency: 'INR'}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)}, + {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)}, + {"qty": 5}, + {"uom": 'Unit'}, + {"rate": 100}, + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} + ] + ]}, + + {tc_name: 'Test Term 1'}, + {terms: 'This is a term.'} + ]); + }, + + () => { + // Get supplier details + assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct"); + assert.ok($('div.control-value.like-disabled-input.for-description').text().includes('Contact 3'), "Contact display correct"); + assert.ok(cur_frm.doc.contact_email == 'test@supplier.com', "Contact email correct"); + // Get item details + assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item name correct"); + assert.ok(cur_frm.doc.items[0].description == 'Test Product 4', "Description correct"); + assert.ok(cur_frm.doc.items[0].qty == 5, "Quantity correct"); + // Calculate total + assert.ok(cur_frm.doc.total == 500, "Total correct"); + // Get terms + assert.ok(cur_frm.doc.terms == 'This is a term.', "Terms correct"); + }, + + () => cur_frm.print_doc(), + () => frappe.timeout(2), + () => { + assert.ok($('.btn-print-print').is(':visible'), "Print Format Available"); + assert.ok($('div > div:nth-child(5) > div > div > table > tbody > tr > td:nth-child(4) > div').text().includes('Test Product 4'), "Print Preview Works"); + }, + + () => cur_frm.print_doc(), + () => frappe.timeout(1), + + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + + () => { + assert.ok(cur_frm.doc.status == 'To Receive and Bill', "Submitted successfully"); + }, + + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js new file mode 100644 index 0000000000..8c0c144314 --- /dev/null +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js @@ -0,0 +1,61 @@ +QUnit.module('Buying'); + +QUnit.test("test: purchase order with get items", function(assert) { + assert.expect(4); + let done = assert.async(); + + frappe.run_serially([ + () => { + return frappe.tests.make('Purchase Order', [ + {supplier: 'Test Supplier'}, + {is_subcontracted: 'No'}, + {buying_price_list: 'Test-Buying-USD'}, + {currency: 'USD'}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"qty": 5}, + {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)}, + {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)}, + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} + ] + ]} + ]); + }, + + () => { + assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct"); + }, + + () => frappe.timeout(0.3), + () => frappe.click_button('Get items from'), + () => frappe.timeout(0.3), + + () => frappe.click_link('Product Bundle'), + () => frappe.timeout(0.5), + + () => cur_dialog.set_value('product_bundle', 'Computer'), + () => frappe.click_button('Get Items'), + () => frappe.timeout(1), + + // Check if items are fetched from Product Bundle + () => { + assert.ok(cur_frm.doc.items[1].item_name == 'CPU', "Product bundle item 1 correct"); + assert.ok(cur_frm.doc.items[2].item_name == 'Screen', "Product bundle item 2 correct"); + assert.ok(cur_frm.doc.items[3].item_name == 'Keyboard', "Product bundle item 3 correct"); + }, + + () => cur_frm.doc.items[1].warehouse = 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company")), + () => cur_frm.doc.items[2].warehouse = 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company")), + () => cur_frm.doc.items[3].warehouse = 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company")), + + () => cur_frm.save(), + () => frappe.timeout(1), + + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js new file mode 100644 index 0000000000..daf8d6c259 --- /dev/null +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js @@ -0,0 +1,74 @@ +QUnit.module('Buying'); + +QUnit.test("test: purchase order receipt", function(assert) { + assert.expect(5); + let done = assert.async(); + + frappe.run_serially([ + () => { + return frappe.tests.make('Purchase Order', [ + {supplier: 'Test Supplier'}, + {is_subcontracted: 'No'}, + {buying_price_list: 'Test-Buying-USD'}, + {currency: 'USD'}, + {items: [ + [ + {"item_code": 'Test Product 1'}, + {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)}, + {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)}, + {"qty": 5}, + {"uom": 'Unit'}, + {"rate": 100}, + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} + ] + ]}, + ]); + }, + + () => { + + // Check supplier and item details + assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct"); + assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 1', "Item name correct"); + assert.ok(cur_frm.doc.items[0].description == 'Test Product 1', "Description correct"); + assert.ok(cur_frm.doc.items[0].qty == 5, "Quantity correct"); + + }, + + () => frappe.timeout(1), + + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + + () => frappe.timeout(1.5), + () => frappe.click_button('Close'), + () => frappe.timeout(0.3), + + // Make Purchase Receipt + () => frappe.click_button('Make'), + () => frappe.timeout(0.3), + + () => frappe.click_link('Receipt'), + () => frappe.timeout(2), + + () => cur_frm.save(), + + // Save and submit Purchase Receipt + () => frappe.timeout(1), + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(1), + + // View Purchase order in Stock Ledger + () => frappe.click_button('View'), + () => frappe.timeout(0.3), + + () => frappe.click_link('Stock Ledger'), + () => frappe.timeout(2), + () => { + assert.ok($('div.slick-cell.l2.r2 > a').text().includes('Test Product 1') + && $('div.slick-cell.l9.r9 > div').text().includes(5), "Stock ledger entry correct"); + }, + () => done() + ]); +}); diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js new file mode 100644 index 0000000000..4e73ab8ef4 --- /dev/null +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js @@ -0,0 +1,47 @@ +QUnit.module('Buying'); + +QUnit.test("test: purchase order with discount on grand total", function(assert) { + assert.expect(4); + let done = assert.async(); + + frappe.run_serially([ + () => { + return frappe.tests.make('Purchase Order', [ + {supplier: 'Test Supplier'}, + {is_subcontracted: 'No'}, + {buying_price_list: 'Test-Buying-EUR'}, + {currency: 'EUR'}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"qty": 5}, + {"uom": 'Unit'}, + {"rate": 500 }, + {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)}, + {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)}, + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} + ] + ]}, + {apply_discount_on: 'Grand Total'}, + {additional_discount_percentage: 10} + ]); + }, + + () => frappe.timeout(1), + + () => { + assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct"); + assert.ok(cur_frm.doc.items[0].rate == 500, "Rate correct"); + // Calculate total + assert.ok(cur_frm.doc.total == 2500, "Total correct"); + // Calculate grand total after discount + assert.ok(cur_frm.doc.grand_total == 2250, "Grand total correct"); + }, + + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js new file mode 100644 index 0000000000..1e54e50dda --- /dev/null +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js @@ -0,0 +1,44 @@ +QUnit.module('Buying'); + +QUnit.test("test: purchase order with item wise discount", function(assert) { + assert.expect(4); + let done = assert.async(); + + frappe.run_serially([ + () => { + return frappe.tests.make('Purchase Order', [ + {supplier: 'Test Supplier'}, + {is_subcontracted: 'No'}, + {buying_price_list: 'Test-Buying-EUR'}, + {currency: 'EUR'}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"qty": 5}, + {"uom": 'Unit'}, + {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)}, + {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)}, + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}, + {"discount_percentage": 20} + ] + ]} + ]); + }, + + () => frappe.timeout(1), + + () => { + assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct"); + assert.ok(cur_frm.doc.items[0].discount_percentage == 20, "Discount correct"); + // Calculate totals after discount + assert.ok(cur_frm.doc.total == 2000, "Total correct"); + assert.ok(cur_frm.doc.grand_total == 2000, "Grand total correct"); + }, + + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js new file mode 100644 index 0000000000..bf2dfeb37b --- /dev/null +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js @@ -0,0 +1,39 @@ +QUnit.module('Buying'); + +QUnit.test("test: purchase order with multi UOM", function(assert) { + assert.expect(3); + let done = assert.async(); + + frappe.run_serially([ + () => { + return frappe.tests.make('Purchase Order', [ + {supplier: 'Test Supplier'}, + {is_subcontracted: 'No'}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"qty": 5}, + {"uom": 'Unit'}, + {"rate": 100}, + {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)}, + {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)}, + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} + ] + ]} + ]); + }, + + () => { + assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct"); + assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item name correct"); + assert.ok(cur_frm.doc.items[0].uom == 'Unit', "Multi UOM correct"); + }, + + () => frappe.timeout(1), + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js new file mode 100644 index 0000000000..9d87af2342 --- /dev/null +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js @@ -0,0 +1,43 @@ +QUnit.module('Buying'); + +QUnit.test("test: purchase order with taxes and charges", function(assert) { + assert.expect(3); + let done = assert.async(); + + frappe.run_serially([ + () => { + return frappe.tests.make('Purchase Order', [ + {supplier: 'Test Supplier'}, + {is_subcontracted: 'No'}, + {buying_price_list: 'Test-Buying-USD'}, + {currency: 'USD'}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"qty": 5}, + {"uom": 'Unit'}, + {"rate": 500 }, + {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)}, + {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)}, + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} + ] + ]}, + + {taxes_and_charges: 'TEST In State GST'} + ]); + }, + + () => { + // Check taxes and calculate grand total + assert.ok(cur_frm.doc.taxes[1].account_head=='SGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), "Account Head abbr correct"); + assert.ok(cur_frm.doc.total_taxes_and_charges == 225, "Taxes and charges correct"); + assert.ok(cur_frm.doc.grand_total == 2725, "Grand total correct"); + }, + + () => frappe.timeout(0.3), + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 558e072cad..8509d77e20 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -138,7 +138,6 @@ frappe.ui.form.on("Request for Quotation",{ dialog.show(); }, - make_suppplier_quotation: function(frm) { var doc = frm.doc; var dialog = new frappe.ui.Dialog({ @@ -207,6 +206,29 @@ frappe.ui.form.on("Request for Quotation Supplier",{ if(!w) { frappe.msgprint(__("Please enable pop-ups")); return; } + }, + no_quote: function(frm, cdt, cdn) { + var d = locals[cdt][cdn]; + if (d.no_quote) { + if (d.quote_status != __('Received')) { + frappe.model.set_value(cdt, cdn, 'quote_status', 'No Quote'); + } else { + frappe.msgprint(__("Cannot set a received RFQ to No Quote")); + frappe.model.set_value(cdt, cdn, 'no_quote', 0); + } + } else { + d.quote_status = __('Pending'); + frm.call({ + method:"update_rfq_supplier_status", + doc: frm.doc, + args: { + sup_name: d.supplier + }, + callback: function(r) { + frm.refresh_field("suppliers"); + } + }); + } } }) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index e9603fbcae..a775f5f345 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -54,6 +54,7 @@ class RequestforQuotation(BuyingController): frappe.db.set(self, 'status', 'Submitted') for supplier in self.suppliers: supplier.email_sent = 0 + supplier.quote_status = 'Pending' def on_cancel(self): frappe.db.set(self, 'status', 'Cancelled') @@ -157,6 +158,28 @@ class RequestforQuotation(BuyingController): attachments.append(frappe.attach_print(self.doctype, self.name, doc=self)) return attachments + def update_rfq_supplier_status(self, sup_name=None): + for supplier in self.suppliers: + if sup_name == None or supplier.supplier == sup_name: + if supplier.quote_status != _('No Quote'): + quote_status = _('Received') + for item in self.items: + sqi_count = frappe.db.sql(""" + SELECT + COUNT(sqi.name) as count + FROM + `tabSupplier Quotation Item` as sqi, + `tabSupplier Quotation` as sq + WHERE sq.supplier = %(supplier)s + AND sqi.docstatus = 1 + AND sqi.request_for_quotation_item = %(rqi)s + AND sqi.parent = sq.name""", + {"supplier": supplier.supplier, "rqi": item.name}, as_dict=1)[0] + if (sqi_count.count) == 0: + quote_status = _('Pending') + supplier.quote_status = quote_status + + @frappe.whitelist() def send_supplier_emails(rfq_name): check_portal_enabled('Request for Quotation') @@ -172,7 +195,12 @@ def check_portal_enabled(reference_doctype): def get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context list_context = get_list_context(context) - list_context["show_sidebar"] = True + list_context.update({ + 'show_sidebar': True, + 'show_search': True, + 'no_breadcrumbs': True, + 'title': _('Request for Quotation'), + }) return list_context def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py index 2e8b946f9d..5c9fb13209 100644 --- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py @@ -10,21 +10,41 @@ from erpnext.templates.pages.rfq import check_supplier_has_docname_access from frappe.utils import nowdate class TestRequestforQuotation(unittest.TestCase): + def test_quote_status(self): + from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation + rfq = make_request_for_quotation() + + self.assertEquals(rfq.get('suppliers')[0].quote_status, 'Pending') + self.assertEquals(rfq.get('suppliers')[1].quote_status, 'Pending') + + # Submit the first supplier quotation + sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier) + sq.submit() + + # No Quote first supplier quotation + rfq.get('suppliers')[1].no_quote = 1 + rfq.get('suppliers')[1].quote_status = 'No Quote' + + rfq.update_rfq_supplier_status() #rfq.get('suppliers')[1].supplier) + + self.assertEquals(rfq.get('suppliers')[0].quote_status, 'Received') + self.assertEquals(rfq.get('suppliers')[1].quote_status, 'No Quote') + def test_make_supplier_quotation(self): from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation rfq = make_request_for_quotation() - + sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier) sq.submit() - + sq1 = make_supplier_quotation(rfq.name, rfq.get('suppliers')[1].supplier) sq1.submit() - + self.assertEquals(sq.supplier, rfq.get('suppliers')[0].supplier) self.assertEquals(sq.get('items')[0].request_for_quotation, rfq.name) self.assertEquals(sq.get('items')[0].item_code, "_Test Item") self.assertEquals(sq.get('items')[0].qty, 5) - + self.assertEquals(sq1.supplier, rfq.get('suppliers')[1].supplier) self.assertEquals(sq1.get('items')[0].request_for_quotation, rfq.name) self.assertEquals(sq1.get('items')[0].item_code, "_Test Item") @@ -61,15 +81,15 @@ class TestRequestforQuotation(unittest.TestCase): rfq.get('items')[0].rate = 100 rfq.supplier = rfq.suppliers[0].supplier supplier_quotation_name = create_supplier_quotation(rfq) - + supplier_quotation_doc = frappe.get_doc('Supplier Quotation', supplier_quotation_name) - + self.assertEquals(supplier_quotation_doc.supplier, rfq.get('suppliers')[0].supplier) self.assertEquals(supplier_quotation_doc.get('items')[0].request_for_quotation, rfq.name) self.assertEquals(supplier_quotation_doc.get('items')[0].item_code, "_Test Item") self.assertEquals(supplier_quotation_doc.get('items')[0].qty, 5) self.assertEquals(supplier_quotation_doc.get('items')[0].amount, 500) - + def make_request_for_quotation(supplier_data=None): """ @@ -81,10 +101,10 @@ def make_request_for_quotation(supplier_data=None): rfq.status = 'Draft' rfq.company = '_Test Company' rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.' - + for data in supplier_data: rfq.append('suppliers', data) - + rfq.append("items", { "item_code": "_Test Item", "description": "_Test Item", @@ -93,11 +113,11 @@ def make_request_for_quotation(supplier_data=None): "warehouse": "_Test Warehouse - _TC", "schedule_date": nowdate() }) - + rfq.submit() - + return rfq - + def get_supplier_data(): return [{ "supplier": "_Test Supplier", diff --git a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js new file mode 100644 index 0000000000..46e8d1f2d6 --- /dev/null +++ b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js @@ -0,0 +1,75 @@ +QUnit.module('Buying'); + +QUnit.test("test: request_for_quotation", function(assert) { + assert.expect(14); + let done = assert.async(); + let date; + frappe.run_serially([ + () => { + date = frappe.datetime.add_days(frappe.datetime.now_date(), 10); + return frappe.tests.make('Request for Quotation', [ + {transaction_date: date}, + {suppliers: [ + [ + {"supplier": 'Test Supplier'}, + {"email_id": 'test@supplier.com'} + ] + ]}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"qty": 5}, + {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(),20)}, + {"warehouse": 'All Warehouses - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} + ] + ]}, + {message_for_supplier: 'Please supply the specified items at the best possible rates'}, + {tc_name: 'Test Term 1'} + ]); + }, + () => { + assert.ok(cur_frm.doc.transaction_date == date, "Date correct"); + assert.ok(cur_frm.doc.company == cur_frm.doc.company, "Company correct"); + assert.ok(cur_frm.doc.suppliers[0].supplier_name == 'Test Supplier', "Supplier name correct"); + assert.ok(cur_frm.doc.suppliers[0].contact == 'Contact 3-Test Supplier', "Contact correct"); + assert.ok(cur_frm.doc.suppliers[0].email_id == 'test@supplier.com', "Email id correct"); + assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item Name correct"); + assert.ok(cur_frm.doc.items[0].warehouse == 'All Warehouses - '+frappe.get_abbr(frappe.defaults.get_default("Company")), "Warehouse correct"); + assert.ok(cur_frm.doc.message_for_supplier == 'Please supply the specified items at the best possible rates', "Reply correct"); + assert.ok(cur_frm.doc.tc_name == 'Test Term 1', "Term name correct"); + }, + () => frappe.timeout(0.3), + () => cur_frm.print_doc(), + () => frappe.timeout(1), + () => { + assert.ok($('.btn-print-print').is(':visible'), "Print Format Available"); + assert.ok($('.section-break+ .section-break .column-break:nth-child(1) .value').text().includes("Test Product 4"), "Print Preview Works"); + }, + () => cur_frm.print_doc(), + () => frappe.timeout(1), + () => frappe.click_button('Get items from'), + () => frappe.timeout(0.3), + () => frappe.click_link('Material Request'), + () => frappe.timeout(1), + () => frappe.click_button('Get Items'), + () => frappe.timeout(1), + () => { + assert.ok(cur_frm.doc.items[1].item_name == 'Test Product 1', "Getting items from material requests work"); + }, + () => cur_frm.save(), + () => frappe.timeout(1), + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(1), + () => { + assert.ok(cur_frm.doc.docstatus == 1, "Quotation request submitted"); + }, + () => frappe.click_button('Send Supplier Emails'), + () => frappe.timeout(3), + () => { + assert.ok($('div.modal.fade.in > div.modal-dialog > div > div.modal-body.ui-front > div.msgprint').text().includes("Email sent to supplier Test Supplier"), "Send emails working"); + }, + () => frappe.click_button('Close'), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js new file mode 100644 index 0000000000..f831b4f42f --- /dev/null +++ b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js @@ -0,0 +1,132 @@ +QUnit.module('buying'); + +QUnit.test("Test: Request for Quotation", function (assert) { + assert.expect(5); + let done = assert.async(); + let rfq_name = ""; + + frappe.run_serially([ + // Go to RFQ list + () => frappe.set_route("List", "Request for Quotation"), + // Create a new RFQ + () => frappe.new_doc("Request for Quotation"), + () => frappe.timeout(1), + () => cur_frm.set_value("transaction_date", "04-04-2017"), + () => cur_frm.set_value("company", "_Test Company"), + // Add Suppliers + () => { + cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view(); + }, + () => frappe.timeout(1), + () => { + cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.supplier = "_Test Supplier"; + frappe.click_check('Send Email'); + cur_frm.cur_grid.frm.script_manager.trigger('supplier'); + }, + () => frappe.timeout(1), + () => { + cur_frm.cur_grid.toggle_view(); + }, + () => frappe.timeout(1), + () => frappe.click_button('Add Row',0), + () => frappe.timeout(1), + () => { + cur_frm.fields_dict.suppliers.grid.grid_rows[1].toggle_view(); + }, + () => frappe.timeout(1), + () => { + cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.supplier = "_Test Supplier 1"; + frappe.click_check('Send Email'); + cur_frm.cur_grid.frm.script_manager.trigger('supplier'); + }, + () => frappe.timeout(1), + () => { + cur_frm.cur_grid.toggle_view(); + }, + () => frappe.timeout(1), + // Add Item + () => { + cur_frm.fields_dict.items.grid.grid_rows[0].toggle_view(); + }, + () => frappe.timeout(1), + () => { + cur_frm.fields_dict.items.grid.grid_rows[0].doc.item_code = "_Test Item"; + frappe.set_control('item_code',"_Test Item"); + frappe.set_control('qty',5); + frappe.set_control('schedule_date', "05-05-2017"); + cur_frm.cur_grid.frm.script_manager.trigger('supplier'); + }, + () => frappe.timeout(2), + () => { + cur_frm.cur_grid.toggle_view(); + }, + () => frappe.timeout(2), + () => { + cur_frm.fields_dict.items.grid.grid_rows[0].doc.warehouse = "_Test Warehouse - _TC"; + }, + () => frappe.click_button('Save'), + () => frappe.timeout(1), + () => frappe.click_button('Submit'), + () => frappe.timeout(1), + () => frappe.click_button('Yes'), + () => frappe.timeout(1), + () => frappe.click_button('Menu'), + () => frappe.timeout(1), + () => frappe.click_link('Reload'), + () => frappe.timeout(1), + () => { + assert.equal(cur_frm.doc.docstatus, 1); + rfq_name = cur_frm.doc.name; + assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.quote_status == "Pending"); + assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Pending"); + }, + () => { + cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view(); + }, + () => frappe.timeout(1), + () => { + frappe.click_check('No Quote'); + }, + () => frappe.timeout(1), + () => { + cur_frm.cur_grid.toggle_view(); + }, + () => frappe.click_button('Update'), + () => frappe.timeout(1), + + () => frappe.click_button('Supplier Quotation'), + () => frappe.timeout(1), + () => frappe.click_link('Make'), + () => frappe.timeout(1), + () => { + frappe.set_control('supplier',"_Test Supplier 1"); + }, + () => frappe.timeout(1), + () => frappe.click_button('Make Supplier Quotation'), + () => frappe.timeout(1), + () => cur_frm.set_value("company", "_Test Company"), + () => cur_frm.fields_dict.items.grid.grid_rows[0].doc.rate = 4.99, + () => frappe.timeout(1), + () => frappe.click_button('Save'), + () => frappe.timeout(1), + () => frappe.click_button('Submit'), + () => frappe.timeout(1), + () => frappe.click_button('Yes'), + () => frappe.timeout(1), + () => frappe.set_route("List", "Request for Quotation"), + () => frappe.timeout(2), + () => frappe.set_route("List", "Request for Quotation"), + () => frappe.timeout(2), + () => frappe.click_link(rfq_name), + () => frappe.timeout(1), + () => frappe.click_button('Menu'), + () => frappe.timeout(1), + () => frappe.click_link('Reload'), + () => frappe.timeout(1), + () => { + assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Received"); + assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.no_quote == 1); + }, + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json b/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json index a7c5a37683..4babbe92d5 100644 --- a/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json +++ b/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json @@ -137,6 +137,69 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'", + "fieldname": "no_quote", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "No Quote", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote", + "fieldname": "quote_status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Quote Status", + "length": 0, + "no_copy": 0, + "options": "Pending\nReceived\nNo Quote", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -269,7 +332,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-07-24 06:52:19.542717", + "modified": "2017-07-26 22:25:58.096584", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation Supplier", diff --git a/erpnext/buying/doctype/supplier/test_supplier.js b/erpnext/buying/doctype/supplier/test_supplier.js index 6be40a52bf..a953a8dd13 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.js +++ b/erpnext/buying/doctype/supplier/test_supplier.js @@ -8,9 +8,8 @@ QUnit.test("test: supplier", function(assert) { return frappe.tests.make('Supplier', [ {supplier_name: 'Test Supplier'}, {supplier_type: 'Hardware'}, - {country: 'United States'}, - {default_currency: 'USD'}, - {default_price_list: 'Test-Buying-USD'}, + {country: 'India'}, + {default_currency: 'INR'}, {credit_days_based_on: 'Fixed Days'}, {accounts: [ [ @@ -68,7 +67,7 @@ QUnit.test("test: supplier", function(assert) { () => { assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Name correct"); assert.ok(cur_frm.doc.supplier_type == 'Hardware', "Type correct"); - assert.ok(cur_frm.doc.default_currency == 'USD', "Currency correct"); + assert.ok(cur_frm.doc.default_currency == 'INR', "Currency correct"); assert.ok(cur_frm.doc.accounts[0].account == 'Creditors - '+frappe.get_abbr('Test Company'), " Account Head abbr correct"); assert.ok($('.address-box:nth-child(3) p').text().includes('Shipping City 3'), "Address correct"); assert.ok($('.col-sm-6+ .col-sm-6 .h6').text().includes('Contact 3'), "Contact correct"); diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index 1cb5a18662..b3d92be4b6 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -31,9 +31,11 @@ class SupplierQuotation(BuyingController): def on_submit(self): frappe.db.set(self, "status", "Submitted") + self.update_rfq_supplier_status(1) def on_cancel(self): frappe.db.set(self, "status", "Cancelled") + self.update_rfq_supplier_status(0) def on_trash(self): pass @@ -50,6 +52,41 @@ class SupplierQuotation(BuyingController): "is_child_table": True } }) + def update_rfq_supplier_status(self, include_me): + rfq_list = set([]) + for item in self.items: + if item.request_for_quotation: + rfq_list.add(item.request_for_quotation) + for rfq in rfq_list: + doc = frappe.get_doc('Request for Quotation', rfq) + doc_sup = frappe.get_all('Request for Quotation Supplier', filters= + {'parent': doc.name, 'supplier': self.supplier}, fields=['name', 'quote_status'])[0] + + quote_status = _('Received') + for item in doc.items: + sqi_count = frappe.db.sql(""" + SELECT + COUNT(sqi.name) as count + FROM + `tabSupplier Quotation Item` as sqi, + `tabSupplier Quotation` as sq + WHERE sq.supplier = %(supplier)s + AND sqi.docstatus = 1 + AND sq.name != %(me)s + AND sqi.request_for_quotation_item = %(rqi)s + AND sqi.parent = sq.name""", + {"supplier": self.supplier, "rqi": item.name, 'me': self.name}, as_dict=1)[0] + self_count = sum(my_item.request_for_quotation_item == item.name + for my_item in self.items) if include_me else 0 + if (sqi_count.count + self_count) == 0: + quote_status = _('Pending') + if quote_status == _('Received') and doc_sup.quote_status == _('No Quote'): + frappe.msgprint(_("{0} indicates that {1} will not provide a quotation, but all items \ + have been quoted. Updating the RFQ quote status.").format(doc.name, self.supplier)) + frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status) + frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'no_quote', 0) + elif doc_sup.quote_status != _('No Quote'): + frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status) def get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js new file mode 100644 index 0000000000..76be06c6fb --- /dev/null +++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js @@ -0,0 +1,73 @@ +QUnit.module('Buying'); + +QUnit.test("test: supplier quotation", function(assert) { + assert.expect(11); + let done = assert.async(); + let date; + + frappe.run_serially([ + () => { + date = frappe.datetime.add_days(frappe.datetime.now_date(), 10); + return frappe.tests.make('Supplier Quotation', [ + {supplier: 'Test Supplier'}, + {transaction_date: date}, + {currency: 'INR'}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"qty": 5}, + {"uom": 'Unit'}, + {"rate": 200}, + {"warehouse": 'All Warehouses - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} + ] + ]}, + {apply_discount_on: 'Grand Total'}, + {additional_discount_percentage: 10}, + {tc_name: 'Test Term 1'}, + {terms: 'This is a term'} + ]); + }, + () => { + // Get Supplier details + assert.ok(cur_frm.doc.supplier == 'Test Supplier', "Supplier correct"); + assert.ok(cur_frm.doc.company == cur_frm.doc.company, "Company correct"); + // Get Contact details + assert.ok(cur_frm.doc.contact_display == 'Contact 3', "Conatct correct"); + assert.ok(cur_frm.doc.contact_email == 'test@supplier.com', "Email correct"); + // Get uom + assert.ok(cur_frm.doc.items[0].uom == 'Unit', "Multi uom correct"); + assert.ok(cur_frm.doc.total == 1000, "Total correct"); + // Calculate total after discount + assert.ok(cur_frm.doc.grand_total == 900, "Grand total correct"); + // Get terms + assert.ok(cur_frm.doc.tc_name == 'Test Term 1', "Terms correct"); + }, + + () => cur_frm.print_doc(), + () => frappe.timeout(2), + () => { + assert.ok($('.btn-print-print').is(':visible'), "Print Format Available"); + assert.ok($("table > tbody > tr > td:nth-child(3) > div").text().includes("Test Product 4"), "Print Preview Works As Expected"); + }, + () => cur_frm.print_doc(), + () => frappe.timeout(1), + () => frappe.click_button('Get items from'), + () => frappe.timeout(0.3), + () => frappe.click_link('Material Request'), + () => frappe.timeout(0.3), + () => frappe.click_button('Get Items'), + () => frappe.timeout(1), + () => { + // Get item from Material Requests + assert.ok(cur_frm.doc.items[1].item_name == 'Test Product 1', "Getting items from material requests work"); + }, + + () => cur_frm.save(), + () => frappe.timeout(1), + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js new file mode 100644 index 0000000000..bc07b753b3 --- /dev/null +++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js @@ -0,0 +1,34 @@ +QUnit.module('Buying'); + +QUnit.test("test: supplier quotation with item wise discount", function(assert){ + assert.expect(2); + let done = assert.async(); + + frappe.run_serially([ + () => { + return frappe.tests.make('Supplier Quotation', [ + {supplier: 'Test Supplier'}, + {company: 'Test Company'}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"qty": 5}, + {"uom": 'Unit'}, + {"warehouse": 'All Warehouses - TC'}, + {'discount_percentage': 10}, + ] + ]} + ]); + }, + + () => { + assert.ok(cur_frm.doc.total == 900, "Total correct"); + assert.ok(cur_frm.doc.grand_total == 900, "Grand total correct"); + }, + + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js new file mode 100644 index 0000000000..c4b341952c --- /dev/null +++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js @@ -0,0 +1,37 @@ +QUnit.module('Buying'); + +QUnit.test("test: supplier quotation with taxes and charges", function(assert) { + assert.expect(3); + let done = assert.async(); + let supplier_quotation_name; + + frappe.run_serially([ + () => { + return frappe.tests.make('Supplier Quotation', [ + {supplier: 'Test Supplier'}, + {items: [ + [ + {"item_code": 'Test Product 4'}, + {"qty": 5}, + {"rate": 100}, + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + ] + ]}, + {taxes_and_charges:'TEST In State GST'}, + ]); + }, + () => {supplier_quotation_name = cur_frm.doc.name;}, + () => { + assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct"); + assert.ok(cur_frm.doc.total_taxes_and_charges == 45, "Taxes and charges correct"); + assert.ok(cur_frm.doc.grand_total == 545, "Grand total correct"); + }, + + () => cur_frm.save(), + () => frappe.timeout(0.3), + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/config/manufacturing.py b/erpnext/config/manufacturing.py index a930ddc6ef..11711ad004 100644 --- a/erpnext/config/manufacturing.py +++ b/erpnext/config/manufacturing.py @@ -70,8 +70,8 @@ def get_data(): "items": [ { "type": "doctype", - "name": "BOM Replace Tool", - "description": _("Replace Item / BOM in all BOMs"), + "name": "BOM Update Tool", + "description": _("Replace BOM and update latest price in all BOMs"), }, ] }, diff --git a/erpnext/docs/assets/img/manufacturing/bom-replace-tool.png b/erpnext/docs/assets/img/manufacturing/bom-replace-tool.png deleted file mode 100644 index 51ac99324b..0000000000 Binary files a/erpnext/docs/assets/img/manufacturing/bom-replace-tool.png and /dev/null differ diff --git a/erpnext/docs/assets/img/manufacturing/bom-update-tool.png b/erpnext/docs/assets/img/manufacturing/bom-update-tool.png new file mode 100644 index 0000000000..7dbd47fe0e Binary files /dev/null and b/erpnext/docs/assets/img/manufacturing/bom-update-tool.png differ diff --git a/erpnext/docs/assets/img/setup/print/print-style.png b/erpnext/docs/assets/img/setup/print/print-style.png new file mode 100644 index 0000000000..db93ceb0e6 Binary files /dev/null and b/erpnext/docs/assets/img/setup/print/print-style.png differ diff --git a/erpnext/docs/user/manual/en/accounts/multi-currency-accounting.md b/erpnext/docs/user/manual/en/accounts/multi-currency-accounting.md index 598aa0e7f6..060cd17d99 100644 --- a/erpnext/docs/user/manual/en/accounts/multi-currency-accounting.md +++ b/erpnext/docs/user/manual/en/accounts/multi-currency-accounting.md @@ -23,6 +23,15 @@ You can change accounting currency in Party / Account record, until making any t In case of multi-company setup, accounting currency of Party must be same for all the companies. +### Exchange Rates +When dealing with multiple currencies, ERPNext has the Currency Exchange module for managing exchange rates. It allows you to save the exchange rate quotes you require. + +For foreign currency transactions, ERPNext checks Currency Exchange for any matching record. If this fails, ERPNext will attempt to get the exchange rate quote from [fixer.io](http://fixer.io). If this still fails, then the exchange rate will have to be entered manually. + +#### Exchange Rate Selection +ERPNext automatically fetches the latest exchange rate available. + + ## Transactions ### Sales Invoice @@ -50,61 +59,6 @@ In Accounts table, on selection of foreign currency account, system will show Cu Journal Entry in multi currency -#### Example 1: Payment Entry Against Customer With Alternate Currency - -Suppose, default currency of the company is INR and customer's accounting currency is USD. Customer made full payment against an outstanding invoice of USD 100. Exchange Rate (USD -> INR) in Sales Invoice was 60. - -Exchange Rate in the payment entry should always be same as invoice (60), even if exchange rate on the payment date is 62. The bank account will be credited by the amount considering exchange rate as 62. Hence, Exchnage Gain / Loss will be booked based on exchange rate difference. - -Payment Entry - -#### Example 2: Inter-bank Transfer (USD -> INR) - -Suppose the default currency of the company is INR. You have a Paypal account for which Currency is USD. You receive payments in the paypal account and lets say, paypal transfers amount once in a week to your other bank account which is managed in INR. - -Paypal account gets debited on different date with different exchange rate, but on transfer date the exchange rate can be different. Hence, there is generally Exchange Loss / Gain on the transfer entry. -In the bank transfer entry, system sets exchange rate of the credit account (Paypal) based on the average incoming exchange rate. This is to maintain Paypal balance properly in company currency. In case you modify the average exchange rate, you need to adjust the exchange rate manually in the future entries, so that balance in account currency and company currency are in sync. -Then you should calculate and enter Exchange Loss / Gain based on the Paypal exchange rate and the exchange rate on the transfer date. - -Lets say, Paypal account debited by following amounts over the week, which has not been transferred to your other bank account. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
DateAccountDebit (USD)Exchange Rate
2015-09-02Paypal10060
2015-09-02Paypal10061
2015-09-02Paypal10064
- - -Suppose, Exchange Rate on the payment date is 62 and Bank Transfer Entry will be look like below: - -Inter Bank Transfer - - ## Reports ### General Ledger diff --git a/erpnext/docs/user/manual/en/buying/request-for-quotation.md b/erpnext/docs/user/manual/en/buying/request-for-quotation.md index 182c89d4e6..a48c297a6f 100644 --- a/erpnext/docs/user/manual/en/buying/request-for-quotation.md +++ b/erpnext/docs/user/manual/en/buying/request-for-quotation.md @@ -2,43 +2,40 @@ A Request for Quotation is a document that an organization submits to one or more suppliers eliciting quotation for items. -In ERPNext, You can create request for quotation directly by going to: +In ERPNext, You can create Request for Quotation directly by going to: > Buying > Documents > Request for Quotation > New Request for Quotation -![Request For Quotation](/docs/assets/img/buying/request-for-quotation.png) +After creation of Request for Quotation, there are two ways to generate Supplier Quotation from Request for Quotation. -After creation of request for quotation, there are two ways to generate supplier quotation from request for quotation. #### For User -__Step 1:__ Open request for quotation and click on make supplier quotation. +__Step 1:__ Open Request for Quotation and click on make Supplier Quotation. ![Request For Quotation](/docs/assets/img/buying/make-supplier-quotation-from-rfq.png) -__Step 2:__ Select supplier and click on make supplier quotation. +__Step 2:__ Select supplier and click on make Supplier Quotation. ![Request For Quotation](/docs/assets/img/buying/supplier-selection-from-rfq.png) -__Step 3:__ System will open the supplier quotation, user has to enter the rate and submit it. +__Step 3:__ System will open the Supplier Quotation, user has to enter the rate and submit it. ![Request For Quotation](/docs/assets/img/buying/supplier-quotation-from-rfq.png) #### For Supplier -__Step 1:__ User has to create contact or enter Email Address against the supplier on request for quotation. +__Step 1:__ User has to create contact or enter Email Address against the supplier on Request for Quotation. ![Request For Quotation](/docs/assets/img/buying/set-email-id.png) __Step 2:__ User has to click on send supplier emails button. -![Request For Quotation](/docs/assets/img/buying/send-supplier-emails.png) - -* If supplier's user not available: system will create supplier's user and send details to the supplier, supplier will need to click on the link(Password Update) present in the email. After password update supplier can access his portal with the request for quotation form. +* If supplier's user not available: system will create supplier's user and send details to the supplier, supplier will need to click on the link(Password Update) present in the email. After password update supplier can access his portal with the Request for Quotation form. ![Request For Quotation](/docs/assets/img/buying/supplier-password-update-link.png) -* If supplier's user available: system will send request for quotation link to supplier, supplier has to login using his credentials to view request for quotation form on portal. +* If supplier's user available: system will send Request for Quotation link to supplier, supplier has to login using his credentials to view Request for Quotation form on portal. ![Request For Quotation](/docs/assets/img/buying/send-rfq-link.png) @@ -46,9 +43,12 @@ __Step 3:__ Supplier has to enter amount and notes(payment terms) on the form an ![Request For Quotation](/docs/assets/img/buying/supplier-portal-rfq.png) -__Step 4:__ On submission, system will create supplier quotation(draft mode) against the supplier. User has to review the supplier quotation - and submit it. - -More details:- +__Step 4:__ On submission, system will create Supplier Quotation (draft mode) against the supplier. The user has to review the Supplier Quotation + and submit it. When all items from the Request for Quotation have been quoted by a supplier, the status is updated in the Supplier + table of the Request for Quotation. -![Request For Quotation](/docs/assets/img/buying/request-for-quotation.gif) \ No newline at end of file +#### More details + +If a supplier indicates that they will not provide a quotation for the item, this can be indicated in the RFQ document by checking the 'No Quote' box after the Request for Quotation has been submitted. + +![Request For Quotation](/docs/assets/img/buying/request-for-quotation.gif) diff --git a/erpnext/docs/user/manual/en/manufacturing/tools/bom-replace-tool.md b/erpnext/docs/user/manual/en/manufacturing/tools/bom-replace-tool.md deleted file mode 100644 index 98d1f3b985..0000000000 --- a/erpnext/docs/user/manual/en/manufacturing/tools/bom-replace-tool.md +++ /dev/null @@ -1,44 +0,0 @@ -# BOM Replace Tool - -Replace BOM is the utility to replace BOM of sub-assembly item, which is already updated in the BOM of Finished Good item. - -To use the Production Planning Tool, go to: - -> Manufacturing > Tools > BOM Replace Tool - -Let's consider a scenario to understand this better. - -If company manufactures computers, Bill of Material of its finished item will constitute of: - -1. Monitor -1. Key Board -1. Mouse -1. CPU - -Out of all the items above, CPU is asembled separately. Hence separate BOM will be created for the CPU. Following are the items from the BOM of CPU. - -1. 250 GB Hard Disk -1. Mother Board -1. Processor -1. SMTP -1. DVD player - -If we have more items to be added , or existing items to be edited in the BOM of CPU, then we should create new BOM for it. - -1. _350 GB Hard Disk_ -1. Mother Board -1. Processor -1. SMTP -1. DVD player - -To update new BOM updated in the BOM of finished item, where CPU is selected as raw-material, you can use BOM Replace tool. - -BOM replace Tool - -In this tool, you should select Current BOM, and New BOM. On clicking Replace button, current BOM of CPU will be replaced with New BOM in the BOM of finished Item (Computer). - -**Will BOM Replace Tool work for replacing finsihed item in BOM?** - -No. You should Cancel and Amend current BOM, or create a new BOM for finished item. - -{next} diff --git a/erpnext/docs/user/manual/en/manufacturing/tools/bom-update-tool.md b/erpnext/docs/user/manual/en/manufacturing/tools/bom-update-tool.md new file mode 100644 index 0000000000..41503531b6 --- /dev/null +++ b/erpnext/docs/user/manual/en/manufacturing/tools/bom-update-tool.md @@ -0,0 +1,54 @@ +# BOM Update Tool + +From BOM Update Tool, you can replace a sub-assembly BOM and update costs of all BOMs. + +### Replace BOM +Using this utility, you can replace an existing BOM of sub-assembly item, with a new one. The system will update the new BOM in all the parent BOMs where it was used. + +To use the BOM Update Tool, go to: + +> Manufacturing > Tools > BOM Update Tool + +Let's consider a scenario to understand this better. + +Suppose a company manufactures computers, Bill of Material of of the computer will look like this: + +1. Monitor +1. Key Board +1. Mouse +1. CPU + +Out of all the items above, CPU is asembled separately. Hence separate BOM will be created for the CPU. Following are the items from the BOM of CPU. + +1. 250 GB Hard Disk +1. Mother Board +1. Processor +1. SMTP +1. DVD player + +If we have more items to be added , or existing items to be edited in the BOM of CPU, then we should create new BOM for it. + +1. _350 GB Hard Disk_ +1. Mother Board +1. Processor +1. SMTP +1. DVD player + +To update new BOM in all the parent BOMs, where CPU is selected as raw-material, you can use Replace utility. + +BOM Update Tool + +In this tool, you should select Current BOM, and New BOM. On clicking Replace button, current BOM of CPU will be replaced with New BOM in the BOM of finished Item (Computer). + +**Will BOM Replace Tool work for replacing finsihed item in BOM?** + +No. You should Cancel and Amend current BOM, or create a new BOM for finished item. + +### Update BOM Cost +Using the button **Update latest price in all BOMs**, you can update cost of all Bill of Materials, based on latest purchase price / price list rate / valuation rate of raw materials. + +On clicking of this buttom, system will create a background process to update all the BOM's cost. It is processed via background jobs because this process can take a few minutes (depending on the number of BOMs) to update all the BOMs. + +This functionality can also be executed automatically on daily basis. For that, you need to enable "Update BOM Cost Automatically" from Manufacturing Settings. + +{next} diff --git a/erpnext/docs/user/manual/en/manufacturing/tools/index.txt b/erpnext/docs/user/manual/en/manufacturing/tools/index.txt index aaf7cecc70..a385154589 100644 --- a/erpnext/docs/user/manual/en/manufacturing/tools/index.txt +++ b/erpnext/docs/user/manual/en/manufacturing/tools/index.txt @@ -1,2 +1,2 @@ production-planning-tool -bom-replace-tool \ No newline at end of file +bom-update-tool \ No newline at end of file diff --git a/erpnext/docs/user/manual/en/setting-up/print/index.txt b/erpnext/docs/user/manual/en/setting-up/print/index.txt index fa23652564..9597cc06cf 100644 --- a/erpnext/docs/user/manual/en/setting-up/print/index.txt +++ b/erpnext/docs/user/manual/en/setting-up/print/index.txt @@ -1,5 +1,6 @@ print-settings print-format-builder +print-style print-headings letter-head address-template diff --git a/erpnext/docs/user/manual/en/setting-up/print/print-style.md b/erpnext/docs/user/manual/en/setting-up/print/print-style.md new file mode 100644 index 0000000000..bdb0ab0e26 --- /dev/null +++ b/erpnext/docs/user/manual/en/setting-up/print/print-style.md @@ -0,0 +1,15 @@ +# Print Style + +Frappe/ERPNext comes with pre-set styles for your printed documents. You can also create new styles using CSS that can be applied to all your print formats. + +To create a new **Print Style** go to **Setup > Printing and Branding > Print Style**, or just type "print style" in the search bar. + +Here you can define the CSS rules for your print formats. These apply to both standard and custom print formats. To find out the various classes available, you can make a standard print format, open in a new page and see the source. + +To set a default style, you can go to [Print Settings](/docs/setup/print/print-settings) + +All Print Format styles are based on Bootstrap (Version 3) CSS Framework. + +Print Style + +{next} \ No newline at end of file diff --git a/erpnext/docs/user/manual/es/index.txt b/erpnext/docs/user/manual/es/index.txt index d19ef97543..feb68a2fe0 100644 --- a/erpnext/docs/user/manual/es/index.txt +++ b/erpnext/docs/user/manual/es/index.txt @@ -1,2 +1,3 @@ introduction accounts +schools diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/__init__.py b/erpnext/docs/user/manual/es/schools/Assessment/__init__.py similarity index 100% rename from erpnext/manufacturing/doctype/bom_replace_tool/__init__.py rename to erpnext/docs/user/manual/es/schools/Assessment/__init__.py diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_criteria.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_criteria.md new file mode 100644 index 0000000000..92f338fd8b --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_criteria.md @@ -0,0 +1,13 @@ +#Criterios de Evaluación + +Criterios de evaluación es el parámetro basado en el que se evalúa el estudiante. + +Assessment Criteria + +Después de la evaluación para un curso, las calificaciones obtenidas se ingresan en base a los criterios de evaluación. Por ejemplo, si la evaluación se llevó a cabo para el tema de la ciencia, entonces usted puede evaluar al estudiante en ciencia en varios criterios como la escritura, prácticas, presentación, etc + +Los Criterios de Evaluación se usan al programar el Plan de Evaluación para el Grupo de Estudiantes y el Curso. + +Assessment Plan Criteria + +{next} diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_group.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_group.md new file mode 100644 index 0000000000..7102888f28 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_group.md @@ -0,0 +1,13 @@ +#Grupo de Evaluación + +La estructura del grupo de evaluación es un maestro donde se puede definir la jerarquía para el examen realizado en su instituto de educación. + +Por ejemplo, si realiza dos evaluaciones en un año académico, configure el Grupo de evaluación de la siguiente manera. + +Assessment Group Term + +En la misma línea, también puede definir varios Grupo de Evaluación basado sobre la evaluación llevada a cabo en su instituto. + +Assessment Group Term + +{next} diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_plan.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_plan.md new file mode 100644 index 0000000000..9d66c213f8 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_plan.md @@ -0,0 +1,19 @@ +#Plan de Evaluación + +Para programar una evaluación/examinación para un Grupo de Estudiantes, para un curso específico, crea un Plan de Evaluación. En el plan de evaluación, puedes capturar detalles como: + +1. Escala de Calificaciones basada en qué calificaciones serán asignadas a los estudiantes. + +2. Fecha de planificación de la Evaluación + +3. Aula donde se va a llevar a cabo la evaluación + +4. Examinador y Supervisor + +Assessment Plan Details + +5. Los Criterios de Evaluación son la lista de criterios basados ​​en que cada estudiante en será evaluado y los grados serán asignados. + +Assessment Plan Criteria + +{next} diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_result.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_result.md new file mode 100644 index 0000000000..2d9409708d --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_result.md @@ -0,0 +1,7 @@ +#Resultados de Evaluación + +El resultado de la evaluación es un registro de las calificaciones obtenidas por el estudiante para una evaluación específica. El resultado de la evaluación se crea en el backend en base a los puntos en [Herramienta de Resultados de Evaluación](/docs/user/manual/es/schools/assessment/assessment_result_tool.html). + +Assessment Result + +{next} diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_result_tool.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_result_tool.md new file mode 100644 index 0000000000..70e4ee3f2f --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_result_tool.md @@ -0,0 +1,10 @@ +#Herramienta de resultados de la evaluación + + +Herramienta de resultados de evaluación le ayuda a ingresar las calificaciones obtenidas por los estudiantes para un curso específico. En esta herramienta, basada en el plan de evaluación, todos los estudiantes van a ser filtrados dentro de la herramienta de resultados de la evaluación. También, Columnas para los Criterios de Evaluación serán donde las calificaciones ganadas pueden ser ingresadas para cada Estudiante. + +Assessment Result Tool + +A medida que vaya introduciendo las notas para un Estudiante y cambie al siguiente alumno, en el backend, el registro de Resultados del Estudiante se creará automáticamente para ese Estudiante. + +{next} diff --git a/erpnext/docs/user/manual/es/schools/Assessment/grading_scale.md b/erpnext/docs/user/manual/es/schools/Assessment/grading_scale.md new file mode 100644 index 0000000000..9107f92a56 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/Assessment/grading_scale.md @@ -0,0 +1,7 @@ +#Escala de calificación + +En la escala de calificación, puedes definir varios grados y límites para los estudiantes. Basado en la calificación obtenida por el estudiante en la evaluación, el grado (Grade) será asignado. + +Grading Scale + +{next} diff --git a/erpnext/docs/user/manual/es/schools/Assessment/index.md b/erpnext/docs/user/manual/es/schools/Assessment/index.md new file mode 100644 index 0000000000..5b4c35a263 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/Assessment/index.md @@ -0,0 +1,19 @@ +#Evaluación + +Cada instituto de educación organiza evaluaciones / examenes para evaluar el progreso de sus estudiantes. En ERPNext, puedes gestionar completamente el proceso de evaluación para sus cuentas en ERPNext. + + + +A continuación se muestra el orden en el que debe configurar los masters en el módulo de evaluación. + +1. Criterio de Evaluación +2. Grupo de Evaluación +3. Escala de Evaluación + +Una vez tengas definido los Grupos de Estudiantes y Cursos, puedes programar una evaluación / examinación creando un Plan de Evaluación. + +Basado en el rendimiento del estudiante en la evaluación, puedes ingresar los resultados de la evaluación por un estudiante. Puedes ingresar todos los registros de los estudiantes usando la herramienta de resultados de evaluación. En esta herramienta, en la selecció del Plan de Evaluación, todos los estudiantes (De un grupo de estudiantes) van a ser buscados y mostrados. Puedes rápidamente ingresar los puntos obtenidos por cada estudiante en cada uno de los Criterios de Evaluación en una sola linea. + +### Temas + +{index} diff --git a/erpnext/docs/user/manual/es/schools/Assessment/index.txt b/erpnext/docs/user/manual/es/schools/Assessment/index.txt new file mode 100644 index 0000000000..61d744c721 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/Assessment/index.txt @@ -0,0 +1,6 @@ +assessment_criteria +assessment_group +grading_scale +assessment_plan +assessment_result_tool +assessment_result \ No newline at end of file diff --git a/erpnext/docs/user/manual/es/schools/__init__.py b/erpnext/docs/user/manual/es/schools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/docs/user/manual/es/schools/admission/__init__.py b/erpnext/docs/user/manual/es/schools/admission/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/docs/user/manual/es/schools/admission/index.md b/erpnext/docs/user/manual/es/schools/admission/index.md new file mode 100644 index 0000000000..df44679d9a --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/admission/index.md @@ -0,0 +1,7 @@ +# Admisiones + +Esta sección contiene los documentos relacionados a adminisiones de estudiantes. + +### Temas + +{index} diff --git a/erpnext/docs/user/manual/es/schools/admission/index.txt b/erpnext/docs/user/manual/es/schools/admission/index.txt new file mode 100644 index 0000000000..ec9e768116 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/admission/index.txt @@ -0,0 +1,2 @@ +student-applicant +program-enrollment \ No newline at end of file diff --git a/erpnext/docs/user/manual/es/schools/admission/program-enrollment.md b/erpnext/docs/user/manual/es/schools/admission/program-enrollment.md new file mode 100644 index 0000000000..34e8996a25 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/admission/program-enrollment.md @@ -0,0 +1,7 @@ +# Inscripción al Programa + +Este formulario te permite inscribir un estudiante a un programa. Un estudiante puede ser inscrito en multiples programas. + +Student Applicant Enrollment + +{next} diff --git a/erpnext/docs/user/manual/es/schools/admission/student-applicant.md b/erpnext/docs/user/manual/es/schools/admission/student-applicant.md new file mode 100644 index 0000000000..33d8dd7b29 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/admission/student-applicant.md @@ -0,0 +1,28 @@ +# Aplicación de Estudiante + +Un registro de Aplicación de Estudiante necesita ser creado cuando un estudiante aplica para un programa en su institución. +Puedes Aprobar o Rechazar una aplicación de estudiante. Aceptando una aplicación de un estudiante puedes agregarlos al master de estudiantes. + +Student Applicant + +### Estados de la Aplicación + +- Por defecto cuando una aplicación de estudiante es creada en el sistema, el estado de la aplicación es seteado a 'Applied' + +- Puedes actualizar el estado a 'Approved' una vez apruebas la aplicación de estudiante para unirse a su institución + +- Cuando el estado de la aplicación es actualizada a 'Approved', el botón de 'Inscribir' debería aparecer. + Puedes crear un registro de estudiante usando la aplicación de estudiante e inscribirlos a un programa al presionar el botón de Inscribir + +- Una vez el estudiante es creado usando la aplicación de estudiante, el sistema va a setear el estado del documento de aplicación a 'Admitted' + y no te va a permiter cambiar el estado de la aplicación a menos que el estudiante sea eliminado + +### Registro de Estudiante + + +Una vez aprobada una Aplicación de Estudiante, puedes inscribirlo a un programa. Cuando le das click al butón 'Inscribir', +el sistema creará un estudiante usando esa aplicación y le va a redireccionar a el [Formulario de Inscripción al Programa](/docs/user/manual/es/schools/student/program-enrollment.html). + +Student Applicant Enrollment + +{next} diff --git a/erpnext/docs/user/manual/es/schools/fees/__init__.py b/erpnext/docs/user/manual/es/schools/fees/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/docs/user/manual/es/schools/fees/fee-category.md b/erpnext/docs/user/manual/es/schools/fees/fee-category.md new file mode 100644 index 0000000000..c05b3f549f --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/fees/fee-category.md @@ -0,0 +1,7 @@ +# Categoría de Cuota + +Todos los tipos diferentes de cuotas que se cobran + +Fees Category + +{next} diff --git a/erpnext/docs/user/manual/es/schools/fees/fee-structure.md b/erpnext/docs/user/manual/es/schools/fees/fee-structure.md new file mode 100644 index 0000000000..1f621d716d --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/fees/fee-structure.md @@ -0,0 +1,7 @@ +# Estructura de Cuota + +Una Estructura de Cuota es una plantilla que puede ser usada cuando se hacen registros de cuotas. + +Fees Structure + +{next} diff --git a/erpnext/docs/user/manual/es/schools/fees/fees.md b/erpnext/docs/user/manual/es/schools/fees/fees.md new file mode 100644 index 0000000000..d6b74dc300 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/fees/fees.md @@ -0,0 +1,8 @@ +# Cuotas + +Mantiene un registro de todas las cuotas recolectadas de los estudiantes. +La [Estructura de Cuota](/docs/user/manual/es/schools/fees/fee-structure.html) es seleccionada basada en el programa seleccionada y los Términos Académicos. + +Fees + +{next} diff --git a/erpnext/docs/user/manual/es/schools/fees/index.md b/erpnext/docs/user/manual/es/schools/fees/index.md new file mode 100644 index 0000000000..e88381378d --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/fees/index.md @@ -0,0 +1,9 @@ +# Cuota + +Esta sección contiene todos los documentos relacionado a las 'Cuota' + +Fees Section + +### Temas + +{index} diff --git a/erpnext/docs/user/manual/es/schools/fees/index.txt b/erpnext/docs/user/manual/es/schools/fees/index.txt new file mode 100644 index 0000000000..265ac6a528 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/fees/index.txt @@ -0,0 +1,3 @@ +fees +fee-structure +fee-category \ No newline at end of file diff --git a/erpnext/docs/user/manual/es/schools/index.md b/erpnext/docs/user/manual/es/schools/index.md new file mode 100644 index 0000000000..a1824dc6e8 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/index.md @@ -0,0 +1,8 @@ +# Schools + + +Los módulos de School estan diseñados para satisfacer los requerimientos de Escuelas, Colegios e Institutos Educacionales. + +Fees Section + +{index} diff --git a/erpnext/docs/user/manual/es/schools/index.txt b/erpnext/docs/user/manual/es/schools/index.txt new file mode 100644 index 0000000000..b485fdcfea --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/index.txt @@ -0,0 +1,6 @@ +student +admission +schedule +fees +setup +assessment \ No newline at end of file diff --git a/erpnext/docs/user/manual/es/schools/schedule/__init__.py b/erpnext/docs/user/manual/es/schools/schedule/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/docs/user/manual/es/schools/schedule/course-schedule.md b/erpnext/docs/user/manual/es/schools/schedule/course-schedule.md new file mode 100644 index 0000000000..629c828451 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/schedule/course-schedule.md @@ -0,0 +1,25 @@ +# Horario de un Curso + +El Horario de Curso es el horario de una sesión de un profesor para un Curso en particular. +Puedes ver un resumen del horario del curso en la vista de Calendario. + +Course Schedule + +### Marcando asistencia + +Puedes pasar la asistencia para un grupo de estudiantes usando el Horario de Curso. + +Course Schedule Attendance + +- Para hacer la asistencia, expandir la sección de asistencia. +- Selecciona los estudiantes que estaban presentes para esa sesión. +- Click en el botón de 'Mark Attendance'. El sistema va a crear los registros de asistencia automáticamente. + +### Ver Asistencia + +Una vez hayas marcado la asistencia usando la sección de asistencia en el Horario de un Curso, esta sección debería estar oculta. +Un botón de Ver Asistencia debería aparecer. Click en el botón para ver todos los registros de asistencia creados para ese Horario de Curso. + +Course Schedule Attendance + +{next} diff --git a/erpnext/docs/user/manual/es/schools/schedule/examination.md b/erpnext/docs/user/manual/es/schools/schedule/examination.md new file mode 100644 index 0000000000..5f85aedd41 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/schedule/examination.md @@ -0,0 +1,8 @@ +# Examinación + +El registro de examinación puede ser usado para hacer el seguimiento del horario de los examenes y los resultados de los mismos. + +Examination + + +{next} diff --git a/erpnext/docs/user/manual/es/schools/schedule/index.md b/erpnext/docs/user/manual/es/schools/schedule/index.md new file mode 100644 index 0000000000..f9c2c3b5d1 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/schedule/index.md @@ -0,0 +1,7 @@ +# Horario + +Schedule Section + +### Temas + +{index} diff --git a/erpnext/docs/user/manual/es/schools/schedule/index.txt b/erpnext/docs/user/manual/es/schools/schedule/index.txt new file mode 100644 index 0000000000..704ad84b89 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/schedule/index.txt @@ -0,0 +1,4 @@ +course-schedule +student-attendance +scheduling-tool +examination \ No newline at end of file diff --git a/erpnext/docs/user/manual/es/schools/schedule/scheduling-tool.md b/erpnext/docs/user/manual/es/schools/schedule/scheduling-tool.md new file mode 100644 index 0000000000..070a0354ec --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/schedule/scheduling-tool.md @@ -0,0 +1,23 @@ +# Herramienta para Horarios + +Esta herramienta puede ser usada para crear los Horarios de los Cursos. + +Scheduling Tool + +### Creando Horarios de Cursos + +- Selecciona el Grupo de Estudiante para el cual vas a establecer el Horario. +- Seleccione el curso, Aula y un Instructor para el Horario. +- Específica los rangos de fecha desde-hasta para el horario de los cursos. +- Especifíca Fecha Inicio y Fecha Final de un curso(Los Horarios de los cursos van a ser creados dentro de este rango de fechas) +- Espeficíca el dia de la semana en el cual deseas establecer el horario. +- Presiona el botón de 'Schedule Course' +- El sistema va a crear la planificación si el Aula y el Instructor estan disponibles y no hay ningún conflicto con el grupo de estudiantes seleccionados con otros horarios para el mismo grupo. + +###Reprogramar Horario + +- Si deseas reprogramar el horario de un curso, sigue las instrucciones para crear horarios de cursos. +- Selecciona el checkbox 'Reschedule' y luego da click en el botón 'Schedule Course'. +- El sistema va a eliminar la progración de horario especifícada para ese curso dentro del rango de fecha seleccionado y crearia la nueva planificación de horario para el curso. + +{next} diff --git a/erpnext/docs/user/manual/es/schools/schedule/student-attendance.md b/erpnext/docs/user/manual/es/schools/schedule/student-attendance.md new file mode 100644 index 0000000000..a06c4f276d --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/schedule/student-attendance.md @@ -0,0 +1,7 @@ +# Asistencia de Estudiante + +Mantiene los registros de la asistencia del estudiante. Los registros de asistencia pueden ser creados sobre los horarios de los cursos (Course Schedules). + +Student Attendance + +{next} diff --git a/erpnext/docs/user/manual/es/schools/setup/__init__.py b/erpnext/docs/user/manual/es/schools/setup/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/docs/user/manual/es/schools/setup/academic-term.md b/erpnext/docs/user/manual/es/schools/setup/academic-term.md new file mode 100644 index 0000000000..83af58ac31 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/setup/academic-term.md @@ -0,0 +1,6 @@ +# Término Académico + +Academic Term + + +{next} diff --git a/erpnext/docs/user/manual/es/schools/setup/academic-year.md b/erpnext/docs/user/manual/es/schools/setup/academic-year.md new file mode 100644 index 0000000000..56a46f9c39 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/setup/academic-year.md @@ -0,0 +1,5 @@ +# Año Académico + +Academic Year + +{next} diff --git a/erpnext/docs/user/manual/es/schools/setup/course.md b/erpnext/docs/user/manual/es/schools/setup/course.md new file mode 100644 index 0000000000..799f9b46e1 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/setup/course.md @@ -0,0 +1,5 @@ +# Curso + +Course + +{next} diff --git a/erpnext/docs/user/manual/es/schools/setup/index.md b/erpnext/docs/user/manual/es/schools/setup/index.md new file mode 100644 index 0000000000..070db54015 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/setup/index.md @@ -0,0 +1,7 @@ +# Configuración + +Setup Section + +### Temas + +{index} diff --git a/erpnext/docs/user/manual/es/schools/setup/index.txt b/erpnext/docs/user/manual/es/schools/setup/index.txt new file mode 100644 index 0000000000..fb9ba05d09 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/setup/index.txt @@ -0,0 +1,6 @@ +course +program +instructor +room +academic-term +academic-year \ No newline at end of file diff --git a/erpnext/docs/user/manual/es/schools/setup/instructor.md b/erpnext/docs/user/manual/es/schools/setup/instructor.md new file mode 100644 index 0000000000..1a4d35161c --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/setup/instructor.md @@ -0,0 +1,5 @@ +# Instructor + +Instructor + +{next} \ No newline at end of file diff --git a/erpnext/docs/user/manual/es/schools/setup/program.md b/erpnext/docs/user/manual/es/schools/setup/program.md new file mode 100644 index 0000000000..78895c5806 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/setup/program.md @@ -0,0 +1,5 @@ +# Programa + +Program + +{next} diff --git a/erpnext/docs/user/manual/es/schools/setup/room.md b/erpnext/docs/user/manual/es/schools/setup/room.md new file mode 100644 index 0000000000..92a4de77af --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/setup/room.md @@ -0,0 +1,6 @@ +# Aula + + +Room + +{next} diff --git a/erpnext/docs/user/manual/es/schools/student/__init__.py b/erpnext/docs/user/manual/es/schools/student/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/docs/user/manual/es/schools/student/index.md b/erpnext/docs/user/manual/es/schools/student/index.md new file mode 100644 index 0000000000..2d9c6e1049 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/student/index.md @@ -0,0 +1,7 @@ +# Student + +Esta sección contiene los documentos relacionados a los estudiantes. + +### Temas + +{index} diff --git a/erpnext/docs/user/manual/es/schools/student/index.txt b/erpnext/docs/user/manual/es/schools/student/index.txt new file mode 100644 index 0000000000..9b31be4dc9 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/student/index.txt @@ -0,0 +1,5 @@ +student +student-log +student-batch +student-group +student-group-creation-tool diff --git a/erpnext/docs/user/manual/es/schools/student/student-batch.md b/erpnext/docs/user/manual/es/schools/student/student-batch.md new file mode 100644 index 0000000000..4d5a17e671 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/student/student-batch.md @@ -0,0 +1,8 @@ +# Lote de Estudiantes + + +Un lote de estudiantes es una colección de estudiantes desde los Grupos de Estudiantes. + +Student + +{next} diff --git a/erpnext/docs/user/manual/es/schools/student/student-group-creation-tool.md b/erpnext/docs/user/manual/es/schools/student/student-group-creation-tool.md new file mode 100644 index 0000000000..2102c34de9 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/student/student-group-creation-tool.md @@ -0,0 +1,8 @@ +# Herramienta para creación de Grupo de Estudiantes + +Esta herramienta te permite crear grupos de estudiantes. Puedes especificar multiples parámetros para crearlos. + + +Student Group Creation Tool + +{next} diff --git a/erpnext/docs/user/manual/es/schools/student/student-group.md b/erpnext/docs/user/manual/es/schools/student/student-group.md new file mode 100644 index 0000000000..f5841cc060 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/student/student-group.md @@ -0,0 +1,8 @@ +# Grupo de Estudiante + +Un Grupo de Estudiante es una colección de estudiantes tomando el mismo curso. Puedes crear calendarios para los cursos y examinaciones para un Grupo de Estudiante. +Un Grupo de Estudiante necesita ser creado para cada curso en un año o término académico en particular. + +Student Group + +{next} diff --git a/erpnext/docs/user/manual/es/schools/student/student-log.md b/erpnext/docs/user/manual/es/schools/student/student-log.md new file mode 100644 index 0000000000..296b867c60 --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/student/student-log.md @@ -0,0 +1,8 @@ +# Bitácora de Estudiante (Log) + +Puedes crear una nota de una actividad de un estudiante usando la bitácora de estudiante (log) +Los registros de bitágora pueden ser categorizadas como 'General', 'Academic', 'Medical' or 'Achievement' + +Student + +{next} diff --git a/erpnext/docs/user/manual/es/schools/student/student.md b/erpnext/docs/user/manual/es/schools/student/student.md new file mode 100644 index 0000000000..fee84c5c4c --- /dev/null +++ b/erpnext/docs/user/manual/es/schools/student/student.md @@ -0,0 +1,10 @@ +# Estudiante + +Un estudiante es una persona que se ha registrado en su institución y has aceptado se solicitud de aplicación. +El doctype de Estudiante mantiene los detalles personales de los estudiantes. + +Puedes ver todo lo relacionado a un estudiante en particular en esta página. Ejemplo: Pagos, Grupo de Estudiante, etc. + +Student + +{next} diff --git a/erpnext/hooks.py b/erpnext/hooks.py index fc8d39b7ba..7e65fc9262 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -50,7 +50,8 @@ calendars = ["Task", "Production Order", "Leave Application", "Sales Order", "Ho fixtures = ["Web Form"] -website_generators = ["Item Group", "Item", "BOM", "Sales Partner", "Job Opening", "Student Admission"] +website_generators = ["Item Group", "Item", "BOM", "Sales Partner", + "Job Opening", "Student Admission"] website_context = { "favicon": "/assets/erpnext/images/favicon.png", @@ -83,7 +84,7 @@ website_route_rules = [ {"from_route": "/quotations/", "to_route": "order", "defaults": { "doctype": "Quotation", - "parents": [{"label": _("Quotations"), "route": "quotation"}] + "parents": [{"label": _("Quotations"), "route": "quotations"}] } }, {"from_route": "/shipments", "to_route": "Delivery Note"}, @@ -195,7 +196,8 @@ scheduler_events = { "erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary", "erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status", "erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards", - "erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history" + "erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history", + "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", ] } diff --git a/erpnext/hr/doctype/employee/test_employee.js b/erpnext/hr/doctype/employee/test_employee.js index 64a6b7a9bf..b7f510506b 100644 --- a/erpnext/hr/doctype/employee/test_employee.js +++ b/erpnext/hr/doctype/employee/test_employee.js @@ -1,39 +1,40 @@ QUnit.module('hr'); QUnit.test("Test: Employee [HR]", function (assert) { - assert.expect(3); + assert.expect(4); let done = assert.async(); - let today_date = frappe.datetime.nowdate(); - - frappe.run_serially([ + // let today_date = frappe.datetime.nowdate(); + let employee_creation = (name,joining_date,birth_date) => { + frappe.run_serially([ // test employee creation - () => frappe.set_route("List", "Employee", "List"), - () => frappe.new_doc("Employee"), - () => frappe.timeout(1), - () => cur_frm.set_value("employee_name", "Test Employee 1"), - () => cur_frm.set_value("salutation", "Ms"), - () => cur_frm.set_value("company", "Test Company"), - () => cur_frm.set_value("date_of_joining", frappe.datetime.add_months(today_date, -2)), // joined 2 month from now - () => cur_frm.set_value("date_of_birth", frappe.datetime.add_months(today_date, -240)), // age is 20 years - () => cur_frm.set_value("employment_type", "Test Employment type"), - () => cur_frm.set_value("holiday_list", "Test Holiday list"), - () => cur_frm.set_value("branch", "Test Branch"), - () => cur_frm.set_value("department", "Test Department"), - () => cur_frm.set_value("designation", "Test Designation"), - () => frappe.click_button('Add Row'), - () => cur_frm.fields_dict.leave_approvers.grid.grid_rows[0].doc.leave_approver="Administrator", - // save data - () => cur_frm.save(), - () => frappe.timeout(1), - // check name of employee - () => assert.equal("Test Employee 1", cur_frm.doc.employee_name, - 'name of employee correctly saved'), - // check auto filled gender according to salutation - () => assert.equal("Female", cur_frm.doc.gender, - 'gender correctly saved as per salutation'), - // check auto filled retirement date [60 years from DOB] - () => assert.equal(frappe.datetime.add_months(today_date, 480), cur_frm.doc.date_of_retirement, // 40 years from now - 'retirement date correctly saved as per date of birth'), + () => { + frappe.tests.make('Employee', [ + { employee_name: name}, + { salutation: 'Mr'}, + { company: 'Test Company'}, + { date_of_joining: joining_date}, + { date_of_birth: birth_date}, + { employment_type: 'Test Employment Type'}, + { holiday_list: 'Test Holiday List'}, + { branch: 'Test Branch'}, + { department: 'Test Department'}, + { designation: 'Test Designation'} + ]); + }, + () => frappe.timeout(2), + () => { + assert.ok(cur_frm.get_field('employee_name').value==name, + 'Name of an Employee is correctly set'); + assert.ok(cur_frm.get_field('gender').value=='Male', + 'Gender of an Employee is correctly set'); + }, + ]); + }; + frappe.run_serially([ + () => employee_creation('Test Employee 1','2017-04-01','1992-02-02'), + () => frappe.timeout(6), + () => employee_creation('Test Employee 3','2017-04-01','1992-02-02'), + () => frappe.timeout(4), () => done() ]); }); \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js b/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js index a71ba0f2a2..3ec8ac0a59 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js +++ b/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js @@ -1,10 +1,10 @@ QUnit.module('hr'); QUnit.test("Test: Employee attendance tool [HR]", function (assert) { - assert.expect(3); + assert.expect(2); let done = assert.async(); let today_date = frappe.datetime.nowdate(); - let date_of_attendance = frappe.datetime.add_days(today_date, -1); // previous day + let date_of_attendance = frappe.datetime.add_days(today_date, -2); // previous day frappe.run_serially([ // create employee @@ -38,11 +38,9 @@ QUnit.test("Test: Employee attendance tool [HR]", function (assert) { () => frappe.set_route("List", "Attendance", "List"), () => frappe.timeout(1), () => { - assert.deepEqual(["Test Employee 2", "Test Employee 1"], [cur_list.data[0].employee_name, cur_list.data[1].employee_name], - "marked attendance correctly saved for both employee"); let marked_attendance = cur_list.data.filter(d => d.attendance_date == date_of_attendance); - assert.equal(marked_attendance.length, 2, - 'both the attendance are marked for correct date'); + assert.equal(marked_attendance.length, 3, + 'all the attendance are marked for correct date'); }, () => done() ]); diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.js b/erpnext/hr/doctype/job_applicant/test_job_applicant.js new file mode 100644 index 0000000000..b5391c8bf3 --- /dev/null +++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.js @@ -0,0 +1,29 @@ +QUnit.module('hr'); + +QUnit.test("Test: Job Opening [HR]", function (assert) { + assert.expect(2); + let done = assert.async(); + + frappe.run_serially([ + // Job Applicant creation + () => { + frappe.tests.make('Job Applicant', [ + { applicant_name: 'Utkarsh Goswami'}, + { email_id: 'goswamiutkarsh0@gmail.com'}, + { job_title: 'software-developer'}, + { cover_letter: 'Highly skilled in designing, testing, and developing software.'+ + ' This is just a test.'} + ]); + }, + () => frappe.timeout(4), + () => frappe.set_route('List','Job Applicant'), + () => frappe.timeout(3), + () => { + assert.ok(cur_list.data.length==1, 'Job Applicant created successfully'); + assert.ok(cur_list.data[0].name=='Utkarsh Goswami - goswamiutkarsh0@gmail.com - software-developer', + 'Correct job applicant with valid job title'); + }, + () => done() + ]); +}); + diff --git a/erpnext/hr/doctype/job_opening/test_job_opening.js b/erpnext/hr/doctype/job_opening/test_job_opening.js new file mode 100644 index 0000000000..b9e6c0a8b2 --- /dev/null +++ b/erpnext/hr/doctype/job_opening/test_job_opening.js @@ -0,0 +1,27 @@ +QUnit.module('hr'); + +QUnit.test("Test: Job Opening [HR]", function (assert) { + assert.expect(2); + let done = assert.async(); + + frappe.run_serially([ + // Job Opening creation + () => { + frappe.tests.make('Job Opening', [ + { job_title: 'Software Developer'}, + { description: + 'You might be responsible for writing and coding individual'+ + ' programmes or providing an entirely new software resource.'} + ]); + }, + () => frappe.timeout(4), + () => frappe.set_route('List','Job Opening'), + () => frappe.timeout(3), + () => { + assert.ok(cur_list.data.length==1, 'Job Opening created successfully'); + assert.ok(cur_list.data[0].job_title=='Software Developer', 'Job title Correctly set'); + }, + () => done() + ]); +}); + diff --git a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js index e71ff6e396..5133c0c282 100644 --- a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js +++ b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js @@ -28,8 +28,8 @@ QUnit.test("Test: Leave control panel [HR]", function (assert) { () => frappe.timeout(1), () => { let leave_allocated = cur_list.data.filter(d => d.leave_type == "Test Leave type"); - assert.equal(2, leave_allocated.length, - 'leave allocation successfully done for both the employee'); + assert.equal(3, leave_allocated.length, + 'leave allocation successfully done for all the employees'); }, () => done() ]); diff --git a/erpnext/hr/doctype/offer_letter/test_offer_letter.js b/erpnext/hr/doctype/offer_letter/test_offer_letter.js new file mode 100644 index 0000000000..2069532612 --- /dev/null +++ b/erpnext/hr/doctype/offer_letter/test_offer_letter.js @@ -0,0 +1,51 @@ +QUnit.module('hr'); + +QUnit.test("Test: Offer Letter [HR]", function (assert) { + assert.expect(3); + let done = assert.async(); + frappe.run_serially([ + // Offer Letter Creation + () => { + frappe.tests.make('Offer Letter', [ + { job_applicant: 'Utkarsh Goswami - goswamiutkarsh0@gmail.com - software-developer'}, + { applicant_name: 'Utkarsh Goswami'}, + { status: 'Accepted'}, + { designation: 'Software Developer'}, + { offer_terms: [ + [ + {offer_term: 'Responsibilities'}, + {value: 'Design, installation, testing and maintenance of software systems.'} + ], + [ + {offer_term: 'Department'}, + {value: 'Research & Development'} + ], + [ + {offer_term: 'Probationary Period'}, + {value: 'The Probation period is for 3 months.'} + ] + ]}, + ]); + }, + () => frappe.timeout(8), + () => frappe.click_button('Submit'), + () => frappe.timeout(2), + () => frappe.click_button('Yes'), + () => frappe.timeout(8), + () => { + // To check if the fields are correctly set + assert.ok(cur_frm.get_field('status').value=='Accepted', + 'Status of job offer is correct'); + assert.ok(cur_frm.get_field('designation').value=='Software Developer', + 'Designation of applicant is correct'); + }, + () => frappe.set_route('List','Offer Letter','List'), + () => frappe.timeout(2), + // Checking the submission of and offer letter + () => { + assert.ok(cur_list.data[0].docstatus==1,'Offer Letter Submitted successfully'); + }, + () => frappe.timeout(4), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/process_payroll/test_process_payroll.js b/erpnext/hr/doctype/process_payroll/test_process_payroll.js new file mode 100644 index 0000000000..7e23fb9c98 --- /dev/null +++ b/erpnext/hr/doctype/process_payroll/test_process_payroll.js @@ -0,0 +1,59 @@ +QUnit.module('hr'); + +QUnit.test("Test: Process Payroll [HR]", function (assert) { + assert.expect(5); + let done = assert.async(); + let net_pay; + + let check_amounts = (employee_name,net_amt,gross_amt) => { + frappe.run_serially([ + // Retrieving the actual amount from salary slip + () => frappe.db.get_value('Salary Slip', {'employee_name': employee_name}, 'net_pay'), + (r) => { + net_pay=r.message.net_pay; + }, + () => frappe.db.get_value('Salary Slip', {'employee_name': employee_name}, 'gross_pay'), + + // Checking if amounts are correctly calculated + (r) => { + assert.ok(net_pay==net_amt, + 'Net Pay is correctly calculated for '+employee_name); + assert.ok(r.message.gross_pay==gross_amt, + 'Gross Pay is correctly calculated for '+employee_name); + }, + ]); + }; + frappe.run_serially([ + + // Deleting the already generated Salary Slips for employees + () => frappe.set_route('List','Salary Slip'), + () => frappe.timeout(2), + () => { $('input.list-row-checkbox').click();}, + () => frappe.click_button('Delete'), + () => frappe.click_button('Yes'), + () => frappe.timeout(2), + () => assert.ok(cur_list.data.length==0,"Salary Slips successfully deleted"), + () => frappe.timeout(3), + + + // Creating Process Payroll for specific company + () => frappe.set_route('Form','Process Payroll'), + () => { + cur_frm.set_value('company','Test Company'), + frappe.timeout(1), + cur_frm.set_value('payroll_frequency','Monthly'), + cur_frm.set_value('start_date','2017-08-01'), + frappe.timeout(1), + cur_frm.set_value('end_date','2017-08-31'), + cur_frm.set_value('cost_center','Main-TC'), + frappe.timeout(1), + frappe.click_button('Create Salary Slip'); + }, + () => frappe.timeout(3), + () => check_amounts('Test Employee 1','19200','24000'), + () => frappe.timeout(3), + () => check_amounts('Test Employee 3','23040','28800'), + () => frappe.timeout(4), + () => done() + ]); +}); diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.js b/erpnext/hr/doctype/salary_slip/test_salary_slip.js new file mode 100644 index 0000000000..a49c973d13 --- /dev/null +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.js @@ -0,0 +1,49 @@ +QUnit.test("test salary slip", function(assert) { + assert.expect(6); + let done = assert.async(); + let employee_name; + + let salary_slip = (ename) => { + frappe.run_serially([ + () => frappe.db.get_value('Employee', {'employee_name': ename}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => { + // Creating a salary slip for a employee + frappe.tests.make('Salary Slip', [ + { employee: employee_name} + ]); + }, + () => frappe.timeout(1), + () => { + // To check if all the calculations are correctly done + if(ename === 'Test Employee 1') + { + assert.ok(cur_frm.doc.gross_pay==24000, + 'Gross amount for first employee is correctly calculated'); + assert.ok(cur_frm.doc.total_deduction==4800, + 'Deduction amount for first employee is correctly calculated'); + assert.ok(cur_frm.doc.net_pay==19200, + 'Net amount for first employee is correctly calculated'); + } + if(ename === 'Test Employee 3') + { + assert.ok(cur_frm.doc.gross_pay==28800, + 'Gross amount for second employee is correctly calculated'); + assert.ok(cur_frm.doc.total_deduction==5760, + 'Deduction amount for second employee is correctly calculated'); + assert.ok(cur_frm.doc.net_pay==23040, + 'Net amount for second employee is correctly calculated'); + } + }, + ]); + }; + frappe.run_serially([ + () => salary_slip('Test Employee 1'), + () => frappe.timeout(6), + () => salary_slip('Test Employee 3'), + () => frappe.timeout(3), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.js b/erpnext/hr/doctype/salary_structure/test_salary_structure.js new file mode 100644 index 0000000000..23b52f6a1d --- /dev/null +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.js @@ -0,0 +1,81 @@ +QUnit.test("test Salary Structure", function(assert) { + assert.expect(6); + let done = assert.async(); + let employee_name1; + + let salary_structure = (ename1,ename2) => { + frappe.run_serially([ + () => frappe.db.get_value('Employee', {'employee_name': ename1}, 'name'), + (r) => { + employee_name1 = r.message.name; + }, + () => frappe.db.get_value('Employee', {'employee_name': ename2}, 'name'), + (r) => { + // Creating Salary Structure for employees); + frappe.tests.make('Salary Structure', [ + { company: 'Test Company'}, + { payroll_frequency: 'Monthly'}, + { employees: [ + [ + {employee: employee_name1}, + {from_date: '2017-07-01'}, + {base: 25000} + ], + [ + {employee: r.message.name}, + {from_date: '2017-07-01'}, + {base: 30000} + ] + ]}, + { earnings: [ + [ + {salary_component: 'Basic'}, + {formula: 'base * .80'} + ], + [ + {salary_component: 'Leave Encashment'}, + {formula: 'B * .20'} + ] + ]}, + { deductions: [ + [ + {salary_component: 'Income Tax'}, + {formula: '(B+LE) * .20'} + ] + ]}, + { payment_account: 'CASH - TC'}, + ]); + }, + () => frappe.timeout(10), + () => cur_dialog.set_value('value','Test Salary Structure'), + () => frappe.timeout(2), + () => frappe.click_button('Create'), + () => { + // To check if all the fields are correctly set + assert.ok(cur_frm.doc.employees[0].employee_name.includes('Test Employee 1') && + cur_frm.doc.employees[1].employee_name.includes('Test Employee 3'), + 'Employee names are correctly set'); + + assert.ok(cur_frm.doc.employees[0].base==25000, + 'Base value for first employee is correctly set'); + + assert.ok(cur_frm.doc.employees[1].base==30000, + 'Base value for second employee is correctly set'); + + assert.ok(cur_frm.doc.earnings[0].formula.includes('base * .80'), + 'Formula for earnings as Basic is correctly set'); + + assert.ok(cur_frm.doc.earnings[1].formula.includes('B * .20'), + 'Formula for earnings as Leave Encashment is correctly set'); + + assert.ok(cur_frm.doc.deductions[0].formula.includes('(B+LE) * .20'), + 'Formula for deductions as Income Tax is correctly set'); + }, + ]); + }; + frappe.run_serially([ + () => salary_structure('Test Employee 1','Test Employee 3'), + () => frappe.timeout(16), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index 05d3df565a..bb200a997e 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -15,6 +15,12 @@ def execute(filters=None): att_map = get_attendance_list(conditions, filters) emp_map = get_employee_details() + holiday_list = [emp_map[d]["holiday_list"] for d in emp_map if emp_map[d]["holiday_list"]] + default_holiday_list = frappe.db.get_value("Company", filters.get("company"), "default_holiday_list") + holiday_list.append(default_holiday_list) + holiday_list = list(set(holiday_list)) + holiday_map = get_holiday(holiday_list, filters["month"]) + data = [] for emp in sorted(att_map): emp_det = emp_map.get(emp) @@ -27,7 +33,11 @@ def execute(filters=None): total_p = total_a = total_l = 0.0 for day in range(filters["total_days_in_month"]): status = att_map.get(emp).get(day + 1, "None") - status_map = {"Present": "P", "Absent": "A", "Half Day": "H", "On Leave": "L", "None": ""} + status_map = {"Present": "P", "Absent": "A", "Half Day": "HD", "On Leave": "L", "None": "", "Holiday":"H"} + if status == "None" and holiday_map: + emp_holiday_list = emp_det.holiday_list if emp_det.holiday_list else default_holiday_list + if (day+1) in holiday_map[emp_holiday_list]: + status = "Holiday" row.append(status_map[status]) if status == "Present": @@ -88,13 +98,21 @@ def get_conditions(filters): def get_employee_details(): emp_map = frappe._dict() - for d in frappe.db.sql("""select name, employee_name, designation, - department, branch, company - from tabEmployee""", as_dict=1): + for d in frappe.db.sql("""select name, employee_name, designation, department, branch, company, + holiday_list from tabEmployee""", as_dict=1): emp_map.setdefault(d.name, d) return emp_map +def get_holiday(holiday_list, month): + holiday_map = frappe._dict() + for d in holiday_list: + if d: + holiday_map.setdefault(d, frappe.db.sql_list('''select day(holiday_date) from `tabHoliday` + where parent=%s and month(holiday_date)=%s''', (d, month))) + + return holiday_map + @frappe.whitelist() def get_attendance_years(): year_list = frappe.db.sql_list("""select distinct YEAR(attendance_date) from tabAttendance ORDER BY YEAR(attendance_date) DESC""") diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 347314b2c1..c58c89cd5d 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -5,7 +5,7 @@ frappe.provide("erpnext.bom"); frappe.ui.form.on("BOM", { setup: function(frm) { - frm.add_fetch('buying_price_list', 'currency', 'currency') + frm.add_fetch('buying_price_list', 'currency', 'currency'); frm.set_query("bom_no", "items", function() { return { @@ -13,15 +13,15 @@ frappe.ui.form.on("BOM", { 'currency': frm.doc.currency, 'company': frm.doc.company } - } + }; }); - + frm.set_query("source_warehouse", "items", function() { return { filters: { 'company': frm.doc.company, } - } + }; }); }, @@ -57,10 +57,14 @@ frappe.ui.form.on("BOM", { doc: frm.doc, method: "update_cost", freeze: true, + args: { + update_parent: true, + from_child_bom:false + }, callback: function(r) { if(!r.exc) frm.refresh_fields(); } - }) + }); } }); diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index f41087b686..46a1ffd101 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -232,7 +232,7 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, @@ -262,7 +262,7 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, @@ -291,6 +291,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "set_rate_of_sub_assembly_item_based_on_bom", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Set rate of sub-assembly item based on BOM", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1640,7 +1671,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-11 14:09:30.492628", + "modified": "2017-08-23 14:09:30.492628", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 7130a3e94d..71ff43f48a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -92,7 +92,7 @@ class BOM(WebsiteGenerator): def validate_rm_item(self, item): if (item[0]['name'] in [it.item_code for it in self.items]) and item[0]['name'] == self.item: - frappe.throw(_("Raw material cannot be same as main Item")) + frappe.throw(_("BOM #{0}: Raw material cannot be same as main Item").format(self.name)) def set_bom_material_details(self): for item in self.get("items"): @@ -155,15 +155,17 @@ class BOM(WebsiteGenerator): rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list, "item_code": arg["item_code"]}, "price_list_rate") or 0 - if not rate and arg['bom_no']: - rate = self.get_bom_unitcost(arg['bom_no']) + if arg['bom_no'] and (not rate or self.set_rate_of_sub_assembly_item_based_on_bom): + rate = self.get_bom_unitcost(arg['bom_no']) return rate - def update_cost(self): + def update_cost(self, update_parent=True, from_child_bom=False): if self.docstatus == 2: return + existing_bom_cost = self.total_cost + for d in self.get("items"): rate = self.get_bom_material_detail({'item_code': d.item_code, 'bom_no': d.bom_no, 'stock_qty': d.stock_qty})["rate"] @@ -176,7 +178,16 @@ class BOM(WebsiteGenerator): self.save() self.update_exploded_items() - frappe.msgprint(_("Cost Updated")) + # update parent BOMs + if self.total_cost != existing_bom_cost and update_parent: + parent_boms = frappe.db.sql_list("""select distinct parent from `tabBOM Item` + where bom_no = %s and docstatus=1""", self.name) + + for bom in parent_boms: + frappe.get_doc("BOM", bom).update_cost(from_child_bom=True) + + if not from_child_bom: + frappe.msgprint(_("Cost Updated")) def get_bom_unitcost(self, bom_no): bom = frappe.db.sql("""select name, total_cost/quantity as unit_cost from `tabBOM` @@ -566,4 +577,27 @@ def get_children(): where bom_item.parent=%s and bom_item.item_code = item.name order by bom_item.idx - """, frappe.form_dict.parent, as_dict=True) \ No newline at end of file + """, frappe.form_dict.parent, as_dict=True) + +def get_boms_in_bottom_up_order(bom_no=None): + def _get_parent(bom_no): + return frappe.db.sql_list("""select distinct parent from `tabBOM Item` + where bom_no = %s and docstatus=1""", bom_no) + + count = 0 + bom_list = [] + if bom_no: + bom_list.append(bom_no) + else: + # get all leaf BOMs + bom_list = frappe.db.sql_list("""select name from `tabBOM` bom where docstatus=1 + and not exists(select bom_no from `tabBOM Item` + where parent=bom.name and ifnull(bom_no, '')!='')""") + + while(count < len(bom_list)): + for child_bom in _get_parent(bom_list[count]): + if child_bom not in bom_list: + bom_list.append(child_bom) + count += 1 + + return bom_list \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 4e520ef233..d678444e4d 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -6,6 +6,8 @@ from __future__ import unicode_literals import unittest import frappe from frappe.utils import cstr +from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation +from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost test_records = frappe.get_test_records('BOM') @@ -46,6 +48,32 @@ class TestBOM(unittest.TestCase): bom.save() self.assertTrue(_get_default_bom_in_item(), bom.name) + + def test_update_bom_cost_in_all_boms(self): + # get current rate for '_Test Item 2' + rm_rate = frappe.db.sql("""select rate from `tabBOM Item` + where parent='BOM-_Test Item Home Desktop Manufactured-001' + and item_code='_Test Item 2' and docstatus=1""") + rm_rate = rm_rate[0][0] if rm_rate else 0 + + # update valuation rate of item '_Test Item 2' + warehouse_list = frappe.db.sql_list("""select warehouse from `tabBin` + where item_code='_Test Item 2' and actual_qty > 0""") + + if not warehouse_list: + warehouse_list.append("_Test Warehouse - _TC") + + for warehouse in warehouse_list: + create_stock_reconciliation(item_code="_Test Item 2", warehouse=warehouse, + qty=200, rate=rm_rate + 10) + + # update cost of all BOMs based on latest valuation rate + update_cost() + # check if new valuation rate updated in all BOMs + for d in frappe.db.sql("""select rate from `tabBOM Item` + where item_code='_Test Item 2' and docstatus=1""", as_dict=1): + self.assertEqual(d.rate, rm_rate + 10) + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/README.md b/erpnext/manufacturing/doctype/bom_replace_tool/README.md deleted file mode 100644 index 4abce74e85..0000000000 --- a/erpnext/manufacturing/doctype/bom_replace_tool/README.md +++ /dev/null @@ -1 +0,0 @@ -Tool to replace one Item with another in all Bill of Material (BOM) trees. \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.js b/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.js deleted file mode 100644 index 8a3f70ce1d..0000000000 --- a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.js +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - - -cur_frm.cscript.refresh = function(doc) { - cur_frm.disable_save(); -} - -cur_frm.set_query("current_bom", function(doc) { - return{ - query: "erpnext.controllers.queries.bom", - filters: {name: "!" + doc.new_bom} - } -}); - - -cur_frm.set_query("new_bom", function(doc) { - return{ - query: "erpnext.controllers.queries.bom", - filters: {name: "!" + doc.current_bom} - } -}); \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.json b/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.json deleted file mode 100644 index bf7e6fc38d..0000000000 --- a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "allow_copy": 1, - "allow_import": 0, - "allow_rename": 0, - "creation": "2012-12-06 12:10:10", - "custom": 0, - "description": "Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Other", - "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "The BOM which will be replaced", - "fieldname": "current_bom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Current BOM", - "no_copy": 0, - "options": "BOM", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "The new BOM after replacement", - "fieldname": "new_bom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New BOM", - "no_copy": 0, - "options": "BOM", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "replace", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Replace", - "no_copy": 0, - "options": "replace_bom", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "hide_heading": 1, - "hide_toolbar": 1, - "icon": "fa fa-magic", - "idx": 1, - "in_create": 1, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "modified": "2015-08-12 08:52:46.035343", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "BOM Replace Tool", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Manufacturing Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "read_only": 1, - "read_only_onload": 0 -} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_tool/__init__.py b/erpnext/manufacturing/doctype/bom_update_tool/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js new file mode 100644 index 0000000000..a4b48af5a5 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js @@ -0,0 +1,34 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('BOM Update Tool', { + setup: function(frm) { + frm.set_query("current_bom", function() { + return { + query: "erpnext.controllers.queries.bom", + filters: {name: "!" + frm.doc.new_bom} + }; + }); + + frm.set_query("new_bom", function() { + return { + query: "erpnext.controllers.queries.bom", + filters: {name: "!" + frm.doc.current_bom} + }; + }); + }, + + refresh: function(frm) { + frm.disable_save(); + }, + + update_latest_price_in_all_boms: function() { + frappe.call({ + method: "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.enqueue_update_cost", + freeze: true, + callback: function() { + frappe.msgprint(__("Latest price updated in all BOMs")); + } + }); + } +}); \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json new file mode 100644 index 0000000000..ab63c0b540 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json @@ -0,0 +1,244 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2012-12-06 12:10:10", + "custom": 0, + "description": "Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM.\nIt also updates latest price in all the BOMs.", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Other", + "editable_grid": 0, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "replace_bom_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Replace BOM", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "The BOM which will be replaced", + "fieldname": "current_bom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Current BOM", + "length": 0, + "no_copy": 0, + "options": "BOM", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "The new BOM after replacement", + "fieldname": "new_bom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "New BOM", + "length": 0, + "no_copy": 0, + "options": "BOM", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "replace", + "fieldtype": "Button", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Replace", + "length": 0, + "no_copy": 0, + "options": "replace_bom", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "update_cost_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Update Cost", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "update_latest_price_in_all_boms", + "fieldtype": "Button", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Update latest price in all BOMs", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 1, + "hide_toolbar": 1, + "icon": "icon-magic", + "idx": 1, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2017-07-31 18:08:05.919276", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "BOM Update Tool", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Manufacturing Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 1, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py similarity index 63% rename from erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py rename to erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index f0a834c37b..91b5070dbd 100644 --- a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -1,14 +1,15 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt from __future__ import unicode_literals import frappe from frappe.utils import cstr, flt from frappe import _ - +from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order from frappe.model.document import Document -class BOMReplaceTool(Document): +class BOMUpdateTool(Document): def replace_bom(self): self.validate_bom() self.update_new_bom() @@ -40,3 +41,17 @@ class BOMReplaceTool(Document): return [d[0] for d in frappe.db.sql("""select distinct parent from `tabBOM Item` where ifnull(bom_no, '') = %s and docstatus < 2""", self.new_bom)] + +@frappe.whitelist() +def enqueue_update_cost(): + frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost") + frappe.msgprint(_("Queued for updating latest price in all Bill of Materials. It may take a few minutes.")) + +def update_latest_price_in_all_boms(): + if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"): + update_cost() + +def update_cost(): + bom_list = get_boms_in_bottom_up_order() + for bom in bom_list: + frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True) \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js new file mode 100644 index 0000000000..d220df2824 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: BOM Update Tool", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially('BOM Update Tool', [ + // insert a new BOM Update Tool + () => frappe.tests.make([ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py new file mode 100644 index 0000000000..154addf14e --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -0,0 +1,30 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + + +from __future__ import unicode_literals +import unittest +import frappe + +test_records = frappe.get_test_records('BOM') + +class TestBOMUpdateTool(unittest.TestCase): + def test_replace_bom(self): + current_bom = "BOM-_Test Item Home Desktop Manufactured-001" + + bom_doc = frappe.copy_doc(test_records[0]) + bom_doc.items[1].item_code = "_Test Item" + bom_doc.insert() + + update_tool = frappe.get_doc("BOM Update Tool") + update_tool.current_bom = current_bom + update_tool.new_bom = bom_doc.name + update_tool.replace_bom() + + self.assertFalse(frappe.db.sql("select name from `tabBOM Item` where bom_no=%s", current_bom)) + self.assertTrue(frappe.db.sql("select name from `tabBOM Item` where bom_no=%s", bom_doc.name)) + + # reverse, as it affects other testcases + update_tool.current_bom = bom_doc.name + update_tool.new_bom = current_bom + update_tool.replace_bom() \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 9f12e4554a..455a983e76 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -1,23 +1,32 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, + "beta": 0, "creation": "2014-11-27 14:12:07.542534", "custom": 0, "docstatus": 0, "doctype": "DocType", "document_type": "Document", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "capacity_planning", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Capacity Planning", "length": 0, "no_copy": 0, @@ -26,6 +35,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -33,16 +43,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Disables creation of time logs against Production Orders. Operations shall not be tracked against Production Order", "fieldname": "disable_capacity_planning", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Disable Capacity Planning and Time Tracking", "length": 0, "no_copy": 0, @@ -51,6 +66,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -58,16 +74,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Plan time logs outside Workstation Working Hours.", "fieldname": "allow_overtime", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Allow Overtime", "length": 0, "no_copy": 0, @@ -76,6 +97,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -83,16 +105,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "", "fieldname": "allow_production_on_holidays", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Allow Production on Holidays", "length": 0, "no_copy": 0, @@ -102,6 +129,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -109,15 +137,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "column_break_3", "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -125,6 +158,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -132,17 +166,22 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "30", "description": "Try planning operations for X days in advance.", "fieldname": "capacity_planning_for_days", "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Capacity Planning For (Days)", "length": 0, "no_copy": 0, @@ -151,6 +190,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -158,16 +198,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Default 10 mins", "fieldname": "mins_between_operations", "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Time Between Operations (in mins)", "length": 0, "no_copy": 0, @@ -176,6 +221,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -183,15 +229,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "section_break_6", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -199,6 +250,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -206,15 +258,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "over_production_allowance_percentage", "fieldtype": "Percent", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Over Production Allowance Percentage", "length": 0, "no_copy": 0, @@ -223,6 +280,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -230,16 +288,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "BOM", "fieldname": "backflush_raw_materials_based_on", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Backflush Raw Materials Based On", "length": 0, "no_copy": 0, @@ -249,6 +312,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -256,15 +320,22 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", + "columns": 0, + "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.", + "fieldname": "update_bom_costs_automatically", + "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, + "label": "Update BOM Cost Automatically", "length": 0, "no_copy": 0, "permlevel": 0, @@ -272,6 +343,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -279,15 +351,49 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "default_wip_warehouse", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Default Work In Progress Warehouse", "length": 0, "no_copy": 0, @@ -297,6 +403,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -304,15 +411,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "default_fg_warehouse", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Default Finished Goods Warehouse", "length": 0, "no_copy": 0, @@ -322,6 +434,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -329,18 +442,19 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, - "icon": "fa fa-wrench", + "icon": "icon-wrench", "idx": 0, + "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2015-12-10 00:03:20.895790", + "modified": "2017-07-31 19:25:04.242693", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", @@ -368,8 +482,12 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js new file mode 100644 index 0000000000..2b2589eddd --- /dev/null +++ b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Manufacturing Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially('Manufacturing Settings', [ + // insert a new Manufacturing Settings + () => frappe.tests.make([ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js index c61eec620a..33acb41d4b 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.js +++ b/erpnext/manufacturing/doctype/production_order/production_order.js @@ -91,7 +91,7 @@ frappe.ui.form.on("Production Order", { frm.set_indicator_formatter('operation', function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" }); }, - + refresh: function(frm) { erpnext.toggle_naming_series(); erpnext.production_order.set_custom_buttons(frm); diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index 022e9f3d18..5fedc72889 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -523,7 +523,8 @@ def set_production_order_ops(name): @frappe.whitelist() def make_stock_entry(production_order_id, purpose, qty=None): production_order = frappe.get_doc("Production Order", production_order_id) - if not frappe.db.get_value("Warehouse", production_order.wip_warehouse, "is_group"): + if not frappe.db.get_value("Warehouse", production_order.wip_warehouse, "is_group") \ + and not production_order.skip_transfer: wip_warehouse = production_order.wip_warehouse else: wip_warehouse = None diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.js b/erpnext/manufacturing/doctype/production_order/test_production_order.js index 0a7745e673..a1e910a5de 100644 --- a/erpnext/manufacturing/doctype/production_order/test_production_order.js +++ b/erpnext/manufacturing/doctype/production_order/test_production_order.js @@ -112,11 +112,11 @@ QUnit.test("test: production order", function (assert) { () => click_make(), () => { assert.equal(cur_frm.doc.total_incoming_value, "105700", - "Incoming cost is correct"); // Price of each item x5, values are in USD + "Incoming cost is correct "+cur_frm.doc.total_incoming_value); // Price of each item x5, values are in INR assert.equal(cur_frm.doc.total_outgoing_value, "99000", - "Outgoing cost is correct"); // Price of each item x5, values are in USD + "Outgoing cost is correct"); // Price of each item x5, values are in INR assert.equal(cur_frm.doc.total_incoming_value - cur_frm.doc.total_outgoing_value, cur_frm.doc.value_difference, - "Value difference is correct"); // Price of each item x5, values are in USD + "Value difference is correct"); // Price of each item x5, values are in INR }, () => frappe.click_button("Save"), () => frappe.timeout(1), @@ -135,4 +135,4 @@ QUnit.test("test: production order", function (assert) { () => done() ]); -}); \ No newline at end of file +}); diff --git a/erpnext/manufacturing/doctype/production_order_item/production_order_item.json b/erpnext/manufacturing/doctype/production_order_item/production_order_item.json index 00e3adf13b..c0b1d009ba 100644 --- a/erpnext/manufacturing/doctype/production_order_item/production_order_item.json +++ b/erpnext/manufacturing/doctype/production_order_item/production_order_item.json @@ -229,6 +229,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval:!parent.skip_transfer", "fieldname": "transferred_qty", "fieldtype": "Float", "hidden": 0, @@ -353,7 +354,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-07-10 17:37:20.212361", + "modified": "2017-08-18 18:11:10.311736", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Order Item", diff --git a/erpnext/patches/v5_0/reset_values_in_tools.py b/erpnext/patches/v5_0/reset_values_in_tools.py index 5aac83eef2..fd970ba1b0 100644 --- a/erpnext/patches/v5_0/reset_values_in_tools.py +++ b/erpnext/patches/v5_0/reset_values_in_tools.py @@ -6,7 +6,7 @@ import frappe def execute(): for dt in ["Payment Tool", "Bank Reconciliation", "Payment Reconciliation", "Leave Control Panel", - "Salary Manager", "Upload Attenadance", "Production Planning Tool", "BOM Replace Tool", "Customize Form", - "Employee Attendance Tool", "Rename Tool", "BOM Replace Tool", "Process Payroll", "Naming Series"]: + "Salary Manager", "Upload Attenadance", "Production Planning Tool", "BOM Update Tool", "Customize Form", + "Employee Attendance Tool", "Rename Tool", "BOM Update Tool", "Process Payroll", "Naming Series"]: frappe.db.sql("delete from `tabSingles` where doctype=%s", dt) \ No newline at end of file diff --git a/erpnext/patches/v8_6/rename_bom_update_tool.py b/erpnext/patches/v8_6/rename_bom_update_tool.py new file mode 100644 index 0000000000..45a4ddc788 --- /dev/null +++ b/erpnext/patches/v8_6/rename_bom_update_tool.py @@ -0,0 +1,7 @@ +import frappe +def execute(): + frappe.delete_doc_if_exists("DocType", "BOM Replace Tool") + + frappe.reload_doctype("BOM") + frappe.db.sql("update tabBOM set conversion_rate=1 where conversion_rate is null or conversion_rate=0") + frappe.db.sql("update tabBOM set set_rate_of_sub_assembly_item_based_on_bom=1") \ No newline at end of file diff --git a/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py b/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py index c2320ec04c..db4f94748e 100644 --- a/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py +++ b/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py @@ -8,4 +8,4 @@ def execute(): # Set write permission to permlevel 1 for sales manager role in Quotation doctype frappe.db.sql(""" update `tabCustom DocPerm` set `tabCustom DocPerm`.write = 1 where `tabCustom DocPerm`.parent = 'Quotation' and `tabCustom DocPerm`.role = 'Sales Manager' - and `tabCustom DocPerm`.permlevel = 1 """) + and `tabCustom DocPerm`.permlevel = 1 """) \ No newline at end of file diff --git a/erpnext/public/css/website.css b/erpnext/public/css/website.css index 0245675225..733194a10d 100644 --- a/erpnext/public/css/website.css +++ b/erpnext/public/css/website.css @@ -62,11 +62,6 @@ .featured-products { border-top: 1px solid #EBEFF2; } -.transaction-list-item:hover, -.transaction-list-item:active, -.transaction-list-item:focus { - background-color: #fafbfc; -} .transaction-list-item .indicator { font-weight: inherit; color: #8D99A6; diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index 7de87b7964..d8af0e516d 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -508,8 +508,8 @@ frappe.help.help_links['Form/Production Planning Tool'] = [ { label: 'Production Planning Tool', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/tools/production-planning-tool' }, ] -frappe.help.help_links['Form/BOM Replace Tool'] = [ - { label: 'BOM Replace Tool', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/tools/bom-replace-tool' }, +frappe.help.help_links['Form/BOM Update Tool'] = [ + { label: 'BOM Update Tool', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/tools/bom-update-tool' }, ] //Customize diff --git a/erpnext/public/less/website.less b/erpnext/public/less/website.less index 79a89a050a..25172a1ffb 100644 --- a/erpnext/public/less/website.less +++ b/erpnext/public/less/website.less @@ -76,12 +76,6 @@ } .transaction-list-item { - &:hover, - &:active, - &:focus { - background-color: @light-bg; - } - .indicator { font-weight: inherit; color: @text-muted; diff --git a/erpnext/schools/doctype/student_applicant/tests/test_student_applicant_options.js b/erpnext/schools/doctype/student_applicant/tests/test_student_applicant_options.js index b983afd997..d8877e63e3 100644 --- a/erpnext/schools/doctype/student_applicant/tests/test_student_applicant_options.js +++ b/erpnext/schools/doctype/student_applicant/tests/test_student_applicant_options.js @@ -15,6 +15,7 @@ QUnit.test('test student applicant', function(assert){ () => frappe.timeout(0.5), () => frappe.tests.click_button('Submit'), () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.5), () => { testing_status = $('span.indicator.orange').text(); assert.ok(testing_status.indexOf('Submit this document to confirm') == -1); // checking if submit has been successfull @@ -86,8 +87,9 @@ QUnit.test('test student applicant', function(assert){ // Enrolling the Student into a Program () => {$('.form-documents .row:nth-child(1) .col-xs-6:nth-child(1) .octicon-plus').click();}, () => frappe.timeout(1), + () => cur_frm.set_value('program', 'Standard Test'), + () => frappe.timeout(1), () => { - cur_frm.set_value('program', 'Standard Test'); cur_frm.set_value('student_category', 'Reservation'); cur_frm.set_value('student_batch_name', 'A'); cur_frm.set_value('academic_year', '2016-17'); diff --git a/erpnext/schools/doctype/student_group/test_student_group.js b/erpnext/schools/doctype/student_group/test_student_group.js index df72ae9598..634ad18254 100644 --- a/erpnext/schools/doctype/student_group/test_student_group.js +++ b/erpnext/schools/doctype/student_group/test_student_group.js @@ -57,7 +57,7 @@ QUnit.test('Test: Student Group', function(assert){ () => frappe.set_route("Form", ('Student Group/' + index)), () => frappe.timeout(0.5), () => frappe.tests.click_button('Get Students'), - () => frappe.timeout(0.5), + () => frappe.timeout(1), () => { assert.equal(cur_frm.doc.students.length, 5, 'Successfully fetched list of students'); }, diff --git a/erpnext/schools/doctype/student_group_creation_tool/test_student_group_creation_tool.js b/erpnext/schools/doctype/student_group_creation_tool/test_student_group_creation_tool.js index 366822ee91..a8567b3ba0 100644 --- a/erpnext/schools/doctype/student_group_creation_tool/test_student_group_creation_tool.js +++ b/erpnext/schools/doctype/student_group_creation_tool/test_student_group_creation_tool.js @@ -19,9 +19,10 @@ QUnit.test('Test: Student Group Creation Tool', function(assert){ cur_frm.set_value("program", "Standard Test"); frappe.tests.click_button('Get Courses'); }, - () => frappe.timeout(0.5), + () => frappe.timeout(1), () => { - assert.equal(cur_frm.doc.courses.length, 4, 'Successfully created groups using the tool'); + let no_of_courses = $('input.grid-row-check.pull-left').size() - 1; + assert.equal(cur_frm.doc.courses.length, no_of_courses, 'Successfully created groups using the tool'); }, () => { diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index c149742fe3..3e5e52fe2a 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -38,10 +38,6 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({ var me = this; - if (doc.valid_till && frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) < 0) { - this.frm.set_intro(__("Validity period of this quotation has ended")); - } - if (doc.__islocal) { this.frm.set_value('valid_till', frappe.datetime.add_months(doc.transaction_date, 1)) } diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index ce5fb49c52..f3ebe81056 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -14,6 +14,14 @@ form_grid_templates = { } class Quotation(SellingController): + def set_indicator(self): + if self.docstatus==1: + self.indicator_color = 'blue' + self.indicator_title = 'Submitted' + if self.valid_till and getdate(self.valid_till) < getdate(nowdate()): + self.indicator_color = 'darkgrey' + self.indicator_title = 'Expired' + def validate(self): super(Quotation, self).validate() self.set_status() diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js index 204ace13f4..8baf9b2518 100644 --- a/erpnext/selling/doctype/quotation/quotation_list.js +++ b/erpnext/selling/doctype/quotation/quotation_list.js @@ -1,9 +1,13 @@ frappe.listview_settings['Quotation'] = { add_fields: ["customer_name", "base_grand_total", "status", - "company", "currency"], + "company", "currency", 'valid_till'], get_indicator: function(doc) { if(doc.status==="Submitted") { - return [__("Submitted"), "blue", "status,=,Submitted"]; + if (doc.valid_till && doc.valid_till < frappe.datetime.nowdate()) { + return [__("Expired"), "darkgrey", "valid_till,<," + frappe.datetime.nowdate()]; + } else { + return [__("Submitted"), "blue", "status,=,Submitted"]; + } } else if(doc.status==="Ordered") { return [__("Ordered"), "green", "status,=,Ordered"]; } else if(doc.status==="Lost") { diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index adbc5985f6..a5afbdb450 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -47,7 +47,7 @@ class TestCompany(unittest.TestCase): def test_coa_based_on_country_template(self): countries = ["India", "Brazil", "United Arab Emirates", "Canada", "Germany", "France", "Guatemala", "Indonesia", "Mexico", "Nicaragua", "Netherlands", "Singapore", - "Brazil", "Argentina", "Hungary"] + "Brazil", "Argentina", "Hungary", "Taiwan"] for country in countries: templates = get_charts_for_country(country) diff --git a/erpnext/setup/doctype/company/tests/test_company_production.js b/erpnext/setup/doctype/company/tests/test_company_production.js index 73bd710e23..b73af1dd98 100644 --- a/erpnext/setup/doctype/company/tests/test_company_production.js +++ b/erpnext/setup/doctype/company/tests/test_company_production.js @@ -10,7 +10,7 @@ QUnit.test("Test: Company", function (assert) { () => frappe.timeout(1), () => cur_frm.set_value("company_name", "Razer Blade"), () => cur_frm.set_value("abbr", "RB"), - () => cur_frm.set_value("default_currency", "USD"), + () => cur_frm.set_value("default_currency", "INR"), () => cur_frm.save(), () => frappe.timeout(1), diff --git a/erpnext/setup/setup_wizard/data/test_mfg.json b/erpnext/setup/setup_wizard/data/test_mfg.json index 47acaff75a..b6ea665c76 100644 --- a/erpnext/setup/setup_wizard/data/test_mfg.json +++ b/erpnext/setup/setup_wizard/data/test_mfg.json @@ -1,7 +1,7 @@ { "add_sample_data": 1, "bank_account": "HDFC", - "company_abbr": "GT", + "company_abbr": "FT", "company_name": "For Testing", "company_tagline": "Just for GST", "country": "India", diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.js b/erpnext/stock/doctype/delivery_note/test_delivery_note.js new file mode 100644 index 0000000000..482f892997 --- /dev/null +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.js @@ -0,0 +1,37 @@ +QUnit.module('Stock'); + +QUnit.test("test delivery note", function(assert) { + assert.expect(2); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Delivery Note', [ + {customer:'Test Customer 1'}, + {items: [ + [ + {'item_code': 'Test Product 1'}, + {'qty': 5}, + ] + ]}, + {shipping_address_name: 'Test1-Shipping'}, + {contact_person: 'Contact 1-Test Customer 1'}, + {taxes_and_charges: 'TEST In State GST'}, + {tc_name: 'Test Term 1'}, + {transporter_name:'TEST TRANSPORT'}, + {lr_no:'MH-04-FG 1111'} + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + assert.ok(cur_frm.doc.grand_total==590, " Grand Total correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + + diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.js index 2bc73358b8..0b3fcc9898 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.js @@ -1,23 +1,42 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line +QUnit.module('Stock'); -QUnit.test("test: Purchase Receipt", function (assert) { +QUnit.test("test Purchase Receipt", function(assert) { + assert.expect(4); let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Purchase Receipt', [ - // insert a new Purchase Receipt - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), + frappe.run_serially([ () => { - assert.equal(cur_frm.doc.key, 'value'); + return frappe.tests.make('Purchase Receipt', [ + {supplier: 'Test Supplier'}, + {items: [ + [ + {'received_qty': 5}, + {'qty': 4}, + {'item_code': 'Test Product 1'}, + {'uom': 'Nos'}, + {'warehouse':'Stores - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {'rejected_warehouse':'Work In Progress - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + ] + ]}, + {taxes_and_charges: 'TEST In State GST'}, + {tc_name: 'Test Term 1'}, + {terms: 'This is Test'} + ]); }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + // get tax details + assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST', "Tax details correct"); + // get tax account head details + assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct"); + // grand_total Calculated + assert.ok(cur_frm.doc.grand_total==472, "Grad Total correct"); + + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), () => done() ]); - }); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7c7e630f01..fbadbc54de 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -556,7 +556,7 @@ class StockEntry(StockController): item_dict = self.get_bom_raw_materials(self.fg_completed_qty) for item in item_dict.values(): - if self.pro_doc: + if self.pro_doc and not self.pro_doc.skip_transfer: item["from_warehouse"] = self.pro_doc.wip_warehouse item["to_warehouse"] = self.to_warehouse if self.purpose=="Subcontract" else "" diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js new file mode 100644 index 0000000000..828738eb6c --- /dev/null +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js @@ -0,0 +1,32 @@ +QUnit.module('Stock'); + +QUnit.test("test material request", function(assert) { + assert.expect(2); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Stock Entry', [ + {purpose:'Material Receipt'}, + {to_warehouse:'Stores - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {items: [ + [ + {'item_code': 'Test Product 1'}, + {'qty': 5}, + ] + ]}, + ]); + }, + () => cur_frm.save(), + () => frappe.click_button('Update Rate and Availability'), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + assert.ok(cur_frm.doc.total_incoming_value==500, " Incoming Value correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js new file mode 100644 index 0000000000..cdeb4ab04a --- /dev/null +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js @@ -0,0 +1,34 @@ +QUnit.module('Stock'); + +QUnit.test("test material request", function(assert) { + assert.expect(3); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Stock Entry', [ + {purpose:'Material Transfer'}, + {from_warehouse:'Stores - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {to_warehouse:'Work In Progress - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {items: [ + [ + {'item_code': 'Test Product 1'}, + {'qty': 5}, + ] + ]}, + ]); + }, + () => cur_frm.save(), + () => frappe.click_button('Update Rate and Availability'), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + assert.ok(cur_frm.doc.total_outgoing_value==500, " Outgoing Value correct"); + assert.ok(cur_frm.doc.total_incoming_value==500, " Incoming Value correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js new file mode 100644 index 0000000000..b0eae735ef --- /dev/null +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js @@ -0,0 +1,31 @@ +QUnit.module('Stock'); + +QUnit.test("test Stock Reconciliation", function(assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + () => frappe.set_route('List', 'Stock Reconciliation'), + () => frappe.click_button('New'), + () => cur_frm.set_value('company','Razer Blade'), + () => frappe.click_button('Items'), + () => {cur_dialog.set_value('warehouse','Stores - RB'); }, + () => frappe.timeout(0.5), + () => frappe.click_button('Update'), + () => { + cur_frm.doc.items[0].qty = 150; + cur_frm.refresh_fields('items');}, + () => frappe.timeout(0.5), + () => cur_frm.set_value('expense_account','Stock Adjustment - RB'), + () => cur_frm.set_value('cost_center','Main - RB'), + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.expense_account=='Stock Adjustment - RB', "expense_account correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index ce357db3f8..e95f5cab57 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -37,6 +37,12 @@ frappe.query_reports["Stock Ledger"] = { "fieldtype": "Link", "options": "Item" }, + { + "fieldname":"item_group", + "label": __("Item Group"), + "fieldtype": "Link", + "options": "Item Group" + }, { "fieldname":"batch_no", "label": __("Batch No"), diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 48a169e51c..8377f59979 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -65,7 +65,7 @@ def get_stock_ledger_entries(filters): def get_item_details(filters): item_details = {} for item in frappe.db.sql("""select name, item_name, description, item_group, - brand, stock_uom from `tabItem` {item_conditions}"""\ + brand, stock_uom from `tabItem` item {item_conditions}"""\ .format(item_conditions=get_item_conditions(filters)), filters, as_dict=1): item_details.setdefault(item.name, item) @@ -74,9 +74,11 @@ def get_item_details(filters): def get_item_conditions(filters): conditions = [] if filters.get("item_code"): - conditions.append("name=%(item_code)s") + conditions.append("item.name=%(item_code)s") if filters.get("brand"): - conditions.append("brand=%(brand)s") + conditions.append("item.brand=%(brand)s") + if filters.get("item_group"): + conditions.append(get_item_group_condition(filters.get("item_group"))) return "where {}".format(" and ".join(conditions)) if conditions else "" @@ -84,7 +86,7 @@ def get_sle_conditions(filters): conditions = [] item_conditions=get_item_conditions(filters) if item_conditions: - conditions.append("""item_code in (select name from tabItem + conditions.append("""sle.item_code in (select item.name from tabItem item {item_conditions})""".format(item_conditions=item_conditions)) if filters.get("warehouse"): warehouse_condition = get_warehouse_condition(filters.get("warehouse")) @@ -124,3 +126,12 @@ def get_warehouse_condition(warehouse): warehouse_details.rgt) return '' + +def get_item_group_condition(item_group): + item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1) + if item_group_details: + return "item.item_group in (select ig.name from `tabItem Group` ig \ + where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"%(item_group_details.lft, + item_group_details.rgt) + + return '' diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html index 471576f4f5..462d77db61 100644 --- a/erpnext/templates/includes/order/order_taxes.html +++ b/erpnext/templates/includes/order/order_taxes.html @@ -1,22 +1,22 @@ {% if doc.taxes %}
-
{{ _("Net Total") }}
-
+
{{ _("Net Total") }}
+
{{ doc.get_formatted("net_total") }}
{% endif %} {% for d in doc.taxes %} {% if d.base_tax_amount > 0 %}
-
{{ d.description }}
-
+
{{ d.description }}
+
{{ d.get_formatted("base_tax_amount") }}
{% endif %} {% endfor %}
-
{{ _("Grand Total") }}
-
+
{{ _("Grand Total") }}
+
{{ doc.get_formatted("grand_total") }} diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html index 10d76057d2..0e47f7d399 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -1,23 +1,22 @@
- diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 2481808b4f..8a495b19ef 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -7,21 +7,44 @@ {% block title %}{{ doc.name }}{% endblock %} -{% block header %}

{{ doc.name }}

{% endblock %} +{% block header %} +

{{ doc.name }}

+{% endblock %} + +{% block header_actions %} +{{ _("Print") }} +{% endblock %} {% block page_content %}
- + {{ doc.indicator_title or doc.status or "Submitted" }}
{{ frappe.utils.formatdate(doc.transaction_date, 'medium') }} + {% if doc.valid_till %} +

+ {{ _("Valid Till") }}: {{ frappe.utils.formatdate(doc.valid_till, 'medium') }} +

+ {% endif %}
+

+{% if doc.doctype == 'Supplier Quotation' %} + {{ doc.supplier_name}} +{% else %} + {{ doc.customer_name}} +{% endif %} +{% if doc.contact_display %} +
+ {{ doc.contact_display }} +{% endif %} +

+ {% if doc._header %} {{ doc._header }} {% endif %} @@ -31,29 +54,29 @@
-
+
{{ _("Item") }}
-
+
{{ _("Quantity") }}
-
+
{{ _("Amount") }}
{% for d in doc.items %}
-
+
{{ item_name_and_description(d) }}
-
+
{{ d.qty }} {% if d.delivered_qty is defined and d.delivered_qty != None %}

{{ _("Delivered: {0}").format(d.delivered_qty) }}

{% endif %}
-
+
{{ d.get_formatted("amount") }}

{{ _("@ {0}").format(d.get_formatted("rate")) }}

@@ -72,8 +95,8 @@
-
-
+
+
{% if enabled_checkout %} {% if (doc.doctype=="Sales Order" and doc.per_billed <= 0) or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0) %} @@ -86,7 +109,7 @@ {% endif %} {% endif %}
- + {% if attachments %}
@@ -106,4 +129,9 @@
{% endif %}
+{% if doc.terms %} +
+

{{ doc.terms }}

+
+{% endif %} {% endblock %} diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py index 7551a0f9bb..110eb57504 100644 --- a/erpnext/templates/pages/order.py +++ b/erpnext/templates/pages/order.py @@ -15,7 +15,7 @@ def get_context(context): context.doc.set_indicator() if show_attachments(): - context.attachments = get_attachments(frappe.form_dict.doctype, frappe.form_dict.name) + context.attachments = get_attachments(frappe.form_dict.doctype, frappe.form_dict.name) context.parents = frappe.form_dict.parents context.title = frappe.form_dict.name @@ -28,5 +28,6 @@ def get_context(context): frappe.throw(_("Not Permitted"), frappe.PermissionError) def get_attachments(dt, dn): - return frappe.get_all("File", fields=["name", "file_name", "file_url", "is_private"], - filters = {"attached_to_name": dn, "attached_to_doctype": dt, "is_private":0}) + return frappe.get_all("File", + fields=["name", "file_name", "file_url", "is_private"], + filters = {"attached_to_name": dn, "attached_to_doctype": dt, "is_private":0}) diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 5634935793..cb33c90c1f 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -4,6 +4,7 @@ erpnext/accounts/doctype/account/test_account.js erpnext/accounts/doctype/account/test_make_tax_account.js erpnext/accounts/doctype/pricing_rule/test_pricing_rule.js erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.js +erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js erpnext/accounts/doctype/shipping_rule/test_shipping_rule.js erpnext/crm/doctype/lead/test_lead.js erpnext/crm/doctype/opportunity/test_opportunity.js @@ -51,10 +52,36 @@ erpnext/stock/doctype/warehouse/test_warehouse.js erpnext/manufacturing/doctype/production_order/test_production_order.js #long erpnext/accounts/page/pos/test_pos.js erpnext/selling/doctype/product_bundle/test_product_bundle.js +erpnext/stock/doctype/delivery_note/test_delivery_note.js erpnext/stock/doctype/material_request/tests/test_material_request.js erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js +erpnext/hr/doctype/salary_structure/test_salary_structure.js +erpnext/hr/doctype/salary_slip/test_salary_slip.js +erpnext/hr/doctype/process_payroll/test_process_payroll.js +erpnext/hr/doctype/job_opening/test_job_opening.js +erpnext/hr/doctype/job_applicant/test_job_applicant.js +erpnext/hr/doctype/offer_letter/test_offer_letter.js +erpnext/buying/doctype/supplier/test_supplier.js +erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js +erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js +erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js +erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice.js +erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment.js +erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js +erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js +erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js +erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js +erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js +erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js +erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js +erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js +erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js +erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.js erpnext/schools/doctype/grading_scale/test_grading_scale.js erpnext/schools/doctype/assessment_criteria_group/test_assessment_criteria_group.js erpnext/schools/doctype/assessment_criteria/test_assessment_criteria.js @@ -71,9 +98,8 @@ erpnext/schools/doctype/student_group_creation_tool/test_student_group_creation_ erpnext/schools/doctype/student_leave_application/test_student_leave_application.js erpnext/schools/doctype/student_attendance_tool/test_student_attendance_tool.js erpnext/schools/doctype/student_attendance/test_student_attendance.js -erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js erpnext/schools/doctype/assessment_group/test_assessment_group.js erpnext/schools/doctype/assessment_plan/test_assessment_plan.js erpnext/schools/doctype/assessment_result/test_assessment_result.js erpnext/schools/doctype/assessment_result_tool/test_assessment_result_tool.js -erpnext/buying/doctype/supplier/test_supplier.js +erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js