diff --git a/.travis.yml b/.travis.yml index f7d28be009..a70062fea3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,4 +55,7 @@ script: - set -e - bench run-tests - sleep 5 + - 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 fb7c761e4f..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.8.3' +__version__ = '8.10.0' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py b/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py index cb95bd17ae..9e3388b37d 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py @@ -4,7 +4,7 @@ """ Import chart of accounts from OpenERP sources """ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import os, json import ast @@ -229,7 +229,7 @@ def make_charts(): filename = src["id"][5:] + "_" + chart_id - print "building " + filename + print("building " + filename) chart = {} chart["name"] = src["name"] chart["country_code"] = src["id"][5:] 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/bank_reconciliation/test_bank_reconciliation.js b/erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js new file mode 100644 index 0000000000..f52f6fb431 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js @@ -0,0 +1,22 @@ +QUnit.module('Account'); + +QUnit.test("test Bank Reconciliation", function(assert) { + assert.expect(0); + let done = assert.async(); + frappe.run_serially([ + () => frappe.set_route('Form', 'Bank Reconciliation'), + () => cur_frm.set_value('bank_account','Cash - FT'), + () => frappe.click_button('Get Payment Entries'), + () => { + for(var i=0;i<=cur_frm.doc.payment_entries.length-1;i++){ + cur_frm.doc.payment_entries[i].clearance_date = frappe.datetime.add_days(frappe.datetime.now_date(), 2); + } + }, + () => {cur_frm.refresh_fields('payment_entries');}, + () => frappe.click_button('Update Clearance Date'), + () => frappe.timeout(0.5), + () => frappe.click_button('Close'), + () => done() + ]); +}); + diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.js b/erpnext/accounts/doctype/journal_entry/test_journal_entry.js new file mode 100644 index 0000000000..28ccd95592 --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.js @@ -0,0 +1,39 @@ +QUnit.module('Journal Entry'); + +QUnit.test("test journal entry", function(assert) { + assert.expect(2); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Journal Entry', [ + {posting_date:frappe.datetime.add_days(frappe.datetime.nowdate(), 0)}, + {accounts: [ + [ + {'account':'Debtors - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {'party_type':'Customer'}, + {'party':'Test Customer 1'}, + {'credit_in_account_currency':1000}, + {'is_advance':'Yes'}, + ], + [ + {'account':'HDFC - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {'debit_in_account_currency':1000}, + ] + ]}, + {cheque_no:1234}, + {cheque_date: frappe.datetime.add_days(frappe.datetime.nowdate(), -1)}, + {user_remark: 'Test'}, + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.total_debit==1000, "total debit correct"); + assert.ok(cur_frm.doc.total_credit==1000, "total credit correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 05986a0808..12e46c42d3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -499,6 +499,7 @@ frappe.ui.form.on('Payment Entry', { var c = frm.add_child("references"); c.reference_doctype = d.voucher_type; c.reference_name = d.voucher_no; + c.due_date = d.due_date c.total_amount = d.invoice_amount; c.outstanding_amount = d.outstanding_amount; if(!in_list(["Sales Order", "Purchase Order", "Expense Claim"], d.voucher_type)) { @@ -568,7 +569,7 @@ frappe.ui.form.on('Payment Entry', { }) var allocated_negative_outstanding = 0; - if((frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || + if ((frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee")) { if(total_positive_outstanding_including_order > paid_amount) { @@ -578,7 +579,7 @@ frappe.ui.form.on('Payment Entry', { } var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding; - } else { + } else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) { if(paid_amount > total_negative_outstanding) { if(total_negative_outstanding == 0) { frappe.msgprint(__("Cannot {0} {1} {2} without any negative outstanding invoice", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7bb9a52ee6..9832c0527a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -393,7 +393,7 @@ class PaymentEntry(AccountsController): if self.payment_type=="Receive": against_account = self.paid_to else: - against_account = self.paid_from + against_account = self.paid_from party_gl_dict = self.get_gl_dict({ diff --git a/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js new file mode 100644 index 0000000000..a4ef0ca4eb --- /dev/null +++ b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js @@ -0,0 +1,29 @@ +QUnit.module('Accounts'); + +QUnit.test("test payment entry", function(assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Payment Entry', [ + {payment_type:'Receive'}, + {mode_of_payment:'Cash'}, + {party_type:'Customer'}, + {party:'Test Customer 3'}, + {paid_amount:675}, + {reference_no:123}, + {reference_date: frappe.datetime.add_days(frappe.datetime.nowdate(), 0)}, + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.total_allocated_amount==675, "Allocated AmountCorrect"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 57f75b46c1..f36fdf7907 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -151,13 +151,8 @@ def get_items_list(pos_profile): def get_item_groups(pos_profile): item_group_dict = {} - if pos_profile.get('item_groups'): - item_groups = [] - for d in pos_profile.get('item_groups'): - item_groups.extend(get_child_nodes('Item Group', d.item_group)) - else: - item_groups = frappe.db.sql("""Select name, - lft, rgt from `tabItem Group` order by lft""", as_dict=1) + item_groups = frappe.db.sql("""Select name, + lft, rgt from `tabItem Group` order by lft""", as_dict=1) for data in item_groups: item_group_dict[data.name] = [data.lft, data.rgt] diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index c38645067c..ef233c6799 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -325,23 +325,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte } this.frm.refresh_fields(); - }, - - company_address: function() { - var me = this; - if(this.frm.doc.company_address) { - frappe.call({ - method: "frappe.contacts.doctype.address.address.get_address_display", - args: {"address_dict": this.frm.doc.company_address }, - callback: function(r) { - if(r.message) { - me.frm.set_value("company_address_display", r.message) - } - } - }) - } else { - this.frm.set_value("company_address_display", ""); - } } }); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0b6926f287..a1d66ad8b7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -19,6 +19,7 @@ from erpnext.accounts.doctype.asset.depreciation \ import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no +from erpnext.setup.doctype.company.company import update_company_current_month_sales form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -108,7 +109,7 @@ class SalesInvoice(SellingController): if not self.recurring_id: frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, - self.company, self.base_grand_total, self) + self.company, self.base_grand_total, self) self.check_prev_docstatus() @@ -140,7 +141,7 @@ class SalesInvoice(SellingController): self.update_time_sheet(self.name) - frappe.enqueue('erpnext.setup.doctype.company.company.update_company_current_month_sales', company=self.company) + self.update_current_month_sales() def validate_pos_paid_amount(self): if len(self.payments) == 0 and self.is_pos: @@ -178,6 +179,15 @@ class SalesInvoice(SellingController): self.make_gl_entries_on_cancel() frappe.db.set(self, 'status', 'Cancelled') + self.update_current_month_sales() + + def update_current_month_sales(self): + if frappe.flags.in_test: + update_company_current_month_sales(self.company) + else: + frappe.enqueue('erpnext.setup.doctype.company.company.update_company_current_month_sales', + company=self.company) + def update_status_updater_args(self): if cint(self.update_stock): self.status_updater.extend([{ diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 806b489da4..e0a453c5c9 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1272,6 +1272,17 @@ class TestSalesInvoice(unittest.TestCase): si.insert() return si + def test_company_monthly_sales(self): + existing_current_month_sales = frappe.db.get_value("Company", "_Test Company", "total_monthly_sales") + + si = create_sales_invoice() + current_month_sales = frappe.db.get_value("Company", "_Test Company", "total_monthly_sales") + self.assertEqual(current_month_sales, existing_current_month_sales + si.base_grand_total) + + si.cancel() + current_month_sales = frappe.db.get_value("Company", "_Test Company", "total_monthly_sales") + self.assertEqual(current_month_sales, existing_current_month_sales) + def create_sales_invoice(**args): si = frappe.new_doc("Sales Invoice") args = frappe._dict(args) 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/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js new file mode 100644 index 0000000000..7abfb415ca --- /dev/null +++ b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js @@ -0,0 +1,52 @@ +QUnit.module('Sales Invoice'); + +QUnit.test("test sales Invoice with payment request", 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 Request'), + () => frappe.timeout(0.2), + () => { cur_frm.set_value('print_format','GST Tax Invoice');}, + () => { cur_frm.set_value('email_to','test@gmail.com');}, + () => cur_frm.save(), + () => { + // get payment details + assert.ok(cur_frm.doc.grand_total==590, "grand total Correct"); + }, + () => done() + ]); +}); + diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index 7faaf11cef..a47df2d862 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -54,13 +54,15 @@ class ShippingRule(Document): d.idx = i + 1 def validate_overlapping_shipping_rule_conditions(self): - def overlap_exists_between((x1, x2), (y1, y2)): + def overlap_exists_between(num_range1, num_range2): """ - (x1, x2) and (y1, y2) are two ranges - if condition x = 100 to 300 - then condition y can only be like 50 to 99 or 301 to 400 + num_range1 and num_range2 are two ranges + ranges are represented as a tuple e.g. range 100 to 300 is represented as (100, 300) + if condition num_range1 = 100 to 300 + then condition num_range2 can only be like 50 to 99 or 301 to 400 hence, non-overlapping condition = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2) """ + (x1, x2), (y1, y2) = num_range1, num_range2 separate = (x1 <= x2 <= y1 <= y2) or (y1 <= y2 <= x1 <= x2) return (not separate) diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py index 36f4221ce1..7324532a39 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import cstr, cint from frappe.contacts.doctype.address.address import get_default_address +from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups class IncorrectCustomerGroup(frappe.ValidationError): pass class IncorrectSupplierType(frappe.ValidationError): pass @@ -134,6 +135,9 @@ def get_tax_template(posting_date, args): for key, value in args.iteritems(): if key=="use_for_shopping_cart": conditions.append("use_for_shopping_cart = {0}".format(1 if value else 0)) + if key == 'customer_group' and value: + customer_group_condition = get_customer_group_condition(value) + conditions.append("ifnull({0}, '') in ('', {1})".format(key, customer_group_condition)) else: conditions.append("ifnull({0}, '') in ('', '{1}')".format(key, frappe.db.escape(cstr(value)))) @@ -157,3 +161,10 @@ def get_tax_template(posting_date, args): return None return tax_template + +def get_customer_group_condition(customer_group): + condition = "" + customer_groups = ["'%s'"%(frappe.db.escape(d.name)) for d in get_parent_customer_groups(customer_group)] + if customer_groups: + condition = ",".join(['%s'] * len(customer_groups))%(tuple(customer_groups)) + return condition \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py index a09d81b5c6..383b02b128 100644 --- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py @@ -34,6 +34,14 @@ class TestTaxRule(unittest.TestCase): tax_rule2.save() self.assertTrue(tax_rule2.name) + def test_for_parent_customer_group(self): + tax_rule1 = make_tax_rule(customer_group= "All Customer Groups", + sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1, from_date = "2015-01-01") + tax_rule1.save() + + self.assertEquals(get_tax_template("2015-01-01", {"customer_group" : "Commercial"}), + "_Test Sales Taxes and Charges Template") + def test_conflict_with_overlapping_dates(self): tax_rule1 = make_tax_rule(customer= "_Test Customer", sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1, from_date = "2015-01-01", to_date = "2015-01-05") 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/party.py b/erpnext/accounts/party.py index 5cb04c5eb6..ba7ae323d4 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -79,7 +79,7 @@ def set_address_details(out, party, party_type, doctype=None, company=None): out.shipping_address = get_address_display(out["shipping_address_name"]) out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name)) - if doctype and doctype in ['Sales Invoice']: + if doctype and doctype in ['Delivery Note', 'Sales Invoice']: out.update(get_company_address(company)) if out.company_address: out.update(get_fetch_values(doctype, 'company_address', out.company_address)) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index 853b805135..8fafce6781 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -10,15 +10,15 @@ {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} - {%= __("Date") %} - {%= __("Ref") %} - {%= __("Party") %} - {%= __("Invoiced Amount") %} - {%= __("Paid Amount") %} - {%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %} - {%= __("Outstanding Amount") %} + {%= __("Date") %} + {%= __("Ref") %} + {%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %} + {%= __("Invoiced Amount") %} + {%= __("Paid Amount") %} + {%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %} + {%= __("Outstanding Amount") %} {% } else { %} - {%= __("Party") %} + {%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %} {%= __("Total Invoiced Amount") %} {%= __("Total Paid Amount") %} {%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %} @@ -34,8 +34,12 @@ {%= dateutil.str_to_user(data[i][__("Posting Date")]) %} {%= data[i][__("Voucher Type")] %}
{%= data[i][__("Voucher No")] %} - {%= data[i][__("Customer Name")] || data[i][__("Customer")] || data[i][__("Supplier Name")] || data[i][__("Supplier")] %} -
{%= __("Remarks") %}: {%= data[i][__("Remarks")] %} + + {% if(!(filters.customer || filters.supplier)) { %} + {%= data[i][__("Customer Name")] || data[i][__("Customer")] || data[i][__("Supplier Name")] || data[i][__("Supplier")] %}
{%= __("Remarks") %}: + {% } %} + {%= data[i][__("Remarks")] %} + {%= format_currency(data[i]["Invoiced Amount"], data[i]["currency"]) %} @@ -59,8 +63,13 @@ {% } else { %} {% if(data[i][__("Customer")] || data[i][__("Supplier")]|| " ") { %} {% if((data[i][__("Customer")] || data[i][__("Supplier")]) != __("'Total'")) { %} - {%= data[i][__("Customer")] || data[i][__("Supplier")] %} -
{%= __("Remarks") %}: {%= data[i][__("Remarks")] %} + + {% if(!(filters.customer || filters.supplier)) { %} + {%= data[i][__("Customer")] || data[i][__("Supplier")] %} +
{%= __("Remarks") %}: + {% } %} + {%= data[i][__("Remarks")] %} + {% } else { %} {%= __("Total") %} {% } %} 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_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 0d1eba1ad3..fa458df472 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -2,9 +2,10 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import flt +from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import get_tax_accounts def execute(filters=None): return _execute(filters) @@ -12,12 +13,14 @@ def execute(filters=None): def _execute(filters=None, additional_table_columns=None, additional_query_columns=None): if not filters: filters = {} columns = get_columns(additional_table_columns) - last_col = len(columns) + + company_currency = erpnext.get_company_currency(filters.company) item_list = get_items(filters, additional_query_columns) aii_account_map = get_aii_accounts() if item_list: - item_row_tax, tax_accounts = get_tax_accounts(item_list, columns) + itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency, + doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges") columns.append({ "fieldname": "currency", @@ -25,7 +28,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum "fieldtype": "Data", "width": 80 }) - company_currency = frappe.db.get_value("Company", filters.company, "default_currency") + + po_pr_map = get_purchase_receipts_against_purchase_order(item_list) data = [] for d in item_list: @@ -33,8 +37,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum if d.purchase_receipt: purchase_receipt = d.purchase_receipt elif d.po_detail: - purchase_receipt = ", ".join(frappe.db.sql_list("""select distinct parent - from `tabPurchase Receipt Item` where docstatus=1 and purchase_order_item=%s""", d.po_detail)) + purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, [])) expense_account = d.expense_account or aii_account_map.get(d.company) row = [d.item_code, d.item_name, d.item_group, d.parent, d.posting_date, d.supplier, @@ -46,13 +49,15 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row += [ d.credit_to, d.mode_of_payment, d.project, d.company, d.purchase_order, - purchase_receipt, expense_account, d.qty, d.base_net_rate, d.base_net_amount + purchase_receipt, expense_account, d.qty, d.stock_uom, d.base_net_rate, d.base_net_amount ] - for tax in tax_accounts: - row.append(item_row_tax.get(d.name, {}).get(tax, 0)) + total_tax = 0 + for tax in tax_columns: + item_tax = itemised_tax.get(d.name, {}).get(tax, {}) + row += [item_tax.get("tax_rate", 0), item_tax.get("tax_amount", 0)] + total_tax += flt(item_tax.get("tax_amount")) - total_tax = sum(row[last_col:]) row += [total_tax, d.base_net_amount + total_tax, company_currency] data.append(row) @@ -76,7 +81,8 @@ def get_columns(additional_table_columns): _("Mode of Payment") + ":Link/Mode of Payment:80", _("Project") + ":Link/Project:80", _("Company") + ":Link/Company:100", _("Purchase Order") + ":Link/Purchase Order:100", _("Purchase Receipt") + ":Link/Purchase Receipt:100", _("Expense Account") + ":Link/Account:140", - _("Qty") + ":Float:120", _("Rate") + ":Currency/currency:120", _("Amount") + ":Currency/currency:120" + _("Qty") + ":Float:120", _("Stock UOM") + "::100", + _("Rate") + ":Currency/currency:120", _("Amount") + ":Currency/currency:120" ] return columns @@ -106,8 +112,9 @@ def get_items(filters, additional_query_columns): pi_item.name, pi_item.parent, pi.posting_date, pi.credit_to, pi.company, pi.supplier, pi.remarks, pi.base_net_total, pi_item.item_code, pi_item.item_name, pi_item.item_group, pi_item.project, pi_item.purchase_order, pi_item.purchase_receipt, - pi_item.po_detail, pi_item.expense_account, pi_item.qty, pi_item.base_net_rate, - pi_item.base_net_amount, pi.supplier_name, pi.mode_of_payment {0} + pi_item.po_detail, pi_item.expense_account, pi_item.qty, pi_item.stock_uom, + pi_item.base_net_rate, pi_item.base_net_amount, + pi.supplier_name, pi.mode_of_payment {0} from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item where pi.name = pi_item.parent and pi.docstatus = 1 %s %s order by pi.posting_date desc, pi_item.item_code desc @@ -116,53 +123,19 @@ def get_items(filters, additional_query_columns): def get_aii_accounts(): return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany")) -def get_tax_accounts(item_list, columns): - import json - item_row_tax = {} - tax_accounts = [] - invoice_item_row = {} - item_row_map = {} - for d in item_list: - invoice_item_row.setdefault(d.parent, []).append(d) - item_row_map.setdefault(d.parent, {}).setdefault(d.item_code, []).append(d) +def get_purchase_receipts_against_purchase_order(item_list): + po_pr_map = frappe._dict() + po_item_rows = list(set([d.po_detail for d in item_list])) - tax_details = frappe.db.sql(""" - select - parent, account_head, item_wise_tax_detail, charge_type, base_tax_amount_after_discount_amount - from `tabPurchase Taxes and Charges` - where parenttype = 'Purchase Invoice' and docstatus = 1 - and (account_head is not null and account_head != '') - and category in ('Total', 'Valuation and Total') - and parent in (%s) - """ % ', '.join(['%s']*len(invoice_item_row)), tuple(invoice_item_row.keys())) + if po_item_rows: + purchase_receipts = frappe.db.sql(""" + select parent, purchase_order_item + from `tabPurchase Receipt Item` + where docstatus=1 and purchase_order_item in (%s) + group by purchase_order_item, parent + """ % (', '.join(['%s']*len(po_item_rows))), tuple(po_item_rows), as_dict=1) - for parent, account_head, item_wise_tax_detail, charge_type, tax_amount in tax_details: - if account_head not in tax_accounts: - tax_accounts.append(account_head) + for pr in purchase_receipts: + po_pr_map.setdefault(pr.po_detail, []).append(pr.parent) - if item_wise_tax_detail: - try: - item_wise_tax_detail = json.loads(item_wise_tax_detail) - - for item_code, tax_amount in item_wise_tax_detail.items(): - tax_amount = flt(tax_amount[1]) if isinstance(tax_amount, list) else flt(tax_amount) - - item_net_amount = sum([flt(d.base_net_amount) - for d in item_row_map.get(parent, {}).get(item_code, [])]) - - for d in item_row_map.get(parent, {}).get(item_code, []): - item_tax_amount = flt((tax_amount * d.base_net_amount) / item_net_amount) if item_net_amount else 0 - item_row_tax.setdefault(d.name, {})[account_head] = item_tax_amount - - except ValueError: - continue - elif charge_type == "Actual" and tax_amount: - for d in invoice_item_row.get(parent, []): - item_row_tax.setdefault(d.name, {})[account_head] = \ - flt((tax_amount * d.base_net_amount) / d.base_net_total) - - tax_accounts.sort() - columns += [account_head + ":Currency/currency:80" for account_head in tax_accounts] - columns += ["Total Tax:Currency/currency:80", "Total:Currency/currency:80"] - - return item_row_tax, tax_accounts + return po_pr_map \ No newline at end of file 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/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 7d10b19dce..0fc58316ef 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -2,9 +2,10 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import flt +from frappe.model.meta import get_field_precision from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments def execute(filters=None): @@ -13,19 +14,20 @@ def execute(filters=None): def _execute(filters=None, additional_table_columns=None, additional_query_columns=None): if not filters: filters = {} columns = get_columns(additional_table_columns) - last_col = len(columns) + + company_currency = erpnext.get_company_currency(filters.company) item_list = get_items(filters, additional_query_columns) if item_list: - item_row_tax, tax_accounts = get_tax_accounts(item_list, columns) + itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) columns.append({ "fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80 }) - company_currency = frappe.db.get_value("Company", filters.get("company"), "default_currency") mode_of_payments = get_mode_of_payments(set([d.parent for d in item_list])) + so_dn_map = get_delivery_notes_against_sales_order(item_list) data = [] for d in item_list: @@ -33,8 +35,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum if d.delivery_note: delivery_note = d.delivery_note elif d.so_detail: - delivery_note = ", ".join(frappe.db.sql_list("""select distinct parent - from `tabDelivery Note Item` where docstatus=1 and so_detail=%s""", d.so_detail)) + delivery_note = ", ".join(so_dn_map.get(d.so_detail, [])) + if not delivery_note and d.update_stock: delivery_note = d.parent @@ -47,13 +49,16 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row += [ d.customer_group, d.debit_to, ", ".join(mode_of_payments.get(d.parent, [])), d.territory, d.project, d.company, d.sales_order, - delivery_note, d.income_account, d.cost_center, d.qty, d.base_net_rate, d.base_net_amount + delivery_note, d.income_account, d.cost_center, d.qty, d.stock_uom, + d.base_net_rate, d.base_net_amount ] - for tax in tax_accounts: - row.append(item_row_tax.get(d.name, {}).get(tax, 0)) + total_tax = 0 + for tax in tax_columns: + item_tax = itemised_tax.get(d.name, {}).get(tax, {}) + row += [item_tax.get("tax_rate", 0), item_tax.get("tax_amount", 0)] + total_tax += flt(item_tax.get("tax_amount")) - total_tax = sum(row[last_col:]) row += [total_tax, d.base_net_amount + total_tax, company_currency] data.append(row) @@ -77,7 +82,7 @@ def get_columns(additional_table_columns): _("Project") + ":Link/Project:80", _("Company") + ":Link/Company:100", _("Sales Order") + ":Link/Sales Order:100", _("Delivery Note") + ":Link/Delivery Note:100", _("Income Account") + ":Link/Account:140", _("Cost Center") + ":Link/Cost Center:140", - _("Qty") + ":Float:120", + _("Qty") + ":Float:120", _("Stock UOM") + "::100", _("Rate") + ":Currency/currency:120", _("Amount") + ":Currency/currency:120" ] @@ -112,62 +117,111 @@ def get_items(filters, additional_query_columns): si_item.name, si_item.parent, si.posting_date, si.debit_to, si.project, si.customer, si.remarks, si.territory, si.company, si.base_net_total, si_item.item_code, si_item.item_name, si_item.item_group, si_item.sales_order, - si_item.delivery_note, si_item.income_account, si_item.cost_center, si_item.qty, - si_item.base_net_rate, si_item.base_net_amount, si.customer_name, - si.customer_group, si_item.so_detail, si.update_stock {0} + si_item.delivery_note, si_item.income_account, si_item.cost_center, + si_item.qty, si_item.stock_uom, si_item.base_net_rate, si_item.base_net_amount, + si.customer_name, si.customer_group, si_item.so_detail, si.update_stock {0} from `tabSales Invoice` si, `tabSales Invoice Item` si_item where si.name = si_item.parent and si.docstatus = 1 %s order by si.posting_date desc, si_item.item_code desc """.format(additional_query_columns or '') % conditions, filters, as_dict=1) -def get_tax_accounts(item_list, columns): +def get_delivery_notes_against_sales_order(item_list): + so_dn_map = frappe._dict() + so_item_rows = list(set([d.so_detail for d in item_list])) + + if so_item_rows: + delivery_notes = frappe.db.sql(""" + select parent, so_detail + from `tabDelivery Note Item` + where docstatus=1 and so_detail in (%s) + group by so_detail, parent + """ % (', '.join(['%s']*len(so_item_rows))), tuple(so_item_rows), as_dict=1) + + for dn in delivery_notes: + so_dn_map.setdefault(dn.so_detail, []).append(dn.parent) + + return so_dn_map + +def get_tax_accounts(item_list, columns, company_currency, + doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): import json - item_row_tax = {} - tax_accounts = [] - invoice_item_row = {} item_row_map = {} + tax_columns = [] + invoice_item_row = {} + itemised_tax = {} + + tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"), + currency=company_currency) or 2 + for d in item_list: invoice_item_row.setdefault(d.parent, []).append(d) item_row_map.setdefault(d.parent, {}).setdefault(d.item_code, []).append(d) + conditions = "" + if doctype == "Purchase Invoice": + conditions = " and category in ('Total', 'Valuation and Total')" + tax_details = frappe.db.sql(""" select - parent, account_head, item_wise_tax_detail, + parent, description, item_wise_tax_detail, charge_type, base_tax_amount_after_discount_amount - from `tabSales Taxes and Charges` + from `tab%s` where - parenttype = 'Sales Invoice' and docstatus = 1 - and (account_head is not null and account_head != '') + parenttype = %s and docstatus = 1 + and (description is not null and description != '') and parent in (%s) - """ % ', '.join(['%s']*len(invoice_item_row)), tuple(invoice_item_row.keys())) + %s + order by description + """ % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row)), conditions), + tuple([doctype] + invoice_item_row.keys())) - for parent, account_head, item_wise_tax_detail, charge_type, tax_amount in tax_details: - if account_head not in tax_accounts: - tax_accounts.append(account_head) + for parent, description, item_wise_tax_detail, charge_type, tax_amount in tax_details: + if description not in tax_columns and tax_amount: + tax_columns.append(description) if item_wise_tax_detail: try: item_wise_tax_detail = json.loads(item_wise_tax_detail) - for item_code, tax_amount in item_wise_tax_detail.items(): - tax_amount = flt(tax_amount[1]) if isinstance(tax_amount, list) else flt(tax_amount) + for item_code, tax_data in item_wise_tax_detail.items(): + itemised_tax.setdefault(item_code, frappe._dict()) + + if isinstance(tax_data, list): + tax_rate, tax_amount = tax_data + else: + tax_rate = tax_data + tax_amount = 0 + + if charge_type == "Actual" and not tax_rate: + tax_rate = "NA" item_net_amount = sum([flt(d.base_net_amount) for d in item_row_map.get(parent, {}).get(item_code, [])]) for d in item_row_map.get(parent, {}).get(item_code, []): - item_tax_amount = flt((tax_amount * d.base_net_amount) / item_net_amount) if item_net_amount else 0 - item_row_tax.setdefault(d.name, {})[account_head] = item_tax_amount + item_tax_amount = flt((tax_amount * d.base_net_amount) / item_net_amount) \ + if item_net_amount else 0 + if item_tax_amount: + itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ + "tax_rate": tax_rate, + "tax_amount": flt(item_tax_amount, tax_amount_precision) + }) except ValueError: continue elif charge_type == "Actual" and tax_amount: for d in invoice_item_row.get(parent, []): - item_row_tax.setdefault(d.name, {})[account_head] = \ - flt((tax_amount * d.base_net_amount) / d.base_net_total) + itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ + "tax_rate": "NA", + "tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total, + tax_amount_precision) + }) - tax_accounts.sort() - columns += [account_head + ":Currency/currency:80" for account_head in tax_accounts] - columns += ["Total Tax:Currency/currency:80", "Total:Currency/currency:80"] + tax_columns.sort() + for desc in tax_columns: + columns.append(desc + " Rate:Data:80") + columns.append(desc + " Amount:Currency/currency:100") - return item_row_tax, tax_accounts + columns += ["Total Tax:Currency/currency:80", "Total:Currency/currency:100"] + + return itemised_tax, tax_columns 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/accounts/utils.py b/erpnext/accounts/utils.py index 6617802235..50530f5dc1 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -580,9 +580,15 @@ def get_outstanding_invoices(party_type, party, account, condition=None): dr_or_cr = "credit_in_account_currency - debit_in_account_currency" payment_dr_or_cr = "payment_gl_entry.debit_in_account_currency - payment_gl_entry.credit_in_account_currency" + invoice = 'Sales Invoice' if party_type == 'Customer' else 'Purchase Invoice' invoice_list = frappe.db.sql(""" select voucher_no, voucher_type, posting_date, ifnull(sum({dr_or_cr}), 0) as invoice_amount, + ( + case when (voucher_type = 'Sales Invoice' or voucher_type = 'Purchase Invoice') + then (select due_date from `tab{invoice}` where name = voucher_no) + else posting_date end + ) as due_date, ( select ifnull(sum({payment_dr_or_cr}), 0) from `tabGL Entry` payment_gl_entry @@ -606,6 +612,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None): having (invoice_amount - payment_amount) > 0.005 order by posting_date, name""".format( dr_or_cr = dr_or_cr, + invoice = invoice, payment_dr_or_cr = payment_dr_or_cr, condition = condition or "" ), { @@ -618,6 +625,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None): outstanding_invoices.append(frappe._dict({ 'voucher_no': d.voucher_no, 'voucher_type': d.voucher_type, + 'due_date': d.due_date, 'posting_date': d.posting_date, 'invoice_amount': flt(d.invoice_amount), 'payment_amount': flt(d.payment_amount), diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js index 940f36f1fd..e6529e60db 100644 --- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js @@ -8,10 +8,8 @@ QUnit.test("test: purchase order", function(assert) { () => { return frappe.tests.make('Purchase Order', [ {supplier: 'Test Supplier'}, - {company: 'Wind Power LLC'}, {is_subcontracted: 'No'}, - {buying_price_list: 'Test-Buying-USD'}, - {currency: 'USD'}, + {currency: 'INR'}, {items: [ [ {"item_code": 'Test Product 4'}, @@ -20,7 +18,7 @@ QUnit.test("test: purchase order", function(assert) { {"qty": 5}, {"uom": 'Unit'}, {"rate": 100}, - {"warehouse": 'Stores - WP'} + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} ] ]}, 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 index 09fc33d4d8..8c0c144314 100644 --- 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 @@ -8,7 +8,6 @@ QUnit.test("test: purchase order with get items", function(assert) { () => { return frappe.tests.make('Purchase Order', [ {supplier: 'Test Supplier'}, - {company: 'Wind Power LLC'}, {is_subcontracted: 'No'}, {buying_price_list: 'Test-Buying-USD'}, {currency: 'USD'}, @@ -18,7 +17,7 @@ QUnit.test("test: purchase order with get items", function(assert) { {"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 - WP'} + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} ] ]} ]); @@ -46,9 +45,9 @@ QUnit.test("test: purchase order with get items", function(assert) { assert.ok(cur_frm.doc.items[3].item_name == 'Keyboard', "Product bundle item 3 correct"); }, - () => cur_frm.doc.items[1].warehouse = 'Stores - WP', - () => cur_frm.doc.items[2].warehouse = 'Stores - WP', - () => cur_frm.doc.items[3].warehouse = 'Stores - WP', + () => 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), 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 index 654586ae57..daf8d6c259 100644 --- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js +++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js @@ -8,7 +8,6 @@ QUnit.test("test: purchase order receipt", function(assert) { () => { return frappe.tests.make('Purchase Order', [ {supplier: 'Test Supplier'}, - {company: 'Wind Power LLC'}, {is_subcontracted: 'No'}, {buying_price_list: 'Test-Buying-USD'}, {currency: 'USD'}, @@ -20,7 +19,7 @@ QUnit.test("test: purchase order receipt", function(assert) { {"qty": 5}, {"uom": 'Unit'}, {"rate": 100}, - {"warehouse": 'Stores - WP'} + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} ] ]}, ]); @@ -68,10 +67,8 @@ QUnit.test("test: purchase order receipt", function(assert) { () => 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) - && $('div.slick-cell.l12.r12 > div').text().includes(100), "Stock ledger entry correct"); + && $('div.slick-cell.l9.r9 > div').text().includes(5), "Stock ledger entry correct"); }, - () => done() ]); -}); \ No newline at end of file +}); 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 index 54fa777668..4e73ab8ef4 100644 --- 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 @@ -8,7 +8,6 @@ QUnit.test("test: purchase order with discount on grand total", function(assert) () => { return frappe.tests.make('Purchase Order', [ {supplier: 'Test Supplier'}, - {company: 'Wind Power LLC'}, {is_subcontracted: 'No'}, {buying_price_list: 'Test-Buying-EUR'}, {currency: 'EUR'}, @@ -20,7 +19,7 @@ QUnit.test("test: purchase order with discount on grand total", function(assert) {"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 - WP'} + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} ] ]}, {apply_discount_on: 'Grand Total'}, 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 index 4ea8b976fb..1e54e50dda 100644 --- 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 @@ -8,7 +8,6 @@ QUnit.test("test: purchase order with item wise discount", function(assert) { () => { return frappe.tests.make('Purchase Order', [ {supplier: 'Test Supplier'}, - {company: 'Wind Power LLC'}, {is_subcontracted: 'No'}, {buying_price_list: 'Test-Buying-EUR'}, {currency: 'EUR'}, @@ -19,7 +18,7 @@ QUnit.test("test: purchase order with item wise discount", function(assert) { {"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 - WP'}, + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}, {"discount_percentage": 20} ] ]} 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 index 0f543c5972..bf2dfeb37b 100644 --- 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 @@ -8,7 +8,6 @@ QUnit.test("test: purchase order with multi UOM", function(assert) { () => { return frappe.tests.make('Purchase Order', [ {supplier: 'Test Supplier'}, - {company: 'Wind Power LLC'}, {is_subcontracted: 'No'}, {items: [ [ @@ -18,7 +17,7 @@ QUnit.test("test: purchase order with multi UOM", function(assert) { {"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 - WP'} + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} ] ]} ]); 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 index b5f59ad339..9d87af2342 100644 --- 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 @@ -8,7 +8,6 @@ QUnit.test("test: purchase order with taxes and charges", function(assert) { () => { return frappe.tests.make('Purchase Order', [ {supplier: 'Test Supplier'}, - {company: 'Wind Power LLC'}, {is_subcontracted: 'No'}, {buying_price_list: 'Test-Buying-USD'}, {currency: 'USD'}, @@ -20,7 +19,7 @@ QUnit.test("test: purchase order with taxes and charges", function(assert) { {"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 - WP'} + {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} ] ]}, 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 2d8820d992..a775f5f345 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -195,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/tests/test_request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js index 6927d82b72..46e8d1f2d6 100644 --- 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 @@ -9,7 +9,6 @@ QUnit.test("test: request_for_quotation", function(assert) { date = frappe.datetime.add_days(frappe.datetime.now_date(), 10); return frappe.tests.make('Request for Quotation', [ {transaction_date: date}, - {company: 'Wind Power LLC'}, {suppliers: [ [ {"supplier": 'Test Supplier'}, @@ -21,7 +20,7 @@ QUnit.test("test: request_for_quotation", function(assert) { {"item_code": 'Test Product 4'}, {"qty": 5}, {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(),20)}, - {"warehouse": 'All Warehouses - WP'} + {"warehouse": 'All Warehouses - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} ] ]}, {message_for_supplier: 'Please supply the specified items at the best possible rates'}, @@ -30,12 +29,12 @@ QUnit.test("test: request_for_quotation", function(assert) { }, () => { assert.ok(cur_frm.doc.transaction_date == date, "Date correct"); - assert.ok(cur_frm.doc.company == 'Wind Power LLC', "Company 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 - WP', "Warehouse 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"); }, 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/tests/test_supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js index 8404cb5b25..76be06c6fb 100644 --- a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js @@ -11,16 +11,14 @@ QUnit.test("test: supplier quotation", function(assert) { return frappe.tests.make('Supplier Quotation', [ {supplier: 'Test Supplier'}, {transaction_date: date}, - {company: 'Wind Power LLC'}, - {buying_price_list: 'Test-Buying-USD'}, - {currency: 'USD'}, + {currency: 'INR'}, {items: [ [ {"item_code": 'Test Product 4'}, {"qty": 5}, {"uom": 'Unit'}, {"rate": 200}, - {"warehouse": 'All Warehouses - WP'} + {"warehouse": 'All Warehouses - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} ] ]}, {apply_discount_on: 'Grand Total'}, @@ -32,7 +30,7 @@ QUnit.test("test: supplier quotation", function(assert) { () => { // Get Supplier details assert.ok(cur_frm.doc.supplier == 'Test Supplier', "Supplier correct"); - assert.ok(cur_frm.doc.company == 'Wind Power LLC', "Company 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"); 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/controllers/queries.py b/erpnext/controllers/queries.py index 6d69a48ab8..11c0790976 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -232,7 +232,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, select `tabDelivery Note`.name, `tabDelivery Note`.customer, `tabDelivery Note`.posting_date from `tabDelivery Note` where `tabDelivery Note`.`%(key)s` like %(txt)s and - `tabDelivery Note`.docstatus = 1 and `tabDelivery Note`.is_return = 0 + `tabDelivery Note`.docstatus = 1 and `tabDelivery Note`.is_return = 0 and status not in ("Stopped", "Closed") %(fcond)s and (`tabDelivery Note`.per_billed < 100 or `tabDelivery Note`.grand_total = 0) %(mcond)s order by `tabDelivery Note`.`%(key)s` asc @@ -367,31 +367,30 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters): sub_query = """ select round(`tabBin`.actual_qty, 2) from `tabBin` where `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions} """.format( - bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"), + bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True)) - response = frappe.db.sql("""select `tabWarehouse`.name, + query = """select `tabWarehouse`.name, CONCAT_WS(" : ", "Actual Qty", ifnull( ({sub_query}), 0) ) as actual_qty from `tabWarehouse` where - `tabWarehouse`.`{key}` like %(txt)s + `tabWarehouse`.`{key}` like '{txt}' {fcond} {mcond} order by `tabWarehouse`.name desc limit - %(start)s, %(page_len)s + {start}, {page_len} """.format( sub_query=sub_query, key=frappe.db.escape(searchfield), fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions), - mcond=get_match_cond(doctype) - ), - { - "txt": "%%%s%%" % frappe.db.escape(txt), - "start": start, - "page_len": page_len - }) - return response + mcond=get_match_cond(doctype), + start=start, + page_len=page_len, + txt=frappe.db.escape('%{0}%'.format(txt)) + ) + + return frappe.db.sql(query) def get_doctype_wise_filters(filters): diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 97bd771fa2..c627664ea5 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -270,7 +270,7 @@ class calculate_taxes_and_totals(object): if tax.item_wise_tax_detail.get(key): item_wise_tax_amount += tax.item_wise_tax_detail[key][1] - tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount, tax.precision("base_tax_amount"))] + tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)] def round_off_totals(self, tax): tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount")) @@ -521,12 +521,20 @@ def get_itemised_tax_breakup_html(doc): frappe.flags.company = doc.company # get headers - tax_accounts = list(set([d.description for d in doc.taxes])) + tax_accounts = [] + for tax in doc.taxes: + if getattr(tax, "category", None) and tax.category=="Valuation": + continue + if tax.description not in tax_accounts: + tax_accounts.append(tax.description) + headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts) # get tax breakup data itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc) - + + get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes")) + frappe.flags.company = None return frappe.render_template( @@ -554,6 +562,9 @@ def get_itemised_tax_breakup_data(doc): def get_itemised_tax(taxes): itemised_tax = {} for tax in taxes: + if getattr(tax, "category", None) and tax.category=="Valuation": + continue + tax_amount_precision = tax.precision("tax_amount") tax_rate_precision = tax.precision("rate") @@ -562,16 +573,16 @@ def get_itemised_tax(taxes): for item_code, tax_data in item_tax_map.items(): itemised_tax.setdefault(item_code, frappe._dict()) - if isinstance(tax_data, list) and tax_data[0]: + if isinstance(tax_data, list): precision = tax_amount_precision if tax.charge_type == "Actual" else tax_rate_precision itemised_tax[item_code][tax.description] = frappe._dict(dict( - tax_rate=flt(tax_data[0], precision), - tax_amount=flt(tax_data[1], tax_amount_precision) + tax_rate=flt(tax_data[0]), + tax_amount=flt(tax_data[1]) )) else: itemised_tax[item_code][tax.description] = frappe._dict(dict( - tax_rate=flt(tax_data, tax_rate_precision), + tax_rate=flt(tax_data), tax_amount=0.0 )) @@ -584,4 +595,10 @@ def get_itemised_taxable_amount(items): itemised_taxable_amount.setdefault(item_code, 0) itemised_taxable_amount[item_code] += item.net_amount - return itemised_taxable_amount \ No newline at end of file + return itemised_taxable_amount + +def get_rounded_tax_amount(itemised_tax, precision): + # Rounding based on tax_amount precision + for taxes in itemised_tax.values(): + for tax_account in taxes: + taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision) \ No newline at end of file diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index 73badc293d..65360ec9ff 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -68,8 +68,8 @@ def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_len if txt: if meta.get_field('items'): if meta.get_field('items').options: - child_doctype = meta.get_field('items').options - for item in frappe.get_all(child_doctype, {"item_name": ['like', "%" + txt + "%"]}): + child_doctype = meta.get_field('items').options + for item in frappe.get_all(child_doctype, {"item_name": ['like', "%" + txt + "%"]}): child = frappe.get_doc(child_doctype, item.name) or_filters.append([doctype, "name", "=", child.parent]) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index e7c2d33f3c..c9d04ac1fc 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -76,36 +76,6 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "salutation", - "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": "Salutation", - "length": 0, - "no_copy": 0, - "options": "Salutation", - "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_on_submit": 0, "bold": 0, "collapsible": 0, @@ -141,21 +111,21 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "gender", - "fieldtype": "Link", + "fieldname": "company_name", + "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, - "label": "Gender", + "label": "Organization Name", "length": 0, "no_copy": 0, - "options": "Gender", + "oldfieldname": "company_name", + "oldfieldtype": "Data", "permlevel": 0, - "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -260,6 +230,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "gender", + "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": "Gender", + "length": 0, + "no_copy": 0, + "options": "Gender", + "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, @@ -295,36 +296,6 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company_name", - "fieldtype": "Data", - "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": "Organization Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "company_name", - "oldfieldtype": "Data", - "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_on_submit": 0, "bold": 0, "collapsible": 0, @@ -435,6 +406,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Follow Up", "length": 0, "no_copy": 0, "permlevel": 0, @@ -514,11 +486,12 @@ { "allow_bulk_edit": 0, "allow_on_submit": 0, - "bold": 0, + "bold": 1, "collapsible": 0, "columns": 0, - "fieldname": "contact_by", - "fieldtype": "Link", + "description": "", + "fieldname": "contact_date", + "fieldtype": "Datetime", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -526,12 +499,11 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Next Contact By", + "label": "Next Contact Date", "length": 0, - "no_copy": 0, - "oldfieldname": "contact_by", - "oldfieldtype": "Link", - "options": "User", + "no_copy": 1, + "oldfieldname": "contact_date", + "oldfieldtype": "Date", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -550,9 +522,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "Add to calendar on this date", - "fieldname": "contact_date", - "fieldtype": "Datetime", + "fieldname": "contact_by", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -560,11 +531,12 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Next Contact Date", + "label": "Next Contact By", "length": 0, - "no_copy": 1, - "oldfieldname": "contact_date", - "oldfieldtype": "Date", + "no_copy": 0, + "oldfieldname": "contact_by", + "oldfieldtype": "Link", + "options": "User", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -726,6 +698,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "salutation", + "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": "Salutation", + "length": 0, + "no_copy": 0, + "options": "Salutation", + "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, @@ -1144,7 +1147,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-06-22 14:29:12.700000", + "modified": "2017-08-21 02:28:21.581948", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index dc8b3e72a0..89eb1919d0 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -419,6 +419,164 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "collapsible_depends_on": "contact_by", + "columns": 0, + "fieldname": "next_contact", + "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": "Follow Up", + "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": "", + "fieldname": "contact_by", + "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": 1, + "label": "Next Contact By", + "length": 0, + "no_copy": 0, + "oldfieldname": "contact_by", + "oldfieldtype": "Link", + "options": "User", + "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, + "width": "75px" + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "contact_date", + "fieldtype": "Datetime", + "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": "Next Contact Date", + "length": 0, + "no_copy": 0, + "oldfieldname": "contact_date", + "oldfieldtype": "Date", + "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": "column_break2", + "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, + "oldfieldtype": "Column Break", + "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, + "width": "50%" + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_discuss", + "fieldtype": "Small Text", + "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": "To Discuss", + "length": 0, + "no_copy": 1, + "oldfieldname": "to_discuss", + "oldfieldtype": "Small Text", + "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, @@ -435,7 +593,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "", + "label": "Items", "length": 0, "no_copy": 0, "oldfieldtype": "Section Break", @@ -986,164 +1144,6 @@ "unique": 0, "width": "50px" }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "contact_by", - "columns": 0, - "fieldname": "next_contact", - "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": "Next Contact", - "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": "Your sales person who will contact the customer in future", - "fieldname": "contact_by", - "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": 1, - "label": "Next Contact By", - "length": 0, - "no_copy": 0, - "oldfieldname": "contact_by", - "oldfieldtype": "Link", - "options": "User", - "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, - "width": "75px" - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Your sales person will get a reminder on this date to contact the customer", - "fieldname": "contact_date", - "fieldtype": "Datetime", - "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": "Next Contact Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "contact_date", - "oldfieldtype": "Date", - "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": "column_break2", - "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, - "oldfieldtype": "Column Break", - "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, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_discuss", - "fieldtype": "Small Text", - "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": "To Discuss", - "length": 0, - "no_copy": 1, - "oldfieldname": "to_discuss", - "oldfieldtype": "Small Text", - "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, @@ -1189,7 +1189,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-07 21:25:10.836517", + "modified": "2017-08-21 02:07:46.486433", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py index ae792ac4a0..cec425ce6b 100644 --- a/erpnext/demo/setup/setup_data.py +++ b/erpnext/demo/setup/setup_data.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import random, json import frappe, erpnext @@ -42,7 +42,7 @@ def setup(domain): frappe.clear_cache() def complete_setup(domain='Manufacturing'): - print "Complete Setup..." + print("Complete Setup...") from frappe.desk.page.setup_wizard.setup_wizard import setup_complete if not frappe.get_all('Company', limit=1): diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py index 1b12db8452..43668fe369 100644 --- a/erpnext/demo/user/stock.py +++ b/erpnext/demo/user/stock.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe, random from frappe.desk import query_report @@ -36,7 +36,7 @@ def make_purchase_receipt(): try: pr.submit() except NegativeStockError: - print 'Negative stock for {0}'.format(po) + print('Negative stock for {0}'.format(po)) pass frappe.db.commit() 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/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/hooks.py b/erpnext/hooks.py index 4df83afa10..7e65fc9262 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -196,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/appraisal/test_appraisal.js b/erpnext/hr/doctype/appraisal/test_appraisal.js new file mode 100644 index 0000000000..91da7d3624 --- /dev/null +++ b/erpnext/hr/doctype/appraisal/test_appraisal.js @@ -0,0 +1,58 @@ +QUnit.module('hr'); + +QUnit.test("Test: Expense Claim [HR]", function (assert) { + assert.expect(3); + let done = assert.async(); + let employee_name; + + frappe.run_serially([ + // Creating Appraisal + () => frappe.set_route('List','Appraisal','List'), + () => frappe.timeout(0.3), + () => frappe.click_button('Make a new Appraisal'), + () => { + cur_frm.set_value('kra_template','Test Appraisal 1'), + cur_frm.set_value('start_date','2017-08-21'), + cur_frm.set_value('end_date','2017-09-21'); + }, + () => frappe.timeout(1), + () => frappe.model.set_value('Appraisal Goal','New Appraisal Goal 1','score',4), + () => frappe.model.set_value('Appraisal Goal','New Appraisal Goal 1','score_earned',2), + () => frappe.model.set_value('Appraisal Goal','New Appraisal Goal 2','score',4), + () => frappe.model.set_value('Appraisal Goal','New Appraisal Goal 2','score_earned',2), + () => frappe.timeout(1), + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + + () => frappe.timeout(1), + () => cur_frm.set_value('employee',employee_name), + () => cur_frm.set_value('employee_name','Test Employee 1'), + () => cur_frm.set_value('company','Test Company'), + () => frappe.click_button('Calculate Total Score'), + () => frappe.timeout(1), + () => cur_frm.save(), + () => frappe.timeout(1), + () => cur_frm.save(), + + // Submitting the Appraisal + () => frappe.click_button('Submit'), + () => frappe.click_button('Yes'), + () => frappe.timeout(3), + + // Checking if the appraisal is correctly set for the employee + () => { + assert.equal('Submitted',cur_frm.get_field('status').value, + 'Appraisal is submitted'); + + assert.equal('Test Employee 1',cur_frm.get_field('employee_name').value, + 'Appraisal is created for correct employee'); + + assert.equal(4,cur_frm.get_field('total_score').value, + 'Total score is correctly calculated'); + }, + () => done() + ]); +}); + diff --git a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js new file mode 100644 index 0000000000..4e245c7117 --- /dev/null +++ b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js @@ -0,0 +1,30 @@ +QUnit.module('hr'); +QUnit.test("Test: Appraisal Template [HR]", function (assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + // Job Opening creation + () => { + frappe.tests.make('Appraisal Template', [ + { kra_title: 'Test Appraisal 1'}, + { description: 'This is just a test'}, + { goals: [ + [ + { kra: 'Design'}, + { per_weightage: 50} + ], + [ + { kra: 'Code creation'}, + { per_weightage: 50} + ] + ]}, + ]); + }, + () => frappe.timeout(5), + () => { + assert.equal('Test Appraisal 1',cur_frm.doc.kra_title, 'Appraisal name correctly set'); + }, + () => done() + ]); +}); + diff --git a/erpnext/hr/doctype/employee_loan/test_employee_loan.js b/erpnext/hr/doctype/employee_loan/test_employee_loan.js new file mode 100644 index 0000000000..9039339773 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/test_employee_loan.js @@ -0,0 +1,79 @@ + +QUnit.test("Test Loan [HR]", function(assert) { + assert.expect(8); + let done = assert.async(); + let employee_name; + + // To create a loan and check principal,interest and balance amount + let loan_creation = (ename,lname) => { + return frappe.run_serially([ + () => frappe.db.get_value('Employee', {'employee_name': ename}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => frappe.db.get_value('Employee Loan Application', {'loan_type': lname}, 'name'), + (r) => { + // Creating loan for an employee + return frappe.tests.make('Employee Loan', [ + { company: 'Test Company'}, + { posting_date: '2017-08-26'}, + { employee: employee_name}, + { employee_loan_application: r.message.name}, + { disbursement_date: '2018-08-26'}, + { mode_of_payment: 'Cash'}, + { employee_loan_account: 'Temporary Opening - TC'}, + { interest_income_account: 'Service - TC'} + ]); + }, + () => frappe.timeout(3), + () => frappe.click_button('Submit'), + () => frappe.timeout(1), + () => frappe.click_button('Yes'), + () => frappe.timeout(3), + + // Checking if all the amounts are correctly calculated + () => { + assert.ok(cur_frm.get_field('employee_name').value=='Test Employee 1'&& + (cur_frm.get_field('status').value=='Sanctioned'), + 'Loan Sanctioned for correct employee'); + + assert.equal(7270, + cur_frm.get_doc('repayment_schedule').repayment_schedule[0].principal_amount, + 'Principal amount for first instalment is correctly calculated'); + + assert.equal(2333, + cur_frm.get_doc('repayment_schedule').repayment_schedule[0].interest_amount, + 'Interest amount for first instalment is correctly calculated'); + + assert.equal(192730, + cur_frm.get_doc('repayment_schedule').repayment_schedule[0].balance_loan_amount, + 'Balance amount after first instalment is correctly calculated'); + + assert.equal(9479, + cur_frm.get_doc('repayment_schedule').repayment_schedule[23].principal_amount, + 'Principal amount for last instalment is correctly calculated'); + + assert.equal(111, + cur_frm.get_doc('repayment_schedule').repayment_schedule[23].interest_amount, + 'Interest amount for last instalment is correctly calculated'); + + assert.equal(0, + cur_frm.get_doc('repayment_schedule').repayment_schedule[23].balance_loan_amount, + 'Balance amount after last instalment is correctly calculated'); + + }, + () => frappe.set_route('List','Employee Loan','List'), + () => frappe.timeout(2), + + // Checking the submission of Loan + () => { + assert.ok(cur_list.data[0].docstatus==1,'Loan sanctioned and submitted successfully'); + }, + ]); + }; + frappe.run_serially([ + // Creating loan + () => loan_creation('Test Employee 1','Test Loan'), + () => done() + ]); +}); diff --git a/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js b/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js new file mode 100644 index 0000000000..72ad915f7d --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js @@ -0,0 +1,68 @@ +QUnit.module('hr'); + +QUnit.test("Test: Employee Loan Application [HR]", function (assert) { + assert.expect(8); + let done = assert.async(); + let employee_name; + + frappe.run_serially([ + // Creation of Loan Application + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => { + frappe.tests.make('Employee Loan Application', [ + { company: 'Test Company'}, + { employee: employee_name}, + { employee_name: 'Test Employee 1'}, + { status: 'Approved'}, + { loan_type: 'Test Loan '}, + { loan_amount: 200000}, + { description: 'This is just a test'}, + { repayment_method: 'Repay Over Number of Periods'}, + { repayment_periods: 24}, + { rate_of_interest: 14} + ]); + }, + () => frappe.timeout(6), + () => frappe.click_button('Submit'), + () => frappe.timeout(1), + () => frappe.click_button('Yes'), + () => frappe.timeout(2), + () => { + // To check if all the amounts are correctly calculated + + assert.ok(cur_frm.get_field('employee_name').value == 'Test Employee 1', + 'Application created successfully'); + + assert.ok(cur_frm.get_field('status').value=='Approved', + 'Status of application is correctly set'); + + assert.ok(cur_frm.get_field('loan_type').value=='Test Loan', + 'Application is created for correct Loan Type'); + + assert.ok(cur_frm.get_field('status').value=='Approved', + 'Status of application is correctly set'); + + assert.ok(cur_frm.get_field('repayment_amount').value==9603, + 'Repayment amount is correctly calculated'); + + assert.ok(cur_frm.get_field('total_payable_interest').value==30459, + 'Interest amount is correctly calculated'); + + assert.ok(cur_frm.get_field('total_payable_amount').value==230459, + 'Total payable amount is correctly calculated'); + }, + + () => frappe.set_route('List','Employee Loan Application','List'), + () => frappe.timeout(2), + + // Checking the submission of Loan Application + () => { + assert.ok(cur_list.data[0].docstatus==1,'Loan Application submitted successfully'); + }, + () => frappe.timeout(1), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.js b/erpnext/hr/doctype/expense_claim/test_expense_claim.js new file mode 100644 index 0000000000..c7c764cab5 --- /dev/null +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.js @@ -0,0 +1,59 @@ +QUnit.module('hr'); + +QUnit.test("Test: Expense Claim [HR]", function (assert) { + assert.expect(3); + let done = assert.async(); + let employee_name; + let d; + frappe.run_serially([ + // Creating Expense Claim + () => frappe.set_route('List','Expense Claim','List'), + () => frappe.timeout(0.3), + () => frappe.click_button('Make a new Expense Claim'), + () => { + cur_frm.set_value('exp_approver','Administrator'), + cur_frm.set_value('is_paid',1), + cur_frm.set_value('expenses',[]), + d = frappe.model.add_child(cur_frm.doc,'Expense Claim Detail','expenses'), + d.expense_date = '2017-08-01', + d.expense_type = 'Test Expense Type 1', + d.description = 'This is just to test Expense Claim', + d.claim_amount = 2000, + d.sanctioned_amount=2000, + refresh_field('expenses'); + }, + () => frappe.timeout(2), + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => frappe.timeout(1), + () => cur_frm.set_value('employee',employee_name), + () => cur_frm.set_value('employee_name','Test Employee 1'), + () => cur_frm.set_value('company','Test Company'), + () => cur_frm.set_value('payable_account','Creditors - TC'), + () => cur_frm.set_value('cost_center','Main - TC'), + () => cur_frm.set_value('mode_of_payment','Cash'), + () => cur_frm.save(), + () => frappe.timeout(1), + () => cur_frm.set_value('approval_status','Approved'), + () => frappe.timeout(1), + () => cur_frm.save(), + // Submitting the Expense Claim + () => frappe.click_button('Submit'), + () => frappe.click_button('Yes'), + () => frappe.timeout(3), + + // Checking if the amount is correctly reimbursed for the employee + () => { + assert.equal(employee_name,cur_frm.get_field('employee').value, + 'Expense Claim is created for correct employee'); + assert.equal(1,cur_frm.get_field('is_paid').value, + 'Expense is paid as required'); + assert.equal(2000,cur_frm.get_field('total_amount_reimbursed').value, + 'Amount is reimbursed correctly'); + }, + () => done() + ]); +}); + diff --git a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js new file mode 100644 index 0000000000..595454fca0 --- /dev/null +++ b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js @@ -0,0 +1,30 @@ +QUnit.module('hr'); + +QUnit.test("Test: Expense Claim Type [HR]", function (assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + // Creating a Expense Claim Type + () => { + frappe.tests.make('Expense Claim Type', [ + { expense_type: 'Test Expense Type 1'}, + { description:'This is just a test'}, + { accounts: [ + [ + { company: 'Test Company'}, + { default_account: 'Round Off - TC'} + ] + ]}, + ]); + }, + () => frappe.timeout(5), + + // Checking if the created type is present in the list + () => { + assert.equal('Test Expense Type 1', cur_frm.doc.expense_type, + 'Expense Claim Type created successfully'); + }, + () => 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_application/test_leave_application.js b/erpnext/hr/doctype/leave_application/test_leave_application.js index 51e8ed623c..6028405c46 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.js +++ b/erpnext/hr/doctype/leave_application/test_leave_application.js @@ -1,7 +1,7 @@ QUnit.module('hr'); QUnit.test("Test: Leave application [HR]", function (assert) { - assert.expect(5); + assert.expect(4); let done = assert.async(); let today_date = frappe.datetime.nowdate(); let leave_date = frappe.datetime.add_days(today_date, 1); // leave for tomorrow @@ -22,8 +22,6 @@ QUnit.test("Test: Leave application [HR]", function (assert) { }, () => frappe.timeout(1), // check calculated total leave days - () => assert.equal("0.5", cur_frm.doc.total_leave_days, - "leave application for half day"), () => assert.ok(!cur_frm.doc.docstatus, "leave application not submitted with status as open"), () => cur_frm.set_value("status", "Approved"), // approve the application [as administrator] diff --git a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py index 42b26e342d..210b8b78b7 100644 --- a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py +++ b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py @@ -10,7 +10,7 @@ from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_ class TestLeaveBlockList(unittest.TestCase): def tearDown(self): - frappe.set_user("Administrator") + frappe.set_user("Administrator") def test_get_applicable_block_dates(self): frappe.set_user("test@example.com") diff --git a/erpnext/hr/doctype/loan_type/test_loan_type.js b/erpnext/hr/doctype/loan_type/test_loan_type.js new file mode 100644 index 0000000000..8b5032b04e --- /dev/null +++ b/erpnext/hr/doctype/loan_type/test_loan_type.js @@ -0,0 +1,31 @@ +QUnit.module('hr'); + +QUnit.test("Test: Loan Type [HR]", function (assert) { + assert.expect(3); + let done = assert.async(); + + frappe.run_serially([ + // Loan Type creation + () => { + frappe.tests.make('Loan Type', [ + { loan_name: 'Test Loan'}, + { maximum_loan_amount: 400000}, + { rate_of_interest: 14}, + { description: + 'This is just a test.'} + ]); + }, + () => frappe.timeout(3), + () => frappe.set_route('List','Loan Type','List'), + () => frappe.timeout(2), + + // Checking if the fields are correctly set + () => { + assert.ok(cur_list.data.length==1, 'Loan Type created successfully'); + assert.ok(cur_list.data[0].name=='Test Loan', 'Loan title Correctly set'); + assert.ok(cur_list.data[0].disabled==0, 'Loan enabled'); + }, + () => 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/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 1d17613381..356104e69c 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -231,12 +231,12 @@ class SalarySlip(TransactionBase): holidays = self.get_holidays_for_employee(self.start_date, self.end_date) working_days = date_diff(self.end_date, self.start_date) + 1 + actual_lwp = self.calculate_lwp(holidays, working_days) if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")): working_days -= len(holidays) if working_days < 0: frappe.throw(_("There are more holidays than working days this month.")) - actual_lwp = self.calculate_lwp(holidays, working_days) if not lwp: lwp = actual_lwp elif lwp != actual_lwp: diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.js b/erpnext/hr/doctype/salary_structure/test_salary_structure.js index 074035772c..23b52f6a1d 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.js +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.js @@ -46,7 +46,7 @@ QUnit.test("test Salary Structure", function(assert) { { payment_account: 'CASH - TC'}, ]); }, - () => frappe.timeout(9), + () => frappe.timeout(10), () => cur_dialog.set_value('value','Test Salary Structure'), () => frappe.timeout(2), () => frappe.click_button('Create'), @@ -75,7 +75,7 @@ QUnit.test("test Salary Structure", function(assert) { }; frappe.run_serially([ () => salary_structure('Test Employee 1','Test Employee 3'), - () => frappe.timeout(14), + () => frappe.timeout(16), () => done() ]); }); \ No newline at end of file diff --git a/erpnext/hr/doctype/training_event/test_training_event.js b/erpnext/hr/doctype/training_event/test_training_event.js new file mode 100644 index 0000000000..a359af3329 --- /dev/null +++ b/erpnext/hr/doctype/training_event/test_training_event.js @@ -0,0 +1,55 @@ +QUnit.module('hr'); + +QUnit.test("Test: Training Event [HR]", function (assert) { + assert.expect(4); + let done = assert.async(); + let employee_name; + + frappe.run_serially([ + // Creation of Training Event + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => { + frappe.tests.make('Training Event', [ + { event_name: 'Test Training Event 1'}, + { location: 'Mumbai'}, + { start_time: '2017-09-01 11:00:0'}, + { end_time: '2017-09-01 17:00:0'}, + { introduction: 'This is just a test'}, + { employees: [ + [ + {employee: employee_name}, + {employee_name: 'Test Employee 1'} + ] + ]}, + ]); + }, + () => frappe.timeout(7), + () => frappe.click_button('Submit'), + () => frappe.timeout(1), + () => frappe.click_button('Yes'), + () => frappe.timeout(8), + () => { + // To check if the fields are correctly set + assert.ok(cur_frm.get_field('event_name').value == 'Test Training Event 1', + 'Event created successfully'); + + assert.ok(cur_frm.get_field('event_status').value=='Scheduled', + 'Status of event is correctly set'); + + assert.ok(cur_frm.doc.employees[0].employee_name=='Test Employee 1', + 'Attendee Employee is correctly set'); + }, + + () => frappe.set_route('List','Training Event','List'), + () => frappe.timeout(2), + // Checking the submission of Training Event + () => { + assert.ok(cur_list.data[0].docstatus==1,'Training Event Submitted successfully'); + }, + () => frappe.timeout(2), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/training_feedback/test_training_feedback.js b/erpnext/hr/doctype/training_feedback/test_training_feedback.js new file mode 100644 index 0000000000..9daa51f927 --- /dev/null +++ b/erpnext/hr/doctype/training_feedback/test_training_feedback.js @@ -0,0 +1,52 @@ +QUnit.module('hr'); + +QUnit.test("Test: Training Feedback [HR]", function (assert) { + assert.expect(3); + let done = assert.async(); + let employee_name; + + frappe.run_serially([ + // Creating Training Feedback + () => frappe.set_route('List','Training Feedback','List'), + () => frappe.timeout(0.3), + () => frappe.click_button('Make a new Training Feedback'), + () => frappe.timeout(1), + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => cur_frm.set_value('employee',employee_name), + () => cur_frm.set_value('employee_name','Test Employee 1'), + () => cur_frm.set_value('training_event','Test Training Event 1'), + () => cur_frm.set_value('event_name','Test Training Event 1'), + () => cur_frm.set_value('feedback','Great Experience. This is just a test.'), + () => frappe.timeout(1), + () => cur_frm.save(), + () => frappe.timeout(1), + () => cur_frm.save(), + + // Submitting the feedback + () => frappe.click_button('Submit'), + () => frappe.click_button('Yes'), + () => frappe.timeout(3), + + // Checking if the feedback is given by correct employee + () => { + assert.equal('Test Employee 1',cur_frm.get_field('employee_name').value, + 'Feedback is given by correct employee'); + + assert.equal('Test Training Event 1',cur_frm.get_field('training_event').value, + 'Feedback is given for correct event'); + }, + + () => frappe.set_route('List','Training Feedback','List'), + () => frappe.timeout(2), + + // Checking the submission of Training Result + () => { + assert.ok(cur_list.data[0].docstatus==1,'Training Feedback Submitted successfully'); + }, + () => done() + ]); +}); + diff --git a/erpnext/hr/doctype/training_result_employee/test_training_result.js b/erpnext/hr/doctype/training_result_employee/test_training_result.js new file mode 100644 index 0000000000..2ebf8962ee --- /dev/null +++ b/erpnext/hr/doctype/training_result_employee/test_training_result.js @@ -0,0 +1,53 @@ +QUnit.module('hr'); + +QUnit.test("Test: Training Result [HR]", function (assert) { + assert.expect(5); + let done = assert.async(); + frappe.run_serially([ + // Creating Training Result + () => frappe.set_route('List','Training Result','List'), + () => frappe.timeout(0.3), + () => frappe.click_button('Make a new Training Result'), + () => { + cur_frm.set_value('training_event','Test Training Event 1'); + }, + () => frappe.timeout(1), + () => frappe.model.set_value('Training Result Employee','New Training Result Employee 1','hours',4), + () => frappe.model.set_value('Training Result Employee','New Training Result Employee 1','grade','A'), + () => frappe.model.set_value('Training Result Employee','New Training Result Employee 1','comments','Nice Seminar'), + () => frappe.timeout(1), + () => cur_frm.save(), + () => frappe.timeout(1), + () => cur_frm.save(), + + // Submitting the Training Result + () => frappe.click_button('Submit'), + () => frappe.click_button('Yes'), + () => frappe.timeout(4), + + // Checking if the fields are correctly set + () => { + assert.equal('Test Training Event 1',cur_frm.get_field('training_event').value, + 'Training Result is created'); + + assert.equal('Test Employee 1',cur_frm.doc.employees[0].employee_name, + 'Training Result is created for correct employee'); + + assert.equal(4,cur_frm.doc.employees[0].hours, + 'Hours field is correctly calculated'); + + assert.equal('A',cur_frm.doc.employees[0].grade, + 'Grade field is correctly set'); + }, + + () => frappe.set_route('List','Training Result','List'), + () => frappe.timeout(2), + + // Checking the submission of Training Result + () => { + assert.ok(cur_list.data[0].docstatus==1,'Training Result Submitted successfully'); + }, + () => done() + ]); +}); + 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..e48a00249d 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` @@ -248,15 +259,15 @@ class BOM(WebsiteGenerator): def update_stock_qty(self): - for m in self.get('items'): + for m in self.get('items'): if not m.conversion_factor: m.conversion_factor = flt(get_conversion_factor(m.item_code, m.uom)['conversion_factor']) - if m.uom and m.qty: - m.stock_qty = flt(m.conversion_factor)*flt(m.qty) - if not m.uom and m.stock_uom: - m.uom = m.stock_uom - m.qty = m.stock_qty + if m.uom and m.qty: + m.stock_qty = flt(m.conversion_factor)*flt(m.qty) + if not m.uom and m.stock_uom: + m.uom = m.stock_uom + m.qty = m.stock_qty def set_conversion_rate(self): @@ -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_replace_tool/__init__.py b/erpnext/manufacturing/doctype/bom_update_tool/__init__.py similarity index 100% rename from erpnext/manufacturing/doctype/bom_replace_tool/__init__.py rename to erpnext/manufacturing/doctype/bom_update_tool/__init__.py 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.txt b/erpnext/patches.txt index 60caf0fec1..35958e34f5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -421,7 +421,7 @@ erpnext.patches.v8_1.removed_report_support_hours erpnext.patches.v8_1.add_indexes_in_transaction_doctypes erpnext.patches.v8_3.set_restrict_to_domain_for_module_def erpnext.patches.v8_1.update_expense_claim_status -erpnext.patches.v8_3.update_company_total_sales +erpnext.patches.v8_3.update_company_total_sales #2017-08-16 erpnext.patches.v8_4.make_scorecard_records erpnext.patches.v8_1.set_delivery_date_in_so_item #2017-07-28 erpnext.patches.v8_5.fix_tax_breakup_for_non_invoice_docs @@ -431,4 +431,8 @@ erpnext.patches.v8_5.set_default_mode_of_payment erpnext.patches.v8_5.update_customer_group_in_POS_profile erpnext.patches.v8_6.update_timesheet_company_from_PO erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager -erpnext.patches.v8_5.remove_project_type_property_setter \ No newline at end of file +erpnext.patches.v8_5.remove_project_type_property_setter +erpnext.patches.v8_7.add_more_gst_fields +erpnext.patches.v8_7.fix_purchase_receipt_status +erpnext.patches.v8_6.rename_bom_update_tool + diff --git a/erpnext/patches/v4_0/new_address_template.py b/erpnext/patches/v4_0/new_address_template.py index f644a5a1ee..fa6602706e 100644 --- a/erpnext/patches/v4_0/new_address_template.py +++ b/erpnext/patches/v4_0/new_address_template.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -10,5 +10,5 @@ def execute(): frappe.db.get_value("Global Defaults", "Global Defaults", "country")}) d.insert() except: - print frappe.get_traceback() + print(frappe.get_traceback()) diff --git a/erpnext/patches/v4_0/reset_permissions_for_masters.py b/erpnext/patches/v4_0/reset_permissions_for_masters.py index b2f1fcd488..bc1b438e2b 100644 --- a/erpnext/patches/v4_0/reset_permissions_for_masters.py +++ b/erpnext/patches/v4_0/reset_permissions_for_masters.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals from frappe.permissions import reset_perms def execute(): @@ -16,5 +16,5 @@ def execute(): try: reset_perms(doctype) except: - print "Error resetting perms for", doctype + print("Error resetting perms for", doctype) raise diff --git a/erpnext/patches/v4_0/set_naming_series_property_setter.py b/erpnext/patches/v4_0/set_naming_series_property_setter.py index 9d12f144ec..e61a5968fe 100644 --- a/erpnext/patches/v4_0/set_naming_series_property_setter.py +++ b/erpnext/patches/v4_0/set_naming_series_property_setter.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.custom.doctype.property_setter.property_setter import make_property_setter @@ -91,7 +91,7 @@ def get_default_series(doctype, new_series): (new_series, new_series)) if not (default_series and default_series[0][0]): - print "[Skipping] Cannot guess which naming series to use for", doctype + print("[Skipping] Cannot guess which naming series to use for", doctype) return return default_series[0][0] diff --git a/erpnext/patches/v4_0/split_email_settings.py b/erpnext/patches/v4_0/split_email_settings.py index 21dc050238..5d1dea60ee 100644 --- a/erpnext/patches/v4_0/split_email_settings.py +++ b/erpnext/patches/v4_0/split_email_settings.py @@ -1,11 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): - print "WARNING!!!! Email Settings not migrated. Please setup your email again." + print("WARNING!!!! Email Settings not migrated. Please setup your email again.") # this will happen if you are migrating very old accounts # comment out this line below and remember to create new Email Accounts diff --git a/erpnext/patches/v4_0/update_account_root_type.py b/erpnext/patches/v4_0/update_account_root_type.py index e3edee99f7..15ddf032a4 100644 --- a/erpnext/patches/v4_0/update_account_root_type.py +++ b/erpnext/patches/v4_0/update_account_root_type.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -31,4 +31,4 @@ def execute(): frappe.db.sql("""UPDATE tabAccount SET root_type=%s WHERE lft>%s and rgt<%s""", (root.root_type, root.lft, root.rgt)) else: - print b"Root type not found for {0}".format(root.name.encode("utf-8")) + print(b"Root type not found for {0}".format(root.name.encode("utf-8"))) diff --git a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py index 0df5801c42..16932af3d6 100644 --- a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py +++ b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.utils import flt @@ -37,7 +37,7 @@ def execute(): if stock_bal and account_bal and abs(flt(stock_bal[0][0]) - flt(account_bal[0][0])) > 0.1: try: - print voucher_type, voucher_no, stock_bal[0][0], account_bal[0][0] + print(voucher_type, voucher_no, stock_bal[0][0], account_bal[0][0]) frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) @@ -45,10 +45,10 @@ def execute(): voucher = frappe.get_doc(voucher_type, voucher_no) voucher.make_gl_entries(repost_future_gle=False) frappe.db.commit() - except Exception, e: - print frappe.get_traceback() + except Exception as e: + print(frappe.get_traceback()) rejected.append([voucher_type, voucher_no]) frappe.db.rollback() - print "Failed to repost: " - print rejected + print("Failed to repost: ") + print(rejected) diff --git a/erpnext/patches/v4_2/party_model.py b/erpnext/patches/v4_2/party_model.py index 8f4fc335d8..6f9335269b 100644 --- a/erpnext/patches/v4_2/party_model.py +++ b/erpnext/patches/v4_2/party_model.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -109,7 +109,7 @@ def delete_individual_party_account(): and exists(select gle.name from `tabGL Entry` gle where gle.account = tabAccount.name)""") if accounts_not_deleted: - print "Accounts not deleted: " + "\n".join(accounts_not_deleted) + print("Accounts not deleted: " + "\n".join(accounts_not_deleted)) def remove_customer_supplier_account_report(): diff --git a/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py b/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py index 44bec0091a..1356129dc0 100644 --- a/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py +++ b/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from erpnext.stock.stock_ledger import NegativeStockError @@ -28,7 +28,7 @@ def execute(): frappe.local.stockledger_exceptions = None frappe.db.rollback() - print "Failed to repost: ", failed_list + print("Failed to repost: ", failed_list) \ No newline at end of file diff --git a/erpnext/patches/v4_2/set_company_country.py b/erpnext/patches/v4_2/set_company_country.py index 929f6c5c51..89f07f2873 100644 --- a/erpnext/patches/v4_2/set_company_country.py +++ b/erpnext/patches/v4_2/set_company_country.py @@ -1,13 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): country = frappe.db.get_single_value("Global Defaults", "country") if not country: - print "Country not specified in Global Defaults" + print("Country not specified in Global Defaults") return for company in frappe.db.sql_list("""select name from `tabCompany` diff --git a/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py b/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py index da58ae2349..76efdcc7c6 100644 --- a/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py +++ b/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -21,6 +21,6 @@ def execute(): je.make_gl_entries() if je_list: - print je_list + print(je_list) \ No newline at end of file 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/v5_4/fix_missing_item_images.py b/erpnext/patches/v5_4/fix_missing_item_images.py index 1891d2d622..c0a25132ff 100644 --- a/erpnext/patches/v5_4/fix_missing_item_images.py +++ b/erpnext/patches/v5_4/fix_missing_item_images.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe import os from frappe.utils import get_files_path @@ -45,7 +45,7 @@ def fix_files_for_item(files_path, unlinked_files): try: file_data.save() except IOError: - print "File {0} does not exist".format(new_file_url) + print("File {0} does not exist".format(new_file_url)) # marking fix to prevent further errors fixed_files.append(file_url) diff --git a/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py b/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py index 125b84fce1..ba311225bb 100644 --- a/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py +++ b/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.email import sendmail_to_system_managers from frappe.utils import get_link_to_form @@ -36,6 +36,6 @@ Administrator""" % "\n".join([(d[0] + ": " + ", ".join(d[1])) for d in wrong_rec except: pass - print "="*50 - print content - print "="*50 \ No newline at end of file + print("="*50) + print(content) + print("="*50) \ No newline at end of file diff --git a/erpnext/patches/v5_7/item_template_attributes.py b/erpnext/patches/v5_7/item_template_attributes.py index 9f141b5b08..22b15d32ae 100644 --- a/erpnext/patches/v5_7/item_template_attributes.py +++ b/erpnext/patches/v5_7/item_template_attributes.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe import MySQLdb @@ -32,7 +32,7 @@ def execute(): migrate_item_variants() except MySQLdb.ProgrammingError: - print "`tabItem Variant` not found" + print("`tabItem Variant` not found") def rename_and_reload_doctypes(): if "tabVariant Attribute" in frappe.db.get_tables(): diff --git a/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py b/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py index dc0df0f89e..fb5eab4e05 100644 --- a/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py +++ b/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe """ @@ -38,19 +38,19 @@ def check(): si_list = get_affected_sales_invoice() if so_list or dn_list or si_list: - print "Entries with Target Warehouse:" + print("Entries with Target Warehouse:") if so_list: - print "Sales Order" - print so_list + print("Sales Order") + print(so_list) if dn_list: - print "Delivery Notes" - print [d.name for d in dn_list] + print("Delivery Notes") + print([d.name for d in dn_list]) if si_list: - print "Sales Invoice" - print [d.name for d in si_list] + print("Sales Invoice") + print([d.name for d in si_list]) def repost(): @@ -61,34 +61,34 @@ def repost(): frappe.db.commit() if dn_failed_list: - print "-"*40 - print "Delivery Note Failed to Repost" - print dn_failed_list + print("-"*40) + print("Delivery Note Failed to Repost") + print(dn_failed_list) if si_failed_list: - print "-"*40 - print "Sales Invoice Failed to Repost" - print si_failed_list - print + print("-"*40) + print("Sales Invoice Failed to Repost") + print(si_failed_list) + print() - print """ + print(""" If above Delivery Notes / Sales Invoice failed due to negative stock, follow these steps: - Ensure that stock is available for those items in the mentioned warehouse on the date mentioned in the error - Run this patch again -""" +""") def repost_dn(dn_failed_list): dn_list = get_affected_delivery_notes() if dn_list: - print "-"*40 - print "Reposting Delivery Notes" + print("-"*40) + print("Reposting Delivery Notes") for dn in dn_list: if dn.docstatus == 0: continue - print dn.name + print(dn.name) try: dn_doc = frappe.get_doc("Delivery Note", dn.name) @@ -107,7 +107,7 @@ def repost_dn(dn_failed_list): except Exception: dn_failed_list.append(dn.name) frappe.local.stockledger_exceptions = None - print frappe.get_traceback() + print(frappe.get_traceback()) frappe.db.rollback() frappe.db.sql("update `tabDelivery Note Item` set target_warehouse='' where docstatus=0") @@ -116,14 +116,14 @@ def repost_si(si_failed_list): si_list = get_affected_sales_invoice() if si_list: - print "-"*40 - print "Reposting Sales Invoice" + print("-"*40) + print("Reposting Sales Invoice") for si in si_list: if si.docstatus == 0: continue - print si.name + print(si.name) try: si_doc = frappe.get_doc("Sales Invoice", si.name) @@ -141,7 +141,7 @@ def repost_si(si_failed_list): except Exception: si_failed_list.append(si.name) frappe.local.stockledger_exceptions = None - print frappe.get_traceback() + print(frappe.get_traceback()) frappe.db.rollback() frappe.db.sql("update `tabSales Invoice Item` set target_warehouse='' where docstatus=0") @@ -152,8 +152,8 @@ def repost_so(): frappe.db.sql("update `tabSales Order Item` set target_warehouse=''") if so_list: - print "-"*40 - print "Sales Order reposted" + print("-"*40) + print("Sales Order reposted") def get_affected_delivery_notes(): diff --git a/erpnext/patches/v6_4/fix_expense_included_in_valuation.py b/erpnext/patches/v6_4/fix_expense_included_in_valuation.py index 436dd02a2c..7ed15ab010 100644 --- a/erpnext/patches/v6_4/fix_expense_included_in_valuation.py +++ b/erpnext/patches/v6_4/fix_expense_included_in_valuation.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.utils import cstr @@ -33,7 +33,7 @@ def execute(): (pi.name, company.expenses_included_in_valuation)) if gle_for_expenses_included_in_valuation: - print pi.name + print(pi.name) frappe.db.sql("""delete from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s""", pi.name) diff --git a/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py b/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py index b1464d5e2a..b53412d7eb 100644 --- a/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py +++ b/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -44,7 +44,7 @@ def execute(): where name=%s""", d.name) for d in journal_entries: - print d + print(d) # delete existing gle frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d) diff --git a/erpnext/patches/v6_4/make_image_thumbnail.py b/erpnext/patches/v6_4/make_image_thumbnail.py index 702148a8f6..3315acc896 100644 --- a/erpnext/patches/v6_4/make_image_thumbnail.py +++ b/erpnext/patches/v6_4/make_image_thumbnail.py @@ -1,3 +1,4 @@ +from __future__ import print_function import frappe def execute(): @@ -11,4 +12,4 @@ def execute(): if item_doc.thumbnail: item_doc.db_set("thumbnail", item_doc.thumbnail, update_modified=False) except Exception: - print "Unable to make thumbnail for {0}".format(item.website_image.encode("utf-8")) + print("Unable to make thumbnail for {0}".format(item.website_image.encode("utf-8"))) diff --git a/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py b/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py index e0268c42db..1319b53558 100644 --- a/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py +++ b/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -13,7 +13,7 @@ def execute(): and against_voucher=je.reference_name)""") for d in je_list: - print d + print(d) # delete existing gle frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d) diff --git a/erpnext/patches/v6_6/fix_website_image.py b/erpnext/patches/v6_6/fix_website_image.py index b3b4cab18a..cc3e2d852c 100644 --- a/erpnext/patches/v6_6/fix_website_image.py +++ b/erpnext/patches/v6_6/fix_website_image.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.utils import encode @@ -25,7 +25,7 @@ def execute(): try: file.validate_file() except IOError: - print encode(item.website_image), "does not exist" + print(encode(item.website_image), "does not exist") file.delete() item.db_set("website_image", None, update_modified=False) diff --git a/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py b/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py index 58da0594ea..2bc09714d8 100644 --- a/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py +++ b/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe, erpnext def execute(): @@ -35,11 +35,11 @@ def execute(): voucher.make_gl_entries() frappe.db.commit() except Exception as e: - print frappe.get_traceback() + print(frappe.get_traceback()) rejected.append([voucher_type, voucher_no]) frappe.db.rollback() - print rejected + print(rejected) def set_warehouse_for_stock_account(warehouse_account): for account in warehouse_account: diff --git a/erpnext/patches/v7_0/rename_salary_components.py b/erpnext/patches/v7_0/rename_salary_components.py index 4e9ceb2173..8409ca842d 100644 --- a/erpnext/patches/v7_0/rename_salary_components.py +++ b/erpnext/patches/v7_0/rename_salary_components.py @@ -81,7 +81,7 @@ def execute(): try: frappe.db.sql("""INSERT INTO `tabSalary Component` ({0}) SELECT {1} FROM `tab{2}`""" .format(target_cols, source_cols, doctype)) - except Exception, e: + except Exception as e: if e.args[0]==1062: pass 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_7/__init__.py b/erpnext/patches/v8_7/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/patches/v8_7/add_more_gst_fields.py b/erpnext/patches/v8_7/add_more_gst_fields.py new file mode 100644 index 0000000000..a1512ed9b7 --- /dev/null +++ b/erpnext/patches/v8_7/add_more_gst_fields.py @@ -0,0 +1,9 @@ +import frappe +from erpnext.regional.india.setup import make_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + make_custom_fields() \ No newline at end of file diff --git a/erpnext/patches/v8_7/fix_purchase_receipt_status.py b/erpnext/patches/v8_7/fix_purchase_receipt_status.py new file mode 100644 index 0000000000..f7037dd7df --- /dev/null +++ b/erpnext/patches/v8_7/fix_purchase_receipt_status.py @@ -0,0 +1,12 @@ +import frappe + +def execute(): + # there is no more status called "Submitted", there was an old issue that used + # to set it as Submitted, fixed in this commit + frappe.db.sql(""" + update + `tabPurchase Receipt` + set + status = 'To Bill' + where + status = 'Submitted'""") \ No newline at end of file diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 92ef72540a..010d4d9269 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -346,7 +346,21 @@ erpnext.buying.get_items_from_product_bundle = function(frm) { }, freeze: true, callback: function(r) { + const first_row_is_empty = function(child_table){ + if($.isArray(child_table) && child_table.length > 0) { + return !child_table[0].item_code; + } + return false; + }; + + const remove_empty_first_row = function(frm){ + if (first_row_is_empty(frm.doc.items)){ + frm.doc.items = frm.doc.items.splice(1); + } + }; + if(!r.exc && r.message) { + remove_empty_first_row(frm); for ( var i=0; i< r.message.length; i++ ) { var d = frm.add_child("items"); var item = r.message[i]; @@ -366,3 +380,4 @@ erpnext.buying.get_items_from_product_bundle = function(frm) { }); dialog.show(); } + diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index c653640de6..d042bb7220 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -62,7 +62,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.frm.doc.conversion_rate = flt(this.frm.doc.conversion_rate, (cur_frm) ? precision("conversion_rate") : 9); var conversion_rate_label = frappe.meta.get_label(this.frm.doc.doctype, "conversion_rate", this.frm.doc.name); - var company_currency = this.frm.doc.currency || this.get_company_currency(); + var company_currency = this.get_company_currency(); if(!this.frm.doc.conversion_rate) { if(this.frm.doc.currency == company_currency) { @@ -419,7 +419,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.frm.doc.currency, precision("rounded_total")); } if(frappe.meta.get_docfield(this.frm.doc.doctype, "base_rounded_total", this.frm.doc.name)) { - var company_currency = this.frm.doc.currency || this.get_company_currency(); + var company_currency = this.get_company_currency(); this.frm.doc.base_rounded_total = round_based_on_smallest_currency_fraction(this.frm.doc.base_grand_total, 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/regional/india/gst_state_code_data.json b/erpnext/regional/india/gst_state_code_data.json index 47af9b190a..6dab81d668 100644 --- a/erpnext/regional/india/gst_state_code_data.json +++ b/erpnext/regional/india/gst_state_code_data.json @@ -147,7 +147,7 @@ { "state_number": "22", "state_code": "CT", - "state_name": "Chattisgarh" + "state_name": "Chhattisgarh" }, { "state_number": "04", diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 7a7e937ea4..0c59ba003a 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -81,6 +81,47 @@ def add_print_formats(): def make_custom_fields(): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Data', options='item_code.gst_hsn_code', insert_after='description', print_hide=1) + invoice_gst_fields = [ + dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', + insert_after='select_print_heading', print_hide=1, collapsible=1), + dict(fieldname='invoice_copy', label='Invoice Copy', + fieldtype='Select', insert_after='gst_section', print_hide=1, allow_on_submit=1, + options='Original for Recipient\nDuplicate for Transporter\nDuplicate for Supplier\nTriplicate for Supplier'), + dict(fieldname='reverse_charge', label='Reverse Charge', + fieldtype='Select', insert_after='invoice_copy', print_hide=1, + options='Y\nN', default='N'), + dict(fieldname='gst_col_break', fieldtype='Column Break', insert_after='reverse_charge'), + dict(fieldname='invoice_type', label='Invoice Type', + fieldtype='Select', insert_after='reverse_charge', print_hide=1, + options='Regular\nSEZ\nExport\nDeemed Export', default='Regular'), + dict(fieldname='export_type', label='Export Type', + fieldtype='Select', insert_after='invoice_type', print_hide=1, + depends_on='eval:in_list(["SEZ", "Export", "Deemed Export"], doc.invoice_type)', + options='\nWith Payment of Tax\nWithout Payment of Tax'), + dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN', + fieldtype='Data', insert_after='export_type', print_hide=1) + ] + + purchase_invoice_gst_fields = [ + dict(fieldname='supplier_gstin', label='Supplier GSTIN', + fieldtype='Data', insert_after='supplier_address', + options='supplier_address.gstin', print_hide=1), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='shipping_address', + options='shipping_address.gstin', print_hide=1) + ] + + sales_invoice_gst_fields = [ + dict(fieldname='customer_gstin', label='Customer GSTIN', + fieldtype='Data', insert_after='shipping_address', + options='shipping_address_name.gstin', print_hide=1), + dict(fieldname='place_of_supply', label='Place of Supply', + fieldtype='Data', insert_after='customer_gstin', print_hide=1, + options='shipping_address_name.gst_state_number', read_only=1), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='company_address', + options='company_address.gstin', print_hide=1) + ] custom_fields = { 'Address': [ @@ -91,25 +132,9 @@ def make_custom_fields(): dict(fieldname='gst_state_number', label='GST State Number', fieldtype='Int', insert_after='gst_state', read_only=1), ], - 'Purchase Invoice': [ - dict(fieldname='supplier_gstin', label='Supplier GSTIN', - fieldtype='Data', insert_after='supplier_address', - options='supplier_address.gstin', print_hide=1), - dict(fieldname='company_gstin', label='Company GSTIN', - fieldtype='Data', insert_after='shipping_address', - options='shipping_address.gstin', print_hide=1), - ], - 'Sales Invoice': [ - dict(fieldname='customer_gstin', label='Customer GSTIN', - fieldtype='Data', insert_after='shipping_address', - options='shipping_address_name.gstin', print_hide=1), - dict(fieldname='company_gstin', label='Company GSTIN', - fieldtype='Data', insert_after='company_address', - options='company_address.gstin', print_hide=1), - dict(fieldname='invoice_copy', label='Invoice Copy', - fieldtype='Select', insert_after='select_print_heading', print_hide=1, allow_on_submit=1, - options='Original for Recipient\nDuplicate for Transporter\nDuplicate for Supplier\nTriplicate for Supplier') - ], + 'Purchase Invoice': purchase_invoice_gst_fields + invoice_gst_fields, + 'Sales Invoice': sales_invoice_gst_fields + invoice_gst_fields, + "Delivery Note": sales_invoice_gst_fields, 'Item': [ dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Link', options='GST HSN Code', insert_after='item_group'), diff --git a/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py b/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py index d88febcf33..1a54cc3ff2 100644 --- a/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py +++ b/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py @@ -9,9 +9,19 @@ def execute(filters=None): return _execute(filters, additional_table_columns=[ dict(fieldtype='Data', label='Supplier GSTIN', width=120), dict(fieldtype='Data', label='Company GSTIN', width=120), - dict(fieldtype='Data', label='HSN Code', width=120) + dict(fieldtype='Data', label='Reverse Charge', width=120), + dict(fieldtype='Data', label='Invoice Type', width=120), + dict(fieldtype='Data', label='Export Type', width=120), + dict(fieldtype='Data', label='E-Commerce GSTIN', width=130), + dict(fieldtype='Data', label='HSN Code', width=120), + dict(fieldtype='Data', label='Supplier Invoice No', width=120) ], additional_query_columns=[ 'supplier_gstin', 'company_gstin', - 'gst_hsn_code' + 'reverse_charge', + 'invoice_type', + 'export_type', + 'ecommerce_gstin', + 'gst_hsn_code', + 'bill_no' ]) diff --git a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py index 14ddff3753..40bbae8b55 100644 --- a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py +++ b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py @@ -9,9 +9,19 @@ def execute(filters=None): return _execute(filters, additional_table_columns=[ dict(fieldtype='Data', label='Customer GSTIN', width=120), dict(fieldtype='Data', label='Company GSTIN', width=120), + dict(fieldtype='Data', label='Place of Supply', width=120), + dict(fieldtype='Data', label='Reverse Charge', width=120), + dict(fieldtype='Data', label='Invoice Type', width=120), + dict(fieldtype='Data', label='Export Type', width=120), + dict(fieldtype='Data', label='E-Commerce GSTIN', width=130), dict(fieldtype='Data', label='HSN Code', width=120) ], additional_query_columns=[ 'customer_gstin', 'company_gstin', + 'place_of_supply', + 'reverse_charge', + 'invoice_type', + 'export_type', + 'ecommerce_gstin', 'gst_hsn_code' ]) diff --git a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py index 59687d0086..8d479a96cd 100644 --- a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py +++ b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py @@ -8,9 +8,17 @@ from erpnext.accounts.report.purchase_register.purchase_register import _execute def execute(filters=None): return _execute(filters, additional_table_columns=[ dict(fieldtype='Data', label='Supplier GSTIN', width=120), - dict(fieldtype='Data', label='Company GSTIN', width=120) + dict(fieldtype='Data', label='Company GSTIN', width=120), + dict(fieldtype='Data', label='Reverse Charge', width=120), + dict(fieldtype='Data', label='Invoice Type', width=120), + dict(fieldtype='Data', label='Export Type', width=120), + dict(fieldtype='Data', label='E-Commerce GSTIN', width=130) ], additional_query_columns=[ 'supplier_gstin', - 'company_gstin' + 'company_gstin', + 'reverse_charge', + 'invoice_type', + 'export_type', + 'ecommerce_gstin' ]) diff --git a/erpnext/regional/report/gst_sales_register/gst_sales_register.py b/erpnext/regional/report/gst_sales_register/gst_sales_register.py index 440594abb0..7f6f809276 100644 --- a/erpnext/regional/report/gst_sales_register/gst_sales_register.py +++ b/erpnext/regional/report/gst_sales_register/gst_sales_register.py @@ -8,8 +8,18 @@ from erpnext.accounts.report.sales_register.sales_register import _execute def execute(filters=None): return _execute(filters, additional_table_columns=[ dict(fieldtype='Data', label='Customer GSTIN', width=120), - dict(fieldtype='Data', label='Company GSTIN', width=120) + dict(fieldtype='Data', label='Company GSTIN', width=120), + dict(fieldtype='Data', label='Place of Supply', width=120), + dict(fieldtype='Data', label='Reverse Charge', width=120), + dict(fieldtype='Data', label='Invoice Type', width=120), + dict(fieldtype='Data', label='Export Type', width=120), + dict(fieldtype='Data', label='E-Commerce GSTIN', width=130) ], additional_query_columns=[ 'customer_gstin', - 'company_gstin' + 'company_gstin', + 'place_of_supply', + 'reverse_charge', + 'invoice_type', + 'export_type', + 'ecommerce_gstin' ]) diff --git a/erpnext/schools/api.py b/erpnext/schools/api.py index c613c8c5a6..ff2da07a30 100644 --- a/erpnext/schools/api.py +++ b/erpnext/schools/api.py @@ -63,7 +63,7 @@ def mark_attendance(students_present, students_absent, course_schedule=None, stu :param student_group: Student Group. :param date: Date. """ - + present = json.loads(students_present) absent = json.loads(students_absent) diff --git a/erpnext/schools/doctype/assessment_group/assessment_group.json b/erpnext/schools/doctype/assessment_group/assessment_group.json index 7eeab20734..8c93bb2ec5 100644 --- a/erpnext/schools/doctype/assessment_group/assessment_group.json +++ b/erpnext/schools/doctype/assessment_group/assessment_group.json @@ -201,7 +201,7 @@ "fieldname": "old_parent", "fieldtype": "Link", "hidden": 0, - "ignore_user_permissions": 0, + "ignore_user_permissions": 1, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, @@ -234,7 +234,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-06-30 08:21:46.411222", + "modified": "2017-08-21 02:12:33.177318", "modified_by": "Administrator", "module": "Schools", "name": "Assessment Group", diff --git a/erpnext/schools/doctype/course_schedule/test_course_schedule.py b/erpnext/schools/doctype/course_schedule/test_course_schedule.py index 6a3456fefc..f1313820a5 100644 --- a/erpnext/schools/doctype/course_schedule/test_course_schedule.py +++ b/erpnext/schools/doctype/course_schedule/test_course_schedule.py @@ -17,7 +17,7 @@ class TestCourseSchedule(unittest.TestCase): cs1 = make_course_schedule_test_record(simulate= True) cs2 = make_course_schedule_test_record(schedule_date=cs1.schedule_date, from_time= cs1.from_time, - to_time= cs1.to_time, instructor="_T-Instructor-00002", room="RM0002", do_not_save= 1) + to_time= cs1.to_time, instructor="_Test Instructor 2", room="RM0002", do_not_save= 1) self.assertRaises(OverlapError, cs2.save) def test_instructor_conflict(self): @@ -31,14 +31,14 @@ class TestCourseSchedule(unittest.TestCase): cs1 = make_course_schedule_test_record(simulate= True) cs2 = make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, - student_group="Course-TC101-2014-2015 (_Test Academic Term)", instructor="_T-Instructor-00002", do_not_save= 1) + student_group="Course-TC101-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", do_not_save= 1) self.assertRaises(OverlapError, cs2.save) def test_no_conflict(self): cs1 = make_course_schedule_test_record(simulate= True) make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, - student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_T-Instructor-00002", room="RM0002") + student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", room="RM0002") def make_course_schedule_test_record(**args): args = frappe._dict(args) @@ -46,7 +46,7 @@ def make_course_schedule_test_record(**args): course_schedule = frappe.new_doc("Course Schedule") course_schedule.student_group = args.student_group or "Course-TC101-2014-2015 (_Test Academic Term)" course_schedule.course = args.course or "TC101" - course_schedule.instructor = args.instructor or "_T-Instructor-00001" + course_schedule.instructor = args.instructor or "_Test Instructor" course_schedule.room = args.room or "RM0001" course_schedule.schedule_date = args.schedule_date or today() diff --git a/erpnext/schools/doctype/instructor/instructor.json b/erpnext/schools/doctype/instructor/instructor.json index a98fe693e7..cd0b4f10f8 100644 --- a/erpnext/schools/doctype/instructor/instructor.json +++ b/erpnext/schools/doctype/instructor/instructor.json @@ -208,7 +208,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-06-30 08:21:49.055531", + "modified": "2017-08-25 01:03:14.602994", "modified_by": "Administrator", "module": "Schools", "name": "Instructor", diff --git a/erpnext/schools/doctype/instructor/instructor.py b/erpnext/schools/doctype/instructor/instructor.py index 4331b91610..ba179f76aa 100644 --- a/erpnext/schools/doctype/instructor/instructor.py +++ b/erpnext/schools/doctype/instructor/instructor.py @@ -4,7 +4,21 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document +from frappe.model.naming import make_autoname class Instructor(Document): - pass + def autoname(self): + naming_method = frappe.db.get_value("School Settings", None, "instructor_created_by") + if not naming_method: + frappe.throw(_("Please setup Instructor Naming System in School > School Settings")) + else: + if naming_method == 'Naming Series': + self.name = make_autoname(self.naming_series + '.####') + elif naming_method == 'Employee Number': + if not self.employee: + frappe.throw("Please select Employee") + self.name = self.employee + elif naming_method == 'Full Name': + self.name = self.instructor_name diff --git a/erpnext/schools/doctype/school_settings/school_settings.json b/erpnext/schools/doctype/school_settings/school_settings.json index 3b5aae69e2..b6d9890ebd 100644 --- a/erpnext/schools/doctype/school_settings/school_settings.json +++ b/erpnext/schools/doctype/school_settings/school_settings.json @@ -194,6 +194,67 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_7", + "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, + "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, + "default": "Full Name", + "fieldname": "instructor_created_by", + "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": "Instructor Records to be created by", + "length": 0, + "no_copy": 0, + "options": "Full Name\nNaming Series\nEmployee Number", + "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, @@ -206,7 +267,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-06-30 08:21:50.339169", + "modified": "2017-08-25 02:36:48.744456", "modified_by": "Administrator", "module": "Schools", "name": "School Settings", diff --git a/erpnext/schools/doctype/school_settings/school_settings.py b/erpnext/schools/doctype/school_settings/school_settings.py index 999014ad80..88235cfc34 100644 --- a/erpnext/schools/doctype/school_settings/school_settings.py +++ b/erpnext/schools/doctype/school_settings/school_settings.py @@ -26,3 +26,10 @@ class SchoolSettings(Document): def get_defaults(self): return frappe.defaults.get_defaults() + + def validate(self): + from frappe.custom.doctype.property_setter.property_setter import make_property_setter + if self.get('instructor_created_by')=='Naming Series': + make_property_setter('Instructor', "naming_series", "hidden", 0, "Check") + else: + make_property_setter('Instructor', "naming_series", "hidden", 1, "Check") diff --git a/erpnext/schools/doctype/student/student.js b/erpnext/schools/doctype/student/student.js index d3d248b2cb..cadf272a1f 100644 --- a/erpnext/schools/doctype/student/student.js +++ b/erpnext/schools/doctype/student/student.js @@ -3,6 +3,11 @@ frappe.ui.form.on('Student', { setup: function(frm) { + frm.add_fetch("guardian", "guardian_name", "guardian_name"); + frm.add_fetch("student", "title", "full_name"); + frm.add_fetch("student", "gender", "gender"); + frm.add_fetch("student", "date_of_birth", "date_of_birth"); + frm.set_query("student", "siblings", function(doc, cdt, cdn) { return { "filters": { @@ -11,18 +16,4 @@ frappe.ui.form.on('Student', { }; }) } -}); - -frappe.ui.form.on("Student Guardian", { - guardian: function(frm) { - frm.add_fetch("guardian", "guardian_name", "guardian_name"); - } -}); - -frappe.ui.form.on('Student Sibling', { - student: function(frm) { - frm.add_fetch("student", "title", "full_name"); - frm.add_fetch("student", "gender", "gender"); - frm.add_fetch("student", "date_of_birth", "date_of_birth"); - } -}); +}); \ No newline at end of file diff --git a/erpnext/schools/doctype/student_applicant/student_applicant.js b/erpnext/schools/doctype/student_applicant/student_applicant.js index 9b08ee5f6a..40a6ac3a3d 100644 --- a/erpnext/schools/doctype/student_applicant/student_applicant.js +++ b/erpnext/schools/doctype/student_applicant/student_applicant.js @@ -39,15 +39,12 @@ frappe.ui.form.on("Student Applicant", { method: "erpnext.schools.api.enroll_student", frm: frm }) - } -}); + }, - -frappe.ui.form.on('Student Sibling', { - student: function(frm) { + setup: function(frm) { + frm.add_fetch("guardian", "guardian_name", "guardian_name"); frm.add_fetch("student", "title", "full_name"); frm.add_fetch("student", "gender", "gender"); frm.add_fetch("student", "date_of_birth", "date_of_birth"); } -}); - +}); \ No newline at end of file diff --git a/erpnext/schools/doctype/student_applicant/student_applicant.json b/erpnext/schools/doctype/student_applicant/student_applicant.json index 271f873195..578f84ceff 100644 --- a/erpnext/schools/doctype/student_applicant/student_applicant.json +++ b/erpnext/schools/doctype/student_applicant/student_applicant.json @@ -881,7 +881,7 @@ "in_standard_filter": 0, "label": "Guardians", "length": 0, - "no_copy": 1, + "no_copy": 0, "options": "Student Guardian", "permlevel": 0, "precision": "", @@ -1058,7 +1058,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-06-30 08:21:50.917086", + "modified": "2017-08-23 06:12:36.996978", "modified_by": "Administrator", "module": "Schools", "name": "Student Applicant", diff --git a/erpnext/schools/doctype/student_applicant/student_applicant.py b/erpnext/schools/doctype/student_applicant/student_applicant.py index 047c7027c6..081fa065db 100644 --- a/erpnext/schools/doctype/student_applicant/student_applicant.py +++ b/erpnext/schools/doctype/student_applicant/student_applicant.py @@ -2,7 +2,7 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe import _ from frappe.model.document import Document @@ -13,7 +13,7 @@ class StudentApplicant(Document): if self.student_admission: naming_series = frappe.db.get_value('Student Admission', self.student_admission, 'naming_series_for_student_applicant') - print naming_series + print(naming_series) if naming_series: self.naming_series = naming_series 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 5a6f6df99b..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 diff --git a/erpnext/schools/doctype/student_group/test_student_group.py b/erpnext/schools/doctype/student_group/test_student_group.py index 18a6b14f50..e358c27bf6 100644 --- a/erpnext/schools/doctype/student_group/test_student_group.py +++ b/erpnext/schools/doctype/student_group/test_student_group.py @@ -8,20 +8,20 @@ import unittest from frappe.utils.make_random import get_random class TestStudentGroup(unittest.TestCase): - def test_student_roll_no(self): - doc = frappe.get_doc({ - "doctype": "Student Group", - "student_group_name": "_Test Student Group R", + def test_student_roll_no(self): + doc = frappe.get_doc({ + "doctype": "Student Group", + "student_group_name": "_Test Student Group R", "group_based_on": "Activity" - }).insert() + }).insert() - student_list = [] - while len(student_list) < 3: - s = get_random("Student") - if s not in student_list: - student_list.append(s) + student_list = [] + while len(student_list) < 3: + s = get_random("Student") + if s not in student_list: + student_list.append(s) - doc.extend("students", [{"student":d} for d in student_list]) - doc.save() - self.assertEquals(max([d.group_roll_number for d in doc.students]), 3) + doc.extend("students", [{"student":d} for d in student_list]) + doc.save() + self.assertEquals(max([d.group_roll_number for d in doc.students]), 3) diff --git a/erpnext/schools/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py b/erpnext/schools/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py index f191022f20..641b78372c 100644 --- a/erpnext/schools/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py +++ b/erpnext/schools/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py @@ -82,7 +82,7 @@ def get_guardian_map(student_list): select parent, guardian, guardian_name, relation from `tabStudent Guardian` where parent in (%s)''' % ', '.join(['%s']*len(student_list)), tuple(student_list), as_dict=1) - guardian_list = list(set([g.guardian for g in guardian_details])) + guardian_list = list(set([g.guardian for g in guardian_details])) or [''] guardian_mobile_no = dict(frappe.db.sql("""select name, mobile_number from `tabGuardian` where name in (%s)""" % ", ".join(['%s']*len(guardian_list)), tuple(guardian_list))) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d797632902..c12cd449b8 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -67,7 +67,7 @@ class Customer(TransactionBase): '''If Customer created from Lead, update lead status to "Converted" update Customer link in Quotation, Opportunity''' if self.lead_name: - frappe.db.set_value('Lead', self.lead_name, 'status', 'Converted', update_modified=False) + frappe.db.set_value('Lead', self.lead_name, 'status', 'Converted', update_modified=False) for doctype in ('Opportunity', 'Quotation'): for d in frappe.get_all(doctype, {'lead': self.lead_name}): diff --git a/erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js b/erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js new file mode 100644 index 0000000000..26a099e4d6 --- /dev/null +++ b/erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js @@ -0,0 +1,41 @@ +QUnit.module('Quotation'); + +QUnit.test("test quotation submit cancel amend", function(assert) { + assert.expect(2); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Quotation', [ + {customer: 'Test Customer 1'}, + {items: [ + [ + {'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)}, + {'qty': 5}, + {'item_code': 'Test Product 1'} + ] + ]}, + {customer_address: 'Test1-Billing'}, + {shipping_address_name: 'Test1-Shipping'}, + {contact_person: 'Contact 1-Test Customer 1'} + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + // get uom details + assert.ok(cur_frm.doc.grand_total== 500, "Grand total correct "); + + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(1), + () => frappe.tests.click_button('Close'), + () => frappe.tests.click_button('Cancel'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.5), + () => frappe.tests.click_button('Amend'), + () => cur_frm.save(), + () => done() + ]); +}); diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 80dc4f23ec..901e236bd2 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -179,7 +179,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( doc: this.frm.doc, method: 'get_production_order_items', callback: function(r) { - if(!r.message.every(function(d) { return !!d.bom })) { + if(!r.message) { frappe.msgprint({ title: __('Production Order not created'), message: __('No Items with Bill of Materials to Manufacture'), diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index c6dbd7043b..5f904c2e3d 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -51,9 +51,9 @@ class SalesOrder(SellingController): # validate p.o date v/s delivery date if self.po_date: for d in self.get("items"): - if d.delivery_date and getdate(self.po_date) > getdate(d.delivery_date): - frappe.throw(_("Row #{0}: Expected Delivery Date cannot be before Purchase Order Date") - .format(d.idx)) + if d.delivery_date and getdate(self.po_date) > getdate(d.delivery_date): + frappe.throw(_("Row #{0}: Expected Delivery Date cannot be before Purchase Order Date") + .format(d.idx)) if self.po_no and self.customer: so = frappe.db.sql("select name from `tabSales Order` \ @@ -330,18 +330,19 @@ class SalesOrder(SellingController): def get_production_order_items(self): '''Returns items with BOM that already do not have a linked production order''' items = [] - for i in self.packed_items or self.items: - bom = frappe.get_all('BOM', dict(item=i.item_code, is_active=True), - order_by='is_default desc') - bom = bom[0].name if bom else None - stock_qty = i.qty if self.packed_items else i.stock_qty - items.append(dict( - item_code= i.item_code, - bom = bom, - warehouse = i.warehouse, - pending_qty= stock_qty - flt(frappe.db.sql('''select sum(qty) from `tabProduction Order` - where production_item=%s and sales_order=%s''', (i.item_code, self.name))[0][0]) - )) + + for table in [self.items, self.packed_items]: + for i in table: + bom = get_default_bom_item(i.item_code) + if bom: + stock_qty = i.qty if i.doctype == 'Packed Item' else i.stock_qty + items.append(dict( + item_code= i.item_code, + bom = bom, + warehouse = i.warehouse, + pending_qty= stock_qty - flt(frappe.db.sql('''select sum(qty) from `tabProduction Order` + where production_item=%s and sales_order=%s''', (i.item_code, self.name))[0][0]) + )) return items @@ -465,6 +466,11 @@ def make_delivery_note(source_name, target_doc=None): target.ignore_pricing_rule = 1 target.run_method("set_missing_values") target.run_method("calculate_taxes_and_totals") + + # set company address + target.update(get_company_address(target.company)) + if target.company_address: + target.update(get_fetch_values("Delivery Note", 'company_address', target.company_address)) def update_item(source, target, source_parent): target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate) @@ -774,3 +780,10 @@ def make_production_orders(items, sales_order, company, project=None): def update_status(status, name): so = frappe.get_doc("Sales Order", name) so.update_status(status) + +def get_default_bom_item(item_code): + bom = frappe.get_all('BOM', dict(item=item_code, is_active=True), + order_by='is_default desc') + bom = bom[0].name if bom else None + + return bom diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 6a84d0e09b..e9671c8711 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -326,6 +326,23 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ this.calculate_taxes_and_totals(); cur_frm.refresh_fields(); } + }, + + company_address: function() { + var me = this; + if(this.frm.doc.company_address) { + frappe.call({ + method: "frappe.contacts.doctype.address.address.get_address_display", + args: {"address_dict": this.frm.doc.company_address }, + callback: function(r) { + if(r.message) { + me.frm.set_value("company_address_display", r.message) + } + } + }) + } else { + this.frm.set_value("company_address_display", ""); + } } }); diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 29630eeb84..b945ee4104 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, os from frappe import _ -from frappe.utils import cint +from frappe.utils import cint, today, formatdate import frappe.defaults @@ -312,39 +312,35 @@ def get_name_with_abbr(name, company): return " - ".join(parts) def update_company_current_month_sales(company): - from frappe.utils import today, formatdate current_month_year = formatdate(today(), "MM-yyyy") - results = frappe.db.sql((''' + results = frappe.db.sql(''' select sum(base_grand_total) as total, date_format(posting_date, '%m-%Y') as month_year from `tabSales Invoice` where - date_format(posting_date, '%m-%Y')="{0}" and - company = "{1}" + date_format(posting_date, '%m-%Y')="{0}" + and docstatus = 1 + and company = "{1}" group by - month_year; - ''').format(current_month_year, frappe.db.escape(company)), as_dict = True) + month_year + '''.format(current_month_year, frappe.db.escape(company)), as_dict = True) monthly_total = results[0]['total'] if len(results) > 0 else 0 - frappe.db.sql((''' - update tabCompany set total_monthly_sales = %s where name=%s - '''), (monthly_total, frappe.db.escape(company))) + frappe.db.set_value("Company", company, "total_monthly_sales", monthly_total) frappe.db.commit() - def update_company_monthly_sales(company): '''Cache past year monthly sales of every company based on sales invoices''' from frappe.utils.goal import get_monthly_results import json - filter_str = "company = '{0}' and status != 'Draft'".format(frappe.db.escape(company)) - month_to_value_dict = get_monthly_results("Sales Invoice", "base_grand_total", "posting_date", filter_str, "sum") + filter_str = "company = '{0}' and status != 'Draft' and docstatus=1".format(frappe.db.escape(company)) + month_to_value_dict = get_monthly_results("Sales Invoice", "base_grand_total", + "posting_date", filter_str, "sum") - frappe.db.sql((''' - update tabCompany set sales_monthly_history = %s where name=%s - '''), (json.dumps(month_to_value_dict), frappe.db.escape(company))) + frappe.db.set_value("Company", company, "sales_monthly_history", json.dumps(month_to_value_dict)) frappe.db.commit() def cache_companies_monthly_sales_history(): diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index f1fb0cf96e..d975a9ee8d 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -27,6 +27,10 @@ def delete_company_transactions(company_name): "Purchase Taxes and Charges Template", "POS Profile", 'BOM'): delete_for_doctype(doctype, company_name) + # reset company values + doc.total_monthly_sales = 0 + doc.sales_monthly_history = None + doc.save() # Clear notification counts clear_notifications() 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/doctype/customer_group/customer_group.py b/erpnext/setup/doctype/customer_group/customer_group.py index b25095b1b1..0f1ee81863 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.py +++ b/erpnext/setup/doctype/customer_group/customer_group.py @@ -18,3 +18,10 @@ class CustomerGroup(NestedSet): def validate_name_with_customer(self): if frappe.db.exists("Customer", self.name): frappe.msgprint(_("An Customer exists with same name"), raise_exception=1) + +def get_parent_customer_groups(customer_group): + lft, rgt = frappe.db.get_value("Customer Group", customer_group, ['lft', 'rgt']) + + return frappe.db.sql("""select name from `tabCustomer Group` + where lft <= %s and rgt >= %s + order by lft asc""", (lft, rgt), as_dict=True) \ No newline at end of file diff --git a/erpnext/setup/doctype/item_group/test_item_group.py b/erpnext/setup/doctype/item_group/test_item_group.py index bc88132b7f..c487c7239b 100644 --- a/erpnext/setup/doctype/item_group/test_item_group.py +++ b/erpnext/setup/doctype/item_group/test_item_group.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import unittest import frappe from frappe.utils.nestedset import NestedSetRecursionError, NestedSetMultipleRootsError, \ @@ -112,7 +112,7 @@ class TestItem(unittest.TestCase): def print_tree(self): import json - print json.dumps(frappe.db.sql("select name, lft, rgt from `tabItem Group` order by lft"), indent=1) + print(json.dumps(frappe.db.sql("select name, lft, rgt from `tabItem Group` order by lft"), indent=1)) def test_move_leaf_into_another_group(self): # before move diff --git a/erpnext/setup/doctype/naming_series/naming_series.js b/erpnext/setup/doctype/naming_series/naming_series.js index e2584bf3aa..7c76d9ca4b 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.js +++ b/erpnext/setup/doctype/naming_series/naming_series.js @@ -1,47 +1,55 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt -cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) { - cur_frm.disable_save(); - cur_frm.toolbar.print_icon.addClass("hide"); - return cur_frm.call({ - doc: cur_frm.doc, - method: 'get_transactions', - callback: function(r) { - cur_frm.cscript.update_selects(r); - cur_frm.cscript.select_doc_for_series(doc, cdt, cdn); - } - }); -} +frappe.ui.form.on("Naming Series", { + onload: function(frm) { + frm.disable_save(); + frm.events.get_doc_and_prefix(frm); + }, -cur_frm.cscript.update_selects = function(r) { - set_field_options('select_doc_for_series', r.message.transactions); - set_field_options('prefix', r.message.prefixes); -} + get_doc_and_prefix: function(frm) { + frappe.call({ + method: "get_transactions", + doc: frm.doc, + callback: function(r) { + frm.set_df_property("select_doc_for_series", "options", r.message.transactions); + frm.set_df_property("prefix", "options", r.message.prefixes); + } + }); + }, -cur_frm.cscript.select_doc_for_series = function(doc, cdt, cdn) { - cur_frm.set_value('user_must_always_select', 0); - cur_frm.toggle_display(['help_html','set_options', 'user_must_always_select', 'update'], - doc.select_doc_for_series); + select_doc_for_series: function(frm) { + frm.set_value("user_must_always_select", 0); + frappe.call({ + method: "get_options", + doc: frm.doc, + callback: function(r) { + frm.set_value("set_options", r.message); + if(r.message && r.message.split('\n')[0]=='') + frm.set_value('user_must_always_select', 1); + frm.refresh(); + } + }); + }, - var callback = function(r, rt){ - locals[cdt][cdn].set_options = r.message; - refresh_field('set_options'); - if(r.message && r.message.split('\n')[0]=='') - cur_frm.set_value('user_must_always_select', 1); + prefix: function(frm) { + frappe.call({ + method: "get_current", + doc: frm.doc, + callback: function(r) { + frm.refresh_field("current_value"); + } + }); + }, + + update: function(frm) { + frappe.call({ + method: "update_series", + doc: frm.doc, + callback: function(r) { + frm.events.get_doc_and_prefix(frm); + } + }); } - - if(doc.select_doc_for_series) - return $c_obj(doc,'get_options','',callback); -} - -cur_frm.cscript.update = function() { - return cur_frm.call_server('update_series', '', cur_frm.cscript.update_selects); -} - -cur_frm.cscript.prefix = function(doc, dt, dn) { - return cur_frm.call_server('get_current', '', function(r) { - refresh_field('current_value'); - }); -} +}); diff --git a/erpnext/setup/doctype/naming_series/naming_series.json b/erpnext/setup/doctype/naming_series/naming_series.json index 0daf1a146c..f936dcf3c9 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.json +++ b/erpnext/setup/doctype/naming_series/naming_series.json @@ -1,29 +1,40 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, + "beta": 0, "creation": "2013-01-25 11:35:08", "custom": 0, "description": "Set prefix for numbering series on your transactions", "docstatus": 0, "doctype": "DocType", + "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Set prefix for numbering series on your transactions", "fieldname": "setup_series", "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": "Setup Series", + "length": 0, "no_copy": 0, "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, @@ -31,20 +42,28 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "select_doc_for_series", "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": "Select Transaction", + "length": 0, "no_copy": 0, "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, @@ -52,21 +71,30 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "select_doc_for_series", "fieldname": "help_html", "fieldtype": "HTML", "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": "Help HTML", + "length": 0, "no_copy": 0, "options": "
\nEdit list of Series in the box below. Rules:\n\nExamples:
\nINV-
\nINV-10-
\nINVK-
\nINV-.####
\n
", "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, @@ -74,20 +102,29 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "select_doc_for_series", "fieldname": "set_options", "fieldtype": "Text", "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": "Series List for this Transaction", + "length": 0, "no_copy": 0, "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, @@ -95,21 +132,30 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "select_doc_for_series", "description": "Check this if you want to force the user to select a series before saving. There will be no default if you check this.", "fieldname": "user_must_always_select", "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": "User must always select", + "length": 0, "no_copy": 0, "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, @@ -117,20 +163,30 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "select_doc_for_series", "fieldname": "update", "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", + "length": 0, "no_copy": 0, + "options": "", "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, @@ -138,21 +194,29 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Change the starting / current sequence number of an existing series.", "fieldname": "update_series", "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 Series", + "length": 0, "no_copy": 0, "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, @@ -160,20 +224,28 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "prefix", "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": "Prefix", + "length": 0, "no_copy": 0, "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, @@ -181,21 +253,29 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "This is the number of the last created transaction with this prefix", "fieldname": "current_value", "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": "Current Value", + "length": 0, "no_copy": 0, "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, @@ -203,21 +283,29 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "update_series_start", "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 Series Number", + "length": 0, "no_copy": 0, "options": "update_series_start", "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, @@ -225,16 +313,18 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 1, "icon": "fa fa-sort-by-order", "idx": 1, + "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-02-05 05:11:41.473793", + "max_attachments": 0, + "modified": "2017-08-17 03:41:37.685910", "modified_by": "Administrator", "module": "Setup", "name": "Naming Series", @@ -261,6 +351,10 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 1, - "read_only_onload": 0 + "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/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index 536b72f6a9..45345846bc 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -21,7 +21,6 @@ class NamingSeries(Document): where fieldname='naming_series'"""))) doctypes = list(set(get_doctypes_with_read()) | set(doctypes)) - prefixes = "" for d in doctypes: options = "" @@ -34,9 +33,8 @@ class NamingSeries(Document): if options: prefixes = prefixes + "\n" + options - prefixes.replace("\n\n", "\n") - prefixes = "\n".join(sorted(prefixes.split())) + prefixes = "\n".join(sorted(prefixes.split("\n"))) return { "transactions": "\n".join([''] + sorted(doctypes)), @@ -112,9 +110,7 @@ class NamingSeries(Document): where dt.name = df.dt and df.fieldname='naming_series' and dt.name != %s""", self.select_doc_for_series) )) - sr = [[frappe.get_meta(p).get_field("naming_series").options, p] - for p in parent] - + sr = [[frappe.get_meta(p).get_field("naming_series").options, p] for p in parent] dt = frappe.get_doc("DocType", self.select_doc_for_series) options = self.scrub_options_list(self.set_options.split("\n")) for series in options: diff --git a/erpnext/setup/doctype/naming_series/test_naming_series.js b/erpnext/setup/doctype/naming_series/test_naming_series.js new file mode 100644 index 0000000000..22b664b2e6 --- /dev/null +++ b/erpnext/setup/doctype/naming_series/test_naming_series.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: Naming Series", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Naming Series + () => frappe.tests.make('Naming Series', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 7b71675f75..9bf15cee64 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe import _ @@ -19,10 +19,10 @@ def after_install(): def check_setup_wizard_not_completed(): if frappe.db.get_default('desktop:home_page') == 'desktop': - print - print "ERPNext can only be installed on a fresh site where the setup wizard is not completed" - print "You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall" - print + print() + print("ERPNext can only be installed on a fresh site where the setup wizard is not completed") + print("You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall") + print() return False def set_single_defaults(): 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/batch/test_batch.js b/erpnext/stock/doctype/batch/test_batch.js new file mode 100644 index 0000000000..af7f50ff91 --- /dev/null +++ b/erpnext/stock/doctype/batch/test_batch.js @@ -0,0 +1,23 @@ +QUnit.module('Stock'); + +QUnit.test("test Batch", function(assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Batch', [ + {batch_id:'TEST-BATCH-001'}, + {item:'Test Product 4'}, + {expiry_date:frappe.datetime.add_days(frappe.datetime.now_date(), 2)}, + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.batch_id=='TEST-BATCH-001', "Batch Id correct"); + }, + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 345750f04a..41f8b8493e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -860,22 +860,22 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "", - "fieldname": "customer_group", + "fieldname": "company_address", "fieldtype": "Link", - "hidden": 1, + "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": "Customer Group", + "label": "Company Address Name", "length": 0, "no_copy": 0, - "options": "Customer Group", + "options": "Address", "permlevel": 0, - "print_hide": 1, + "precision": "", + "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, @@ -891,9 +891,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "", - "fieldname": "territory", - "fieldtype": "Link", + "fieldname": "company_address_display", + "fieldtype": "Small Text", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -901,12 +900,12 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Territory", + "label": "Company Address", "length": 0, "no_copy": 0, - "options": "Territory", "permlevel": 0, - "print_hide": 1, + "precision": "", + "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, @@ -2757,6 +2756,68 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "customer_group", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Customer Group", + "length": 0, + "no_copy": 0, + "options": "Customer Group", + "permlevel": 0, + "print_hide": 1, + "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": "", + "fieldname": "territory", + "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": "Territory", + "length": 0, + "no_copy": 0, + "options": "Territory", + "permlevel": 0, + "print_hide": 1, + "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, @@ -3052,7 +3113,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, - "in_standard_filter": 1, + "in_standard_filter": 0, "label": "Installation Status", "length": 0, "no_copy": 0, @@ -3426,7 +3487,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-07-19 13:48:09.630820", + "modified": "2017-08-23 13:25:34.182268", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 82beff8782..f5a99afbd2 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -92,9 +92,9 @@ class DeliveryNote(SellingController): def so_required(self): """check in manage account if sales order required or not""" if frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes': - for d in self.get('items'): - if not d.against_sales_order: - frappe.throw(_("Sales Order required for Item {0}").format(d.item_code)) + for d in self.get('items'): + if not d.against_sales_order: + frappe.throw(_("Sales Order required for Item {0}").format(d.item_code)) def validate(self): self.validate_posting_time() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 0444369ff3..2d089c4419 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -84,9 +84,9 @@ class PurchaseReceipt(BuyingController): def po_required(self): if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes': - for d in self.get('items'): - if not d.purchase_order: - frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) + for d in self.get('items'): + if not d.purchase_order: + frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) def get_already_received_qty(self, po, po_detail): qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item` @@ -115,9 +115,6 @@ class PurchaseReceipt(BuyingController): frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total) - # Set status as Submitted - frappe.db.set(self, 'status', 'Submitted') - self.update_prevdoc_status() if self.per_billed < 100: self.update_billing_status() @@ -152,8 +149,6 @@ class PurchaseReceipt(BuyingController): if submitted: frappe.throw(_("Purchase Invoice {0} is already submitted").format(submitted[0][0])) - frappe.db.set(self,'status','Cancelled') - self.update_prevdoc_status() self.update_billing_status() 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/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d8cd27174d..6af8c09568 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -6,9 +6,10 @@ from __future__ import unicode_literals import unittest import frappe, erpnext import frappe.defaults -from frappe.utils import cint, flt, cstr, today +from frappe.utils import cint, flt, cstr, today, random_string from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext import set_perpetual_inventory +from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError from erpnext.accounts.doctype.account.test_account import get_inventory_account class TestPurchaseReceipt(unittest.TestCase): @@ -251,6 +252,27 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(pr2.per_billed, 80) self.assertEqual(pr2.status, "To Bill") + def test_not_accept_duplicate_serial_no(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + item_code = frappe.db.get_value('Item', {'has_serial_no': 1}) + if not item_code: + item = make_item("Test Serial Item 1", dict(has_serial_no = 1)) + item_code = item.name + + serial_no = random_string(5) + make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no) + create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no) + + pr = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True) + self.assertRaises(SerialNoDuplicateError, pr.submit) + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, + serial_no=serial_no, basic_rate=100, do_not_submit=True) + self.assertRaises(SerialNoDuplicateError, se.submit) + def get_gl_entries(voucher_type, voucher_no): return frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type=%s and voucher_no=%s diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 76a0406f09..ca64b1eda2 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -213,7 +213,7 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no, sle.item_code), SerialNoItemError) - if sr.warehouse and sle.actual_qty > 0: + if sle.actual_qty > 0 and has_duplicate_serial_no(sr, sle): frappe.throw(_("Serial No {0} has already been received").format(serial_no), SerialNoDuplicateError) @@ -234,6 +234,21 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code), SerialNoRequiredError) +def has_duplicate_serial_no(sn, sle): + if sn.warehouse: + return True + + status = False + if sn.purchase_document_no: + if sle.voucher_type in ['Purchase Receipt', 'Stock Entry']: + status = True + + if status and sle.voucher_type == 'Stock Entry' and \ + frappe.db.get_value('Stock Entry', sle.voucher_no, 'purpose') != 'Material Receipt': + status = False + + return status + def allow_serial_nos_with_different_item(sle_serial_no, sle): """ Allows same serial nos for raw materials and finished goods 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/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 92de229a8c..7fa232e6f7 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -395,7 +395,8 @@ class TestStockEntry(unittest.TestCase): def test_serial_item_error(self): se, serial_nos = self.test_serial_by_series() - make_serialized_item("_Test Serialized Item", "ABCD\nEFGH") + if not frappe.db.exists('Serial No', 'ABCD'): + make_serialized_item("_Test Serialized Item", "ABCD\nEFGH") se = frappe.copy_doc(test_records[0]) se.purpose = "Material Transfer" diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js new file mode 100644 index 0000000000..ffd06642bf --- /dev/null +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js @@ -0,0 +1,35 @@ +QUnit.module('Stock'); + +QUnit.test("test material receipt", 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 4'}, + {'qty': 5}, + {'batch_no':'TEST-BATCH-001'}, + {'serial_no':'Test-Product-001\nTest-Product-002\nTest-Product-003\nTest-Product-004\nTest-Product-005'}, + {'basic_rate':100}, + ] + ]}, + ]); + }, + () => cur_frm.save(), + () => frappe.click_button('Update Rate and Availability'), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 4', "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_for_manufacture.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js new file mode 100644 index 0000000000..e8b2973c45 --- /dev/null +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js @@ -0,0 +1,34 @@ +QUnit.module('Stock'); + +QUnit.test("test material Transfer to manufacture", function(assert) { + assert.expect(3); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Stock Entry', [ + {purpose:'Material Transfer for Manufacture'}, + {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': 1}, + ] + ]}, + ]); + }, + () => 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==100, " Outgoing Value correct"); + assert.ok(cur_frm.doc.total_incoming_value==100, " 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_subcontract.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js new file mode 100644 index 0000000000..131d3ca1de --- /dev/null +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js @@ -0,0 +1,34 @@ +QUnit.module('Stock'); + +QUnit.test("test material Transfer to manufacture", function(assert) { + assert.expect(3); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Stock Entry', [ + {purpose:'Subcontract'}, + {from_warehouse:'Work In Progress - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {to_warehouse:'Finished Goods - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {items: [ + [ + {'item_code': 'Test Product 1'}, + {'qty': 1}, + ] + ]}, + ]); + }, + () => 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==100, " Outgoing Value correct"); + assert.ok(cur_frm.doc.total_incoming_value==100, " 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/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index d9d9568e40..186eaeebb1 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -18,7 +18,7 @@ class StockSettings(Document): self.get("item_naming_by")=="Naming Series", hide_name_field=True) stock_frozen_limit = 356 - submitted_stock_frozen = self.stock_frozen_upto_days + submitted_stock_frozen = self.stock_frozen_upto_days or 0 if submitted_stock_frozen > stock_frozen_limit: self.stock_frozen_upto_days = stock_frozen_limit frappe.msgprint (_("`Freeze Stocks Older Than` should be smaller than %d days.") %stock_frozen_limit) diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json index 275bdefcf1..95544c15e1 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.json +++ b/erpnext/stock/doctype/warehouse/warehouse.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, @@ -72,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, @@ -103,6 +106,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -134,6 +138,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -162,6 +167,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, @@ -220,6 +227,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -249,6 +257,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -279,6 +288,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -310,6 +320,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, @@ -369,6 +381,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -399,6 +412,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -429,6 +443,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -459,6 +474,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, @@ -519,6 +536,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -548,6 +566,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, @@ -578,6 +597,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -607,6 +627,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -636,6 +657,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -643,7 +665,7 @@ "fieldname": "old_parent", "fieldtype": "Link", "hidden": 1, - "ignore_user_permissions": 0, + "ignore_user_permissions": 1, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, @@ -677,7 +699,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-04-21 16:49:19.018576", + "modified": "2017-08-21 02:12:33.652689", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse", diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 6820d8fa5b..8377f59979 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -89,7 +89,9 @@ def get_sle_conditions(filters): conditions.append("""sle.item_code in (select item.name from tabItem item {item_conditions})""".format(item_conditions=item_conditions)) if filters.get("warehouse"): - conditions.append(get_warehouse_condition(filters.get("warehouse"))) + warehouse_condition = get_warehouse_condition(filters.get("warehouse")) + if warehouse_condition: + conditions.append(warehouse_condition) if filters.get("voucher_no"): conditions.append("voucher_no=%(voucher_no)s") if filters.get("batch_no"): diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 403d5cbc30..6a4ac439ee 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.utils import flt, cstr, nowdate, nowtime @@ -170,7 +170,7 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin where item_code=%s and warehouse=%s and docstatus < 2""", (d[0], d[1])) if serial_nos and flt(serial_nos[0][0]) != flt(d[2]): - print d[0], d[1], d[2], serial_nos[0][0] + print(d[0], d[1], d[2], serial_nos[0][0]) sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry` where item_code = %s and warehouse = %s and ifnull(is_cancelled, 'No') = 'No' @@ -244,7 +244,7 @@ def repost_all_stock_vouchers(): i = 0 for voucher_type, voucher_no in vouchers: i+=1 - print i, "/", len(vouchers), voucher_type, voucher_no + print(i, "/", len(vouchers), voucher_type, voucher_no) try: for dt in ["Stock Ledger Entry", "GL Entry"]: frappe.db.sql("""delete from `tab%s` where voucher_type=%s and voucher_no=%s"""% @@ -259,9 +259,9 @@ def repost_all_stock_vouchers(): doc.update_stock_ledger() doc.make_gl_entries(repost_future_gle=False) frappe.db.commit() - except Exception, e: - print frappe.get_traceback() + except Exception as e: + print(frappe.get_traceback()) rejected.append([voucher_type, voucher_no]) frappe.db.rollback() - print rejected + print(rejected) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index c1b76e5374..306736f3dc 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -24,7 +24,7 @@ frappe.ui.form.on("Issue", { frm.timeline.wrapper.find('.comment-header .asset-details .btn-add-to-kb').remove(); $('') - .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details')) + .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])')) .on('click', function() { var content = $(this).parents('.timeline-item:first').find('.timeline-item-content').html(); var doc = frappe.model.get_new_doc('Help Article'); diff --git a/erpnext/templates/includes/itemised_tax_breakup.html b/erpnext/templates/includes/itemised_tax_breakup.html index 2ffc8b4b83..75212d5a1a 100644 --- a/erpnext/templates/includes/itemised_tax_breakup.html +++ b/erpnext/templates/includes/itemised_tax_breakup.html @@ -22,7 +22,9 @@ {% set tax_details = taxes.get(tax_account) %} {% if tax_details %} - ({{ tax_details.tax_rate }}) + {% if tax_details.tax_rate or not tax_details.tax_amount %} + ({{ tax_details.tax_rate }}) + {% endif %} {{ frappe.utils.fmt_money(tax_details.tax_amount, None, company_currency) }} {% else %} diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js index 3905959928..b2d5ad952b 100644 --- a/erpnext/templates/includes/product_page.js +++ b/erpnext/templates/includes/product_page.js @@ -73,7 +73,7 @@ frappe.ready(function() { } } - if (window.location.search.indexOf(item_code)!==-1) { + if (window.location.search == ("?variant=" + item_code) || window.location.search.includes(item_code)) { return; } diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py index e46fed6bb6..6ebe41185f 100644 --- a/erpnext/templates/utils.py +++ b/erpnext/templates/utils.py @@ -36,11 +36,11 @@ def send_message(subject="Website Query", message="", sender="", status="Open"): )) if customer: - opportunity.customer = customer[0][0] + opportunity.customer = customer[0][0] elif lead: - opportunity.lead = lead + opportunity.lead = lead else: - opportunity.lead = new_lead.name + opportunity.lead = new_lead.name opportunity.insert(ignore_permissions=True) diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 2e71285097..653aeecc66 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -60,13 +60,44 @@ erpnext/stock/doctype/material_request/tests/test_material_request_type_manufact 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/hr/doctype/appraisal_template/test_appraisal_template.js +erpnext/hr/doctype/appraisal/test_appraisal.js +erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js +erpnext/hr/doctype/expense_claim/test_expense_claim.js +erpnext/hr/doctype/training_event/test_training_event.js +erpnext/hr/doctype/training_result_employee/test_training_result.js +erpnext/hr/doctype/training_feedback/test_training_feedback.js +erpnext/hr/doctype/loan_type/test_loan_type.js +erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js +erpnext/hr/doctype/employee_loan/test_employee_loan.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/sales_invoice/tests/test_sales_invoice_with_payment_request.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 erpnext/schools/doctype/course/test_course.js erpnext/schools/doctype/program/test_program.js -erpnext/hr/doctype/salary_structure/test_salary_structure.js -erpnext/hr/doctype/salary_slip/test_salary_slip.js erpnext/schools/doctype/guardian/test_guardian.js erpnext/schools/doctype/student_admission/test_student_admission.js erpnext/schools/doctype/student_applicant/tests/test_student_applicant_dummy_data.js @@ -82,17 +113,12 @@ 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/buying/doctype/request_for_quotation/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/test_sales_invoice.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 \ No newline at end of file +erpnext/accounts/doctype/journal_entry/test_journal_entry.js +erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js +erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js +erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js +erpnext/stock/doctype/batch/test_batch.js +erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js diff --git a/erpnext/utilities/__init__.py b/erpnext/utilities/__init__.py index 944f9785a4..0f641b2b38 100644 --- a/erpnext/utilities/__init__.py +++ b/erpnext/utilities/__init__.py @@ -1,5 +1,5 @@ ## temp utility - +from __future__ import print_function import frappe from erpnext.utilities.activation import get_level from frappe.utils import cstr @@ -12,7 +12,7 @@ def update_doctypes(): for f in dt.fields: if f.fieldname == d.fieldname and f.fieldtype in ("Text", "Small Text"): - print f.parent, f.fieldname + print(f.parent, f.fieldname) f.fieldtype = "Text Editor" dt.save() break