From c0d8233a8b37a8b78306f531a94d5bcc21f5eedb Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 18 Nov 2019 15:45:53 +0530 Subject: [PATCH 01/36] fix: Raw material qty depending on the quantity of the parent BOM --- erpnext/manufacturing/report/bom_explorer/bom_explorer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py index 875d1152de..48907adc5f 100644 --- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py +++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py @@ -14,7 +14,7 @@ def execute(filters=None): def get_data(filters, data): get_exploded_items(filters.bom, data) -def get_exploded_items(bom, data, indent=0): +def get_exploded_items(bom, data, indent=0, qty=1): exploded_items = frappe.get_all("BOM Item", filters={"parent": bom}, fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom']) @@ -26,13 +26,13 @@ def get_exploded_items(bom, data, indent=0): 'item_name': item.item_name, 'indent': indent, 'bom': item.bom_no, - 'qty': item.qty, + 'qty': item.qty * qty, 'uom': item.uom, 'description': item.description, 'scrap': item.scrap }) if item.bom_no: - get_exploded_items(item.bom_no, data, indent=indent+1) + get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty) def get_columns(): return [ From 3cf2c2b3d517af72253f662f2d27bb2e74b519ab Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 21 Nov 2019 20:47:08 +0530 Subject: [PATCH 02/36] feat: slides for onboarding wizard in ERPNext --- erpnext/public/less/erpnext.less | 2 +- .../{user_progress_utils.py => onboarding_utils.py} | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) rename erpnext/utilities/{user_progress_utils.py => onboarding_utils.py} (94%) diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less index 8ed5f1adb0..abe48685f0 100644 --- a/erpnext/public/less/erpnext.less +++ b/erpnext/public/less/erpnext.less @@ -426,7 +426,7 @@ body[data-route="pos"] { .collapse-btn { cursor: pointer; } - + @media (max-width: @screen-xs) { .page-actions { max-width: 110px; diff --git a/erpnext/utilities/user_progress_utils.py b/erpnext/utilities/onboarding_utils.py similarity index 94% rename from erpnext/utilities/user_progress_utils.py rename to erpnext/utilities/onboarding_utils.py index b7c24a71ba..35f2b6a7ed 100644 --- a/erpnext/utilities/user_progress_utils.py +++ b/erpnext/utilities/onboarding_utils.py @@ -20,7 +20,7 @@ def create_customers(args_data): args = json.loads(args_data) defaults = frappe.defaults.get_defaults() for i in range(1,4): - customer = args.get("customer_" + str(i)) + customer = args.get("customer_name_" + str(i)) if customer: try: doc = frappe.get_doc({ @@ -58,7 +58,7 @@ def create_suppliers(args_data): args = json.loads(args_data) defaults = frappe.defaults.get_defaults() for i in range(1,4): - supplier = args.get("supplier_" + str(i)) + supplier = args.get("supplier_name_" + str(i)) if supplier: try: doc = frappe.get_doc({ @@ -76,7 +76,7 @@ def create_suppliers(args_data): def create_contact(contact, party_type, party): """Create contact based on given contact name""" - contact = contact .split(" ") + contact = contact.split(" ") contact = frappe.get_doc({ "doctype":"Contact", @@ -232,9 +232,3 @@ def create_users(args_data): emp.insert(ignore_permissions = True) # Ennumerate the setup hooks you're going to need, apart from the slides - -@frappe.whitelist() -def update_default_domain_actions_and_get_state(): - domain = frappe.get_cached_value('Company', erpnext.get_default_company(), 'domain') - update_domain_actions(domain) - return get_domain_actions_state(domain) From 00677f334ed22a9dbdb869fa79f153fb0d421f51 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 25 Nov 2019 11:58:14 +0530 Subject: [PATCH 03/36] fix: user progress code cleanup --- erpnext/hooks.py | 2 - .../images/illustrations/collaboration.png | Bin 0 -> 3849 bytes .../public/images/illustrations/customer.png | Bin 0 -> 4093 bytes .../images/illustrations/letterhead.png | Bin 0 -> 1613 bytes .../public/images/illustrations/onboard.png | Bin 0 -> 2742 bytes .../public/images/illustrations/product.png | Bin 0 -> 3136 bytes .../public/images/illustrations/supplier.png | Bin 0 -> 3531 bytes erpnext/public/images/illustrations/user.png | Bin 0 -> 7887 bytes .../setup/doctype/setup_progress/__init__.py | 0 .../doctype/setup_progress/setup_progress.js | 8 - .../setup_progress/setup_progress.json | 123 -------- .../doctype/setup_progress/setup_progress.py | 63 ---- .../setup_progress/test_setup_progress.js | 23 -- .../setup_progress/test_setup_progress.py | 9 - .../doctype/setup_progress_action/__init__.py | 0 .../setup_progress_action.json | 253 --------------- .../setup_progress_action.py | 9 - erpnext/utilities/onboarding_utils.py | 25 +- erpnext/utilities/user_progress.py | 287 ------------------ 19 files changed, 9 insertions(+), 793 deletions(-) create mode 100644 erpnext/public/images/illustrations/collaboration.png create mode 100644 erpnext/public/images/illustrations/customer.png create mode 100644 erpnext/public/images/illustrations/letterhead.png create mode 100644 erpnext/public/images/illustrations/onboard.png create mode 100644 erpnext/public/images/illustrations/product.png create mode 100644 erpnext/public/images/illustrations/supplier.png create mode 100644 erpnext/public/images/illustrations/user.png delete mode 100644 erpnext/setup/doctype/setup_progress/__init__.py delete mode 100644 erpnext/setup/doctype/setup_progress/setup_progress.js delete mode 100644 erpnext/setup/doctype/setup_progress/setup_progress.json delete mode 100644 erpnext/setup/doctype/setup_progress/setup_progress.py delete mode 100644 erpnext/setup/doctype/setup_progress/test_setup_progress.js delete mode 100644 erpnext/setup/doctype/setup_progress/test_setup_progress.py delete mode 100644 erpnext/setup/doctype/setup_progress_action/__init__.py delete mode 100644 erpnext/setup/doctype/setup_progress_action/setup_progress_action.json delete mode 100644 erpnext/setup/doctype/setup_progress_action/setup_progress_action.py delete mode 100644 erpnext/utilities/user_progress.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9e74bfd290..a88bb44ada 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -40,8 +40,6 @@ after_install = "erpnext.setup.install.after_install" boot_session = "erpnext.startup.boot.boot_session" notification_config = "erpnext.startup.notifications.get_notification_config" get_help_messages = "erpnext.utilities.activation.get_help_messages" -get_user_progress_slides = "erpnext.utilities.user_progress.get_user_progress_slides" -update_and_get_user_progress = "erpnext.utilities.user_progress_utils.update_default_domain_actions_and_get_state" leaderboards = "erpnext.startup.leaderboard.get_leaderboards" diff --git a/erpnext/public/images/illustrations/collaboration.png b/erpnext/public/images/illustrations/collaboration.png new file mode 100644 index 0000000000000000000000000000000000000000..12c67e394ccf22e12ff8e0c149139e82e73b321f GIT binary patch literal 3849 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 GfAc@J)HmJ$ literal 0 HcmV?d00001 diff --git a/erpnext/public/images/illustrations/customer.png b/erpnext/public/images/illustrations/customer.png new file mode 100644 index 0000000000000000000000000000000000000000..b2ddbf3bb4709407ef493926e3490336568f6543 GIT binary patch literal 4093 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 literal 0 HcmV?d00001 diff --git a/erpnext/public/images/illustrations/letterhead.png b/erpnext/public/images/illustrations/letterhead.png new file mode 100644 index 0000000000000000000000000000000000000000..37df6d7f6fce07fd2604c4c8387dd18869b9bc54 GIT binary patch literal 1613 zcmZ`(do>*s zFI0QlN*gjEZq5z>#U~Aj(o7+m;>!V0Y5EyR_Bs_EX;Gf*LUojXtE8bWCtFQ2>IPV% zE zAO>neRn`oW5hBZ7VXw?QZYNlZm{Bz(XXDL+X#NI4iiQD4guQVGUS){JKThOYi~i~u zUKn9?=5jid-;{E~I+I2oX>@1nx3d-XB?-)!;D$=eG#gv?eBS$e$mnw33+4y?n{T_V z@ogT8r~Xvxy{}`BEG@wX3%|C-;@ebW80<+cyOpuljrCbG#yrvWFI#N(e6GP2Of(KD z>^xLx5x>_&B@p`6?S={rG5inv+jpd%`W5J{#fCC3Sv?0&C7zK-G5!&K5#eE7AXZeN z&ufwHzN8M%5x-eY>v^>dQ*VF4zU|~+w0#uz-I%k!Wp(x4S^;Y)awYNFS5C*W^wZNV zQ3w4R%evU&4#)Jzl24bO4KJot)p+#Se_)2L2Kv5sokB-da!!?~v!`?LVxIy;ln-AT zI<`s1c*=VTjC{&9ZMf{VBj9FfOl{ubgf(X>-}&cQ@m1>A&g)s;4@nUCxCb^{zB3;i zcmA;jAzL z2HFtvu#_o8g_ls@yN8}VKn7^1=0^w8vT9#3=4RP;j9b|WN24MiXZ*edQhl53Iz_{^ zx4b8lNO!XvjIhUjG+{QYW%IK5WTmqQ`??EVX7 z!aJTj!xBlCpuZ){W>H;Iw{dwOl%;>J>{fbx#sbRDU3{LL#;bGBbkZTGSk{YZUbC&e z7sxY+ciitmbBfX}21mjp(o2B*QmPrkPlM z6i!d^QncG4(~|5b$FD%Th;UUo(`OJFRXH-2{b0{`S(yGr1RAp4k1Js4*K5&cv&C&CZ>VDdvZ+-k zwN|V&ox2%4w@P6-@ONQ24Nk5Lq?ieki)7@5$1Q= z0C5^F*93GABA5%yRQa);lW6z$D;e@tLFY$(4QBS|$&{`D<0&_E;sxb|?ouu2lB4`% zJy%=kjVHHFK5SDYZNmpZSFNjC{xpBF;^;bTnMHrR_*Wxr=Imz#GvKF-4kAWaDR|}} zdXfB!Wx&K-WdKD-&lE$J0P{UiMA=?QGR^8Y%@@ZM#3qs|anxZGV@FO^pQfPD^F_(| z`xSQm@(3Ib_R}ppH^J}KRrI$-EtL_cqt2U}e4ES7{Bb2oJLy)~{JEl@d|}cx0b6wsGb*4tW{-EP4c22v&$Fc)FJOUEyYd!?)Krs+t8DPX9_=9!lp4MwzWI_3@ zFSLi>Y7zr2+!70mio$-$Hx7sE!j7(P!CV3{94=E5^NSuo`*CD#%jF3089qHWAU@n8SJBA@8AQH^2P0h_si3D%+O(cRPiD0pg zKp+ta8DF;N{!0)U70L|T|Nnx;l2M#gp!L(iD=Li33yxs`kH^C^_wD8E4vuEvqhiAI SCTz8(L*U}*;c#m!dgyN&#IyVW literal 0 HcmV?d00001 diff --git a/erpnext/public/images/illustrations/onboard.png b/erpnext/public/images/illustrations/onboard.png new file mode 100644 index 0000000000000000000000000000000000000000..094aa3f8ddfc1a07762df08cb3c134ae3ce19384 GIT binary patch literal 2742 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 literal 0 HcmV?d00001 diff --git a/erpnext/public/images/illustrations/product.png b/erpnext/public/images/illustrations/product.png new file mode 100644 index 0000000000000000000000000000000000000000..f864b7af60ef44849084225da7f601bcbb33a7b7 GIT binary patch literal 3136 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 BlxP3| literal 0 HcmV?d00001 diff --git a/erpnext/public/images/illustrations/user.png b/erpnext/public/images/illustrations/user.png new file mode 100644 index 0000000000000000000000000000000000000000..7dd7db210d481d3ad5d78277776102d327fe21a2 GIT binary patch literal 7887 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 literal 0 HcmV?d00001 diff --git a/erpnext/setup/doctype/setup_progress/__init__.py b/erpnext/setup/doctype/setup_progress/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/setup/doctype/setup_progress/setup_progress.js b/erpnext/setup/doctype/setup_progress/setup_progress.js deleted file mode 100644 index 5c78bd5075..0000000000 --- a/erpnext/setup/doctype/setup_progress/setup_progress.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Setup Progress', { - refresh: function() { - - } -}); diff --git a/erpnext/setup/doctype/setup_progress/setup_progress.json b/erpnext/setup/doctype/setup_progress/setup_progress.json deleted file mode 100644 index 09072d4665..0000000000 --- a/erpnext/setup/doctype/setup_progress/setup_progress.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-08-27 21:01:42.032109", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actions_sb", - "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": "Actions", - "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": "actions", - "fieldtype": "Table", - "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": "Actions", - "length": 0, - "no_copy": 0, - "options": "Setup Progress Action", - "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": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2017-09-21 11:52:56.106659", - "modified_by": "Administrator", - "module": "Setup", - "name": "Setup Progress", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 1, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/setup/doctype/setup_progress/setup_progress.py b/erpnext/setup/doctype/setup_progress/setup_progress.py deleted file mode 100644 index e1402f5844..0000000000 --- a/erpnext/setup/doctype/setup_progress/setup_progress.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- 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, json -from frappe.model.document import Document - -class SetupProgress(Document): - pass - -def get_setup_progress(): - if not getattr(frappe.local, "setup_progress", None): - frappe.local.setup_progress = frappe.get_doc("Setup Progress", "Setup Progress") - - return frappe.local.setup_progress - -def get_action_completed_state(action_name): - for d in get_setup_progress().actions: - if d.action_name == action_name: - return d.is_completed - -def update_action_completed_state(action_name): - action_table_doc = [d for d in get_setup_progress().actions - if d.action_name == action_name][0] - update_action(action_table_doc) - -def update_action(doc): - doctype = doc.action_doctype - docname = doc.action_document - field = doc.action_field - - if not doc.is_completed: - if doc.min_doc_count: - if frappe.db.count(doctype) >= doc.min_doc_count: - doc.is_completed = 1 - doc.save() - if docname and field: - d = frappe.get_doc(doctype, docname) - if d.get(field): - doc.is_completed = 1 - doc.save() - -def update_domain_actions(domain): - for d in get_setup_progress().actions: - domains = json.loads(d.domains) - if domains == [] or domain in domains: - update_action(d) - -def get_domain_actions_state(domain): - state = {} - for d in get_setup_progress().actions: - domains = json.loads(d.domains) - if domains == [] or domain in domains: - state[d.action_name] = d.is_completed - return state - -@frappe.whitelist() -def set_action_completed_state(action_name): - action_table_doc = [d for d in get_setup_progress().actions - if d.action_name == action_name][0] - action_table_doc.is_completed = 1 - action_table_doc.save() diff --git a/erpnext/setup/doctype/setup_progress/test_setup_progress.js b/erpnext/setup/doctype/setup_progress/test_setup_progress.js deleted file mode 100644 index 9e84e0cb15..0000000000 --- a/erpnext/setup/doctype/setup_progress/test_setup_progress.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Setup Progress", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Setup Progress - () => frappe.tests.make('Setup Progress', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/setup_progress/test_setup_progress.py b/erpnext/setup/doctype/setup_progress/test_setup_progress.py deleted file mode 100644 index 8926219143..0000000000 --- a/erpnext/setup/doctype/setup_progress/test_setup_progress.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -class TestSetupProgress(unittest.TestCase): - pass diff --git a/erpnext/setup/doctype/setup_progress_action/__init__.py b/erpnext/setup/doctype/setup_progress_action/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/setup/doctype/setup_progress_action/setup_progress_action.json b/erpnext/setup/doctype/setup_progress_action/setup_progress_action.json deleted file mode 100644 index e9abcbcd1a..0000000000 --- a/erpnext/setup/doctype/setup_progress_action/setup_progress_action.json +++ /dev/null @@ -1,253 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-08-27 21:00:40.715360", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "action_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": "Action Name", - "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": 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": "action_doctype", - "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": "Action Doctype", - "length": 0, - "no_copy": 0, - "options": "DocType", - "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": "action_document", - "fieldtype": "Dynamic 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": "Action Document", - "length": 0, - "no_copy": 0, - "options": "action_doctype", - "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": "action_field", - "fieldtype": "Data", - "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": "Action Field", - "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": "min_doc_count", - "fieldtype": "Int", - "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": "Min Doc Count", - "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": "domains", - "fieldtype": "Code", - "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": "Domains", - "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": "is_completed", - "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": "Is Completed", - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-09-01 14:34:59.685730", - "modified_by": "Administrator", - "module": "Setup", - "name": "Setup Progress Action", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 1, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/setup/doctype/setup_progress_action/setup_progress_action.py b/erpnext/setup/doctype/setup_progress_action/setup_progress_action.py deleted file mode 100644 index 24af94347e..0000000000 --- a/erpnext/setup/doctype/setup_progress_action/setup_progress_action.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -from frappe.model.document import Document - -class SetupProgressAction(Document): - pass diff --git a/erpnext/utilities/onboarding_utils.py b/erpnext/utilities/onboarding_utils.py index 35f2b6a7ed..7b775a7b7d 100644 --- a/erpnext/utilities/onboarding_utils.py +++ b/erpnext/utilities/onboarding_utils.py @@ -7,19 +7,12 @@ import frappe, erpnext import json from frappe import _ from frappe.utils import flt -from erpnext.setup.doctype.setup_progress.setup_progress import update_domain_actions, get_domain_actions_state - -@frappe.whitelist() -def set_sales_target(args_data): - args = json.loads(args_data) - defaults = frappe.defaults.get_defaults() - frappe.db.set_value("Company", defaults.get("company"), "monthly_sales_target", args.get('monthly_sales_target')) @frappe.whitelist() def create_customers(args_data): args = json.loads(args_data) defaults = frappe.defaults.get_defaults() - for i in range(1,4): + for i in range(1,args.get('max_count')): customer = args.get("customer_name_" + str(i)) if customer: try: @@ -57,7 +50,7 @@ def create_letterhead(args_data): def create_suppliers(args_data): args = json.loads(args_data) defaults = frappe.defaults.get_defaults() - for i in range(1,4): + for i in range(1,args.get('max_count')): supplier = args.get("supplier_name_" + str(i)) if supplier: try: @@ -90,7 +83,7 @@ def create_contact(contact, party_type, party): def create_items(args_data): args = json.loads(args_data) defaults = frappe.defaults.get_defaults() - for i in range(1,4): + for i in range(1, args.get('max_count')): item = args.get("item_" + str(i)) if item: default_warehouse = "" @@ -141,7 +134,7 @@ def make_item_price(item, price_list_name, item_price): @frappe.whitelist() def create_program(args_data): args = json.loads(args_data) - for i in range(1,4): + for i in range(1,args.get('max_count')): if args.get("program_" + str(i)): program = frappe.new_doc("Program") program.program_code = args.get("program_" + str(i)) @@ -154,7 +147,7 @@ def create_program(args_data): @frappe.whitelist() def create_course(args_data): args = json.loads(args_data) - for i in range(1,4): + for i in range(1,args.get('max_count')): if args.get("course_" + str(i)): course = frappe.new_doc("Course") course.course_code = args.get("course_" + str(i)) @@ -167,7 +160,7 @@ def create_course(args_data): @frappe.whitelist() def create_instructor(args_data): args = json.loads(args_data) - for i in range(1,4): + for i in range(1,args.get('max_count')): if args.get("instructor_" + str(i)): instructor = frappe.new_doc("Instructor") instructor.instructor_name = args.get("instructor_" + str(i)) @@ -179,7 +172,7 @@ def create_instructor(args_data): @frappe.whitelist() def create_room(args_data): args = json.loads(args_data) - for i in range(1,4): + for i in range(1,args.get('max_count')): if args.get("room_" + str(i)): room = frappe.new_doc("Room") room.room_name = args.get("room_" + str(i)) @@ -195,7 +188,7 @@ def create_users(args_data): return args = json.loads(args_data) defaults = frappe.defaults.get_defaults() - for i in range(1,4): + for i in range(1,args.get('max_count')): email = args.get("user_email_" + str(i)) fullname = args.get("user_fullname_" + str(i)) if email: @@ -231,4 +224,4 @@ def create_users(args_data): emp.flags.ignore_mandatory = True emp.insert(ignore_permissions = True) -# Ennumerate the setup hooks you're going to need, apart from the slides +# Enumerate the setup hooks you're going to need, apart from the slides diff --git a/erpnext/utilities/user_progress.py b/erpnext/utilities/user_progress.py deleted file mode 100644 index 5cec3ca384..0000000000 --- a/erpnext/utilities/user_progress.py +++ /dev/null @@ -1,287 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe, erpnext -from frappe import _ -from erpnext.setup.doctype.setup_progress.setup_progress import get_action_completed_state - -def get_slide_settings(): - defaults = frappe.defaults.get_defaults() - domain = frappe.get_cached_value('Company', erpnext.get_default_company(), 'domain') - company = defaults.get("company") or '' - currency = defaults.get("currency") or '' - - doc = frappe.get_doc("Setup Progress") - item = [d for d in doc.get("actions") if d.action_name == "Set Sales Target"] - - if len(item): - item = item[0] - if not item.action_document: - item.action_document = company - doc.save() - - # Initial state of slides - return [ - frappe._dict( - action_name='Add Company', - title=_("Setup Company") if domain != 'Education' else _("Setup Institution"), - help=_('Setup your ' + ('company' if domain != 'Education' else 'institution') + ' and brand.'), - # image_src="/assets/erpnext/images/illustrations/shop.jpg", - fields=[], - done_state_title=_("You added " + company), - done_state_title_route=["Form", "Company", company], - help_links=[ - { - "label": _("Chart of Accounts"), - "url": ["https://erpnext.com/docs/user/manual/en/accounts/chart-of-accounts"] - }, - { - "label": _("Opening Balances"), - "video_id": "U5wPIvEn-0c" - } - ] - ), - frappe._dict( - action_name='Set Sales Target', - domains=('Manufacturing', 'Services', 'Retail', 'Distribution'), - title=_("Set a Target"), - help=_("Set a sales goal you'd like to achieve for your company."), - fields=[ - {"fieldtype":"Currency", "fieldname":"monthly_sales_target", - "label":_("Monthly Sales Target (" + currency + ")"), "reqd":1}, - ], - submit_method="erpnext.utilities.user_progress_utils.set_sales_target", - done_state_title=_("Go to " + company), - done_state_title_route=["Form", "Company", company], - help_links=[ - { - "label": _('Learn More'), - "url": ["https://erpnext.com/docs/user/manual/en/setting-up/setting-company-sales-goal"] - } - ] - ), - frappe._dict( - action_name='Add Customers', - domains=('Manufacturing', 'Services', 'Retail', 'Distribution'), - title=_("Add Customers"), - help=_("List a few of your customers. They could be organizations or individuals."), - fields=[ - {"fieldtype":"Section Break"}, - {"fieldtype":"Data", "fieldname":"customer", "label":_("Customer"), - "placeholder":_("Customer Name")}, - {"fieldtype":"Column Break"}, - {"fieldtype":"Data", "fieldname":"customer_contact", - "label":_("Contact Name"), "placeholder":_("Contact Name")} - ], - add_more=1, max_count=3, mandatory_entry=1, - submit_method="erpnext.utilities.user_progress_utils.create_customers", - done_state_title=_("Go to Customers"), - done_state_title_route=["List", "Customer"], - help_links=[ - { - "label": _('Learn More'), - "url": ["https://erpnext.com/docs/user/manual/en/CRM/customer.html"] - } - ] - ), - - frappe._dict( - action_name='Add Letterhead', - domains=('Manufacturing', 'Services', 'Retail', 'Distribution', 'Education'), - title=_("Add Letterhead"), - help=_("Upload your letter head (Keep it web friendly as 900px by 100px)"), - fields=[ - {"fieldtype":"Attach Image", "fieldname":"letterhead", - "is_private": 0, - "align": "center" - }, - ], - mandatory_entry=1, - submit_method="erpnext.utilities.user_progress_utils.create_letterhead", - done_state_title=_("Go to Letterheads"), - done_state_title_route=["List", "Letter Head"] - ), - - frappe._dict( - action_name='Add Suppliers', - domains=('Manufacturing', 'Services', 'Retail', 'Distribution'), - icon="fa fa-group", - title=_("Your Suppliers"), - help=_("List a few of your suppliers. They could be organizations or individuals."), - fields=[ - {"fieldtype":"Section Break"}, - {"fieldtype":"Data", "fieldname":"supplier", "label":_("Supplier"), - "placeholder":_("Supplier Name")}, - {"fieldtype":"Column Break"}, - {"fieldtype":"Data", "fieldname":"supplier_contact", - "label":_("Contact Name"), "placeholder":_("Contact Name")}, - ], - add_more=1, max_count=3, mandatory_entry=1, - submit_method="erpnext.utilities.user_progress_utils.create_suppliers", - done_state_title=_("Go to Suppliers"), - done_state_title_route=["List", "Supplier"], - help_links=[ - { - "label": _('Learn More'), - "url": ["https://erpnext.com/docs/user/manual/en/buying/supplier"] - }, - { - "label": _('Customers and Suppliers'), - "video_id": "zsrrVDk6VBs" - }, - ] - ), - frappe._dict( - action_name='Add Products', - domains=['Manufacturing', 'Services', 'Retail', 'Distribution'], - icon="fa fa-barcode", - title=_("Your Products or Services"), - help=_("List your products or services that you buy or sell."), - fields=[ - {"fieldtype":"Section Break", "show_section_border": 1}, - {"fieldtype":"Data", "fieldname":"item", "label":_("Item"), - "placeholder":_("A Product")}, - {"fieldtype":"Column Break"}, - {"fieldtype":"Select", "fieldname":"item_uom", "label":_("UOM"), - "options":[_("Unit"), _("Nos"), _("Box"), _("Pair"), _("Kg"), _("Set"), - _("Hour"), _("Minute"), _("Litre"), _("Meter"), _("Gram")], - "default": _("Unit"), "static": 1}, - {"fieldtype":"Column Break"}, - {"fieldtype":"Currency", "fieldname":"item_price", "label":_("Rate"), "static": 1} - ], - add_more=1, max_count=3, mandatory_entry=1, - submit_method="erpnext.utilities.user_progress_utils.create_items", - done_state_title=_("Go to Items"), - done_state_title_route=["List", "Item"], - help_links=[ - { - "label": _("Explore Sales Cycle"), - "video_id": "1eP90MWoDQM" - }, - ] - ), - - # Education slides begin - frappe._dict( - action_name='Add Programs', - domains=("Education"), - title=_("Program"), - help=_("Example: Masters in Computer Science"), - fields=[ - {"fieldtype":"Section Break", "show_section_border": 1}, - {"fieldtype":"Data", "fieldname":"program", "label":_("Program"), "placeholder": _("Program Name")}, - ], - add_more=1, max_count=3, mandatory_entry=1, - submit_method="erpnext.utilities.user_progress_utils.create_program", - done_state_title=_("Go to Programs"), - done_state_title_route=["List", "Program"], - help_links=[ - { - "label": _("Student Application"), - "video_id": "l8PUACusN3E" - }, - ] - - ), - frappe._dict( - action_name='Add Courses', - domains=["Education"], - title=_("Course"), - help=_("Example: Basic Mathematics"), - fields=[ - {"fieldtype":"Section Break", "show_section_border": 1}, - {"fieldtype":"Data", "fieldname":"course", "label":_("Course"), "placeholder": _("Course Name")}, - ], - add_more=1, max_count=3, mandatory_entry=1, - submit_method="erpnext.utilities.user_progress_utils.create_course", - done_state_title=_("Go to Courses"), - done_state_title_route=["List", "Course"], - help_links=[ - { - "label": _('Add Students'), - "route": ["List", "Student"] - } - ] - ), - frappe._dict( - action_name='Add Instructors', - domains=["Education"], - title=_("Instructor"), - help=_("People who teach at your organisation"), - fields=[ - {"fieldtype":"Section Break", "show_section_border": 1}, - {"fieldtype":"Data", "fieldname":"instructor", "label":_("Instructor"), "placeholder": _("Instructor Name")}, - ], - add_more=1, max_count=3, mandatory_entry=1, - submit_method="erpnext.utilities.user_progress_utils.create_instructor", - done_state_title=_("Go to Instructors"), - done_state_title_route=["List", "Instructor"], - help_links=[ - { - "label": _('Student Batches'), - "route": ["List", "Student Batch"] - } - ] - ), - frappe._dict( - action_name='Add Rooms', - domains=["Education"], - title=_("Room"), - help=_("Classrooms/ Laboratories etc where lectures can be scheduled."), - fields=[ - {"fieldtype":"Section Break", "show_section_border": 1}, - {"fieldtype":"Data", "fieldname":"room", "label":_("Room")}, - {"fieldtype":"Column Break"}, - {"fieldtype":"Int", "fieldname":"room_capacity", "label":_("Room Capacity"), "static": 1}, - ], - add_more=1, max_count=3, mandatory_entry=1, - submit_method="erpnext.utilities.user_progress_utils.create_room", - done_state_title=_("Go to Rooms"), - done_state_title_route=["List", "Room"], - help_links=[] - ), - # Education slides end - - frappe._dict( - action_name='Add Users', - title=_("Add Users"), - help=_("Add users to your organization, other than yourself."), - fields=[ - {"fieldtype":"Section Break"}, - {"fieldtype":"Data", "fieldname":"user_email", "label":_("Email ID"), - "placeholder":_("user@example.com"), "options": "Email", "static": 1}, - {"fieldtype":"Column Break"}, - {"fieldtype":"Data", "fieldname":"user_fullname", - "label":_("Full Name"), "static": 1}, - ], - add_more=1, max_count=3, mandatory_entry=1, - submit_method="erpnext.utilities.user_progress_utils.create_users", - done_state_title=_("Go to Users"), - done_state_title_route=["List", "User"], - help_links=[ - { - "label": _('Learn More'), - "url": ["https://erpnext.com/docs/user/manual/en/setting-up/users-and-permissions"] - }, - { - "label": _('Users and Permissions'), - "video_id": "8Slw1hsTmUI" - }, - ] - ) - ] - -def get_user_progress_slides(): - slides = [] - slide_settings = get_slide_settings() - - domains = frappe.get_active_domains() - for s in slide_settings: - if not s.domains or any(d in domains for d in s.domains): - s.mark_as_done_method = "erpnext.setup.doctype.setup_progress.setup_progress.set_action_completed_state" - s.done = get_action_completed_state(s.action_name) or 0 - slides.append(s) - - return slides - From 7728ae698801d5f97ef811c40b46ee68ab27acab Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 25 Nov 2019 13:23:53 +0530 Subject: [PATCH 04/36] fix: added company letter head slide --- erpnext/utilities/onboarding_utils.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/erpnext/utilities/onboarding_utils.py b/erpnext/utilities/onboarding_utils.py index 7b775a7b7d..6c40f81b92 100644 --- a/erpnext/utilities/onboarding_utils.py +++ b/erpnext/utilities/onboarding_utils.py @@ -17,7 +17,7 @@ def create_customers(args_data): if customer: try: doc = frappe.get_doc({ - "doctype":"Customer", + "doctype": "Customer", "customer_name": customer, "customer_type": "Company", "customer_group": _("Commercial"), @@ -25,9 +25,9 @@ def create_customers(args_data): "company": defaults.get("company") }).insert() - if args.get("customer_contact_" + str(i)): - create_contact(args.get("customer_contact_" + str(i)), - "Customer", doc.name) + if args.get("customer_email_" + str(i)): + create_contact(customer, "Customer", + doc.name, args.get("customer_email_" + str(i))) except frappe.NameError: pass @@ -38,8 +38,8 @@ def create_letterhead(args_data): if letterhead: try: frappe.get_doc({ - "doctype":"Letter Head", - "content":"""

""".format(letterhead.encode('utf-8')), + "doctype": "Letter Head", + "image": letterhead, "letter_head_name": _("Standard"), "is_default": 1 }).insert() @@ -61,21 +61,22 @@ def create_suppliers(args_data): "company": defaults.get("company") }).insert() - if args.get("supplier_contact_" + str(i)): - create_contact(args.get("supplier_contact_" + str(i)), - "Supplier", doc.name) + if args.get("supplier_email_" + str(i)): + create_contact(supplier, "Supplier", + doc.name, args.get("supplier_email_" + str(i))) except frappe.NameError: pass -def create_contact(contact, party_type, party): +def create_contact(contact, party_type, party, email): """Create contact based on given contact name""" contact = contact.split(" ") contact = frappe.get_doc({ - "doctype":"Contact", - "first_name":contact[0], + "doctype": "Contact", + "first_name": contact[0], "last_name": len(contact) > 1 and contact[1] or "" }) + contact.append('email_ids', dict(email_id=email, is_primary=1)) contact.append('links', dict(link_doctype=party_type, link_name=party)) contact.insert() From 195893db0e18619830ae24c6d7b5222cb2df8080 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 26 Nov 2019 15:22:05 +0530 Subject: [PATCH 05/36] fix: conditionally run old patch for Setup Progress --- erpnext/patches/v8_9/add_setup_progress_actions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/patches/v8_9/add_setup_progress_actions.py b/erpnext/patches/v8_9/add_setup_progress_actions.py index fe123111bc..77501073cf 100644 --- a/erpnext/patches/v8_9/add_setup_progress_actions.py +++ b/erpnext/patches/v8_9/add_setup_progress_actions.py @@ -5,6 +5,9 @@ from frappe import _ def execute(): """Add setup progress actions""" + if not frappe.db.exists('DocType', 'Setup Progress') or not frappe.db.exists('DocType', 'Setup Progress Action'): + return + frappe.reload_doc("setup", "doctype", "setup_progress") frappe.reload_doc("setup", "doctype", "setup_progress_action") From 3f8c46058460346690d6d9b919b231bcff20710e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 26 Nov 2019 15:22:45 +0530 Subject: [PATCH 06/36] feat: moved slide action functions from onboarding utils to doctypes --- erpnext/buying/doctype/supplier/supplier.py | 20 ++ erpnext/selling/doctype/customer/customer.py | 34 +++ erpnext/stock/doctype/item/item.py | 48 ++++ erpnext/utilities/onboarding_utils.py | 228 ------------------- 4 files changed, 102 insertions(+), 228 deletions(-) delete mode 100644 erpnext/utilities/onboarding_utils.py diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index b6d588ed96..62a04f37b1 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -56,3 +56,23 @@ class Supplier(TransactionBase): def after_rename(self, olddn, newdn, merge=False): if frappe.defaults.get_global_default('supp_master_name') == 'Supplier Name': frappe.db.set(self, "supplier_name", newdn) + + def create_onboarding_docs(self, args): + defaults = frappe.defaults.get_defaults() + for i in range(1, args.get('max_count')): + supplier = args.get('supplier_name_' + str(i)) + if supplier: + try: + doc = frappe.get_doc({ + 'doctype': self.doctype, + 'supplier_name': supplier, + 'supplier_group': _('Local'), + 'company': defaults.get('company') + }).insert() + + if args.get('supplier_email_' + str(i)): + from erpnext.selling.doctype.customer.customer import create_contact + create_contact(supplier, 'Supplier', + doc.name, args.get('supplier_email_' + str(i))) + except frappe.NameError: + pass \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index a8e3ce4eae..e1a619ea55 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -204,6 +204,40 @@ class Customer(TransactionBase): else: frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually.")) + def create_onboarding_docs(self, args): + defaults = frappe.defaults.get_defaults() + for i in range(1, args.get('max_count')): + customer = args.get('customer_name_' + str(i)) + if customer: + try: + doc = frappe.get_doc({ + 'doctype': self.doctype, + 'customer_name': customer, + 'customer_type': 'Company', + 'customer_group': _('Commercial'), + 'territory': defaults.get('country'), + 'company': defaults.get('company') + }).insert() + + if args.get('customer_email_' + str(i)): + create_contact(customer, self.doctype, + doc.name, args.get("customer_email_" + str(i))) + except frappe.NameError: + pass + +def create_contact(contact, party_type, party, email): + """Create contact based on given contact name""" + contact = contact.split(' ') + + contact = frappe.get_doc({ + 'doctype': 'Contact', + 'first_name': contact[0], + 'last_name': len(contact) > 1 and contact[1] or "" + }) + contact.append('email_ids', dict(email_id=email, is_primary=1)) + contact.append('links', dict(link_doctype=party_type, link_name=party)) + contact.insert() + @frappe.whitelist() def get_loyalty_programs(doc): ''' returns applicable loyalty programs for a customer ''' diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 164c659fe8..a2abd19557 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -884,6 +884,54 @@ class Item(WebsiteGenerator): if not enabled: frappe.msgprint(msg=_("You have to enable auto re-order in Stock Settings to maintain re-order levels."), title=_("Enable Auto Re-Order"), indicator="orange") + def create_onboarding_docs(self, args): + defaults = frappe.defaults.get_defaults() + for i in range(1, args.get('max_count')): + item = args.get('item_' + str(i)) + if item: + default_warehouse = '' + default_warehouse = frappe.db.get_value('Warehouse', filters={ + 'warehouse_name': _('Finished Goods'), + 'company': defaults.get('company_name') + }) + + try: + frappe.get_doc({ + 'doctype': self.doctype, + 'item_code': item, + 'item_name': item, + 'description': item, + 'show_in_website': 1, + 'is_sales_item': 1, + 'is_purchase_item': 1, + 'is_stock_item': 1, + 'item_group': _('Products'), + 'stock_uom': _(args.get('item_uom_' + str(i))), + 'item_defaults': [{ + 'default_warehouse': default_warehouse, + 'company': defaults.get('company_name') + }] + }).insert() + + except frappe.NameError: + pass + else: + if args.get('item_price_' + str(i)): + item_price = flt(args.get('tem_price_' + str(i))) + + price_list_name = frappe.db.get_value('Price List', {'selling': 1}) + make_item_price(item, price_list_name, item_price) + price_list_name = frappe.db.get_value('Price List', {'buying': 1}) + make_item_price(item, price_list_name, item_price) + +def make_item_price(item, price_list_name, item_price): + frappe.get_doc({ + 'doctype': 'Item Price', + 'price_list': price_list_name, + 'item_code': item, + 'price_list_rate': item_price + }).insert() + def get_timeline_data(doctype, name): '''returns timeline data based on stock ledger entry''' out = {} diff --git a/erpnext/utilities/onboarding_utils.py b/erpnext/utilities/onboarding_utils.py deleted file mode 100644 index 6c40f81b92..0000000000 --- a/erpnext/utilities/onboarding_utils.py +++ /dev/null @@ -1,228 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe, erpnext - -import json -from frappe import _ -from frappe.utils import flt - -@frappe.whitelist() -def create_customers(args_data): - args = json.loads(args_data) - defaults = frappe.defaults.get_defaults() - for i in range(1,args.get('max_count')): - customer = args.get("customer_name_" + str(i)) - if customer: - try: - doc = frappe.get_doc({ - "doctype": "Customer", - "customer_name": customer, - "customer_type": "Company", - "customer_group": _("Commercial"), - "territory": defaults.get("country"), - "company": defaults.get("company") - }).insert() - - if args.get("customer_email_" + str(i)): - create_contact(customer, "Customer", - doc.name, args.get("customer_email_" + str(i))) - except frappe.NameError: - pass - -@frappe.whitelist() -def create_letterhead(args_data): - args = json.loads(args_data) - letterhead = args.get("letterhead") - if letterhead: - try: - frappe.get_doc({ - "doctype": "Letter Head", - "image": letterhead, - "letter_head_name": _("Standard"), - "is_default": 1 - }).insert() - except frappe.NameError: - pass - -@frappe.whitelist() -def create_suppliers(args_data): - args = json.loads(args_data) - defaults = frappe.defaults.get_defaults() - for i in range(1,args.get('max_count')): - supplier = args.get("supplier_name_" + str(i)) - if supplier: - try: - doc = frappe.get_doc({ - "doctype":"Supplier", - "supplier_name": supplier, - "supplier_group": _("Local"), - "company": defaults.get("company") - }).insert() - - if args.get("supplier_email_" + str(i)): - create_contact(supplier, "Supplier", - doc.name, args.get("supplier_email_" + str(i))) - except frappe.NameError: - pass - -def create_contact(contact, party_type, party, email): - """Create contact based on given contact name""" - contact = contact.split(" ") - - contact = frappe.get_doc({ - "doctype": "Contact", - "first_name": contact[0], - "last_name": len(contact) > 1 and contact[1] or "" - }) - contact.append('email_ids', dict(email_id=email, is_primary=1)) - contact.append('links', dict(link_doctype=party_type, link_name=party)) - contact.insert() - -@frappe.whitelist() -def create_items(args_data): - args = json.loads(args_data) - defaults = frappe.defaults.get_defaults() - for i in range(1, args.get('max_count')): - item = args.get("item_" + str(i)) - if item: - default_warehouse = "" - default_warehouse = frappe.db.get_value("Warehouse", filters={ - "warehouse_name": _("Finished Goods"), - "company": defaults.get("company_name") - }) - - try: - frappe.get_doc({ - "doctype":"Item", - "item_code": item, - "item_name": item, - "description": item, - "show_in_website": 1, - "is_sales_item": 1, - "is_purchase_item": 1, - "is_stock_item": 1, - "item_group": _("Products"), - "stock_uom": _(args.get("item_uom_" + str(i))), - "item_defaults": [{ - "default_warehouse": default_warehouse, - "company": defaults.get("company_name") - }] - }).insert() - - except frappe.NameError: - pass - else: - if args.get("item_price_" + str(i)): - item_price = flt(args.get("item_price_" + str(i))) - - price_list_name = frappe.db.get_value("Price List", {"selling": 1}) - make_item_price(item, price_list_name, item_price) - price_list_name = frappe.db.get_value("Price List", {"buying": 1}) - make_item_price(item, price_list_name, item_price) - - -def make_item_price(item, price_list_name, item_price): - frappe.get_doc({ - "doctype": "Item Price", - "price_list": price_list_name, - "item_code": item, - "price_list_rate": item_price - }).insert() - -# Education -@frappe.whitelist() -def create_program(args_data): - args = json.loads(args_data) - for i in range(1,args.get('max_count')): - if args.get("program_" + str(i)): - program = frappe.new_doc("Program") - program.program_code = args.get("program_" + str(i)) - program.program_name = args.get("program_" + str(i)) - try: - program.save() - except frappe.DuplicateEntryError: - pass - -@frappe.whitelist() -def create_course(args_data): - args = json.loads(args_data) - for i in range(1,args.get('max_count')): - if args.get("course_" + str(i)): - course = frappe.new_doc("Course") - course.course_code = args.get("course_" + str(i)) - course.course_name = args.get("course_" + str(i)) - try: - course.save() - except frappe.DuplicateEntryError: - pass - -@frappe.whitelist() -def create_instructor(args_data): - args = json.loads(args_data) - for i in range(1,args.get('max_count')): - if args.get("instructor_" + str(i)): - instructor = frappe.new_doc("Instructor") - instructor.instructor_name = args.get("instructor_" + str(i)) - try: - instructor.save() - except frappe.DuplicateEntryError: - pass - -@frappe.whitelist() -def create_room(args_data): - args = json.loads(args_data) - for i in range(1,args.get('max_count')): - if args.get("room_" + str(i)): - room = frappe.new_doc("Room") - room.room_name = args.get("room_" + str(i)) - room.seating_capacity = args.get("room_capacity_" + str(i)) - try: - room.save() - except frappe.DuplicateEntryError: - pass - -@frappe.whitelist() -def create_users(args_data): - if frappe.session.user == 'Administrator': - return - args = json.loads(args_data) - defaults = frappe.defaults.get_defaults() - for i in range(1,args.get('max_count')): - email = args.get("user_email_" + str(i)) - fullname = args.get("user_fullname_" + str(i)) - if email: - if not fullname: - fullname = email.split("@")[0] - - parts = fullname.split(" ", 1) - - user = frappe.get_doc({ - "doctype": "User", - "email": email, - "first_name": parts[0], - "last_name": parts[1] if len(parts) > 1 else "", - "enabled": 1, - "user_type": "System User" - }) - - # default roles - user.append_roles("Projects User", "Stock User", "Support Team") - user.flags.delay_emails = True - - if not frappe.db.get_value("User", email): - user.insert(ignore_permissions=True) - - # create employee - emp = frappe.get_doc({ - "doctype": "Employee", - "employee_name": fullname, - "user_id": email, - "status": "Active", - "company": defaults.get("company") - }) - emp.flags.ignore_mandatory = True - emp.insert(ignore_permissions = True) - -# Enumerate the setup hooks you're going to need, apart from the slides From e2bb82872caece74123073bacd450203acfb5967 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 26 Nov 2019 18:46:17 +0530 Subject: [PATCH 07/36] fix: moved slide json files to respective modules --- .../add_a_few_suppliers.json | 49 ++++++++++++++++ .../add_a_few_customers.json | 49 ++++++++++++++++ .../welcome_to_erpnext!.json | 22 +++++++ .../add_a_few_products_you_buy_or_sell.json | 57 +++++++++++++++++++ 4 files changed, 177 insertions(+) create mode 100644 erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json create mode 100644 erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json create mode 100644 erpnext/setup/setup_wizard_slide/welcome_to_erpnext!/welcome_to_erpnext!.json create mode 100644 erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json diff --git a/erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json b/erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json new file mode 100644 index 0000000000..006d139eb0 --- /dev/null +++ b/erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json @@ -0,0 +1,49 @@ +{ + "add_more_button": 1, + "app": "ERPNext", + "creation": "2019-11-15 14:45:32.626641", + "docstatus": 0, + "doctype": "Setup Wizard Slide", + "domains": [], + "help_links": [ + { + "label": "Supplier", + "video_id": "zsrrVDk6VBs" + } + ], + "idx": 0, + "image_src": "/assets/erpnext/images/illustrations/supplier.png", + "max_count": 3, + "modified": "2019-11-26 18:26:25.498325", + "modified_by": "Administrator", + "name": "Add A Few Suppliers", + "owner": "Administrator", + "ref_doctype": "Supplier", + "slide_desc": "", + "slide_fields": [ + { + "align": "", + "fieldname": "supplier_name", + "fieldtype": "Data", + "label": "Supplier Name", + "placeholder": "", + "reqd": 1 + }, + { + "align": "", + "fieldtype": "Column Break", + "reqd": 0 + }, + { + "align": "", + "fieldname": "supplier_email", + "fieldtype": "Data", + "label": "Supplier Email", + "reqd": 1 + } + ], + "slide_order": 50, + "slide_title": "Add A Few Suppliers", + "slide_type": "Create", + "submit_method": "" +} \ No newline at end of file diff --git a/erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json b/erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json new file mode 100644 index 0000000000..a0bb6fe26d --- /dev/null +++ b/erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json @@ -0,0 +1,49 @@ +{ + "add_more_button": 1, + "app": "ERPNext", + "creation": "2019-11-15 14:44:10.065014", + "docstatus": 0, + "doctype": "Setup Wizard Slide", + "domains": [], + "help_links": [ + { + "label": "Customers", + "video_id": "zsrrVDk6VBs" + } + ], + "idx": 0, + "image_src": "/assets/erpnext/images/illustrations/customer.png", + "max_count": 3, + "modified": "2019-11-26 18:26:15.888794", + "modified_by": "Administrator", + "name": "Add A Few Customers", + "owner": "Administrator", + "ref_doctype": "Customer", + "slide_desc": "", + "slide_fields": [ + { + "align": "", + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Customer Name", + "placeholder": "", + "reqd": 1 + }, + { + "align": "", + "fieldtype": "Column Break", + "reqd": 0 + }, + { + "align": "", + "fieldname": "customer_email", + "fieldtype": "Data", + "label": "Email ID", + "reqd": 1 + } + ], + "slide_order": 40, + "slide_title": "Add A Few Customers", + "slide_type": "Create", + "submit_method": "" +} \ No newline at end of file diff --git a/erpnext/setup/setup_wizard_slide/welcome_to_erpnext!/welcome_to_erpnext!.json b/erpnext/setup/setup_wizard_slide/welcome_to_erpnext!/welcome_to_erpnext!.json new file mode 100644 index 0000000000..1da9dd44e2 --- /dev/null +++ b/erpnext/setup/setup_wizard_slide/welcome_to_erpnext!/welcome_to_erpnext!.json @@ -0,0 +1,22 @@ +{ + "add_more_button": 0, + "app": "ERPNext", + "creation": "2019-11-26 17:01:26.671859", + "docstatus": 0, + "doctype": "Setup Wizard Slide", + "domains": [], + "help_links": [], + "idx": 0, + "image_src": "/assets/erpnext/images/illustrations/onboard.png", + "max_count": 0, + "modified": "2019-11-26 17:17:29.813299", + "modified_by": "Administrator", + "name": "Welcome to ERPNext!", + "owner": "Administrator", + "slide_desc": "Setting up an ERP can be overwhelming. But don't worry, we have got your back!
\nLet's setup your company.\nThis wizard will help you onboard to ERPNext in a short time!", + "slide_fields": [], + "slide_module": "Setup", + "slide_order": 10, + "slide_title": "Welcome to ERPNext!", + "slide_type": "Information" +} \ No newline at end of file diff --git a/erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json b/erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json new file mode 100644 index 0000000000..c536f7b2ca --- /dev/null +++ b/erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json @@ -0,0 +1,57 @@ +{ + "add_more_button": 1, + "app": "ERPNext", + "creation": "2019-11-15 14:41:12.007359", + "docstatus": 0, + "doctype": "Setup Wizard Slide", + "domains": [], + "help_links": [], + "idx": 0, + "image_src": "/assets/erpnext/images/illustrations/product.png", + "max_count": 3, + "modified": "2019-11-26 18:26:35.305755", + "modified_by": "Administrator", + "name": "Add A Few Products You Buy Or Sell", + "owner": "Administrator", + "ref_doctype": "Item", + "slide_desc": "", + "slide_fields": [ + { + "align": "", + "fieldname": "item", + "fieldtype": "Data", + "label": "Item", + "placeholder": "Product Name", + "reqd": 1 + }, + { + "align": "", + "fieldtype": "Column Break", + "reqd": 1 + }, + { + "align": "", + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM", + "reqd": 1 + }, + { + "align": "", + "fieldtype": "Column Break", + "reqd": 0 + }, + { + "align": "", + "fieldname": "item_price", + "fieldtype": "Currency", + "label": "Item Price", + "reqd": 1 + } + ], + "slide_order": 30, + "slide_title": "Add A Few Products You Buy Or Sell", + "slide_type": "Create", + "submit_method": "" +} \ No newline at end of file From 1511154aa894fe83b2a7612e6c527d81550f340e Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 28 Nov 2019 16:43:39 +0530 Subject: [PATCH 08/36] fix: removed stock value and account balance out of sync validation (#19729) * fix: revert value out of sync feature * fix: removed stock value and account balance out of sync validation --- erpnext/accounts/general_ledger.py | 47 +++++++++++++++--------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 2ba319d05e..feb598a2e5 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -162,33 +162,34 @@ def validate_account_for_perpetual_inventory(gl_map): frappe.throw(_("Account: {0} can only be updated via Stock Transactions") .format(account), StockAccountInvalidTransaction) - elif account_bal != stock_bal: - precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), - currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) + # This has been comment for a temporary, will add this code again on release of immutable ledger + # elif account_bal != stock_bal: + # precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), + # currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) - diff = flt(stock_bal - account_bal, precision) - error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( - stock_bal, account_bal, frappe.bold(account)) - error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) - stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") + # diff = flt(stock_bal - account_bal, precision) + # error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( + # stock_bal, account_bal, frappe.bold(account)) + # error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) + # stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") - db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') - db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') + # db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') + # db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') - journal_entry_args = { - 'accounts':[ - {'account': account, db_or_cr_warehouse_account : abs(diff)}, - {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] - } + # journal_entry_args = { + # 'accounts':[ + # {'account': account, db_or_cr_warehouse_account : abs(diff)}, + # {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] + # } - frappe.msgprint(msg="""{0}

{1}

""".format(error_reason, error_resolution), - raise_exception=StockValueAndAccountBalanceOutOfSync, - title=_('Values Out Of Sync'), - primary_action={ - 'label': _('Make Journal Entry'), - 'client_action': 'erpnext.route_to_adjustment_jv', - 'args': journal_entry_args - }) + # frappe.msgprint(msg="""{0}

{1}

""".format(error_reason, error_resolution), + # raise_exception=StockValueAndAccountBalanceOutOfSync, + # title=_('Values Out Of Sync'), + # primary_action={ + # 'label': _('Make Journal Entry'), + # 'client_action': 'erpnext.route_to_adjustment_jv', + # 'args': journal_entry_args + # }) def validate_cwip_accounts(gl_map): cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) From f51ccbf5d4e85719aa6bb7488977933dc5da0476 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 28 Nov 2019 16:44:46 +0530 Subject: [PATCH 09/36] chore: Added Quick Stock Balance to Stock Module (#19726) - Also 'Stock Balance Report' button no longer primary button --- erpnext/config/stock.py | 4 ++++ .../stock/doctype/quick_stock_balance/quick_stock_balance.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/config/stock.py b/erpnext/config/stock.py index 441a3ab4ec..e24d7b88df 100644 --- a/erpnext/config/stock.py +++ b/erpnext/config/stock.py @@ -241,6 +241,10 @@ def get_data(): "type": "doctype", "name": "Quality Inspection Template", }, + { + "type": "doctype", + "name": "Quick Stock Balance", + }, ] }, { diff --git a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.js b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.js index a6f7343388..f261fd9979 100644 --- a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.js +++ b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.js @@ -16,7 +16,7 @@ frappe.ui.form.on('Quick Stock Balance', { frm.add_custom_button(__('Stock Balance Report'), () => { frappe.set_route('query-report', 'Stock Balance', { 'item_code': frm.doc.item, 'warehouse': frm.doc.warehouse }); - }).addClass("btn-primary"); + }); } }, From 2512962f55e355be4e17ff6ea53efe91c89d921d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 Nov 2019 16:46:58 +0530 Subject: [PATCH 10/36] fix: handle None case for get_shipping_amount_from_rules (#19723) --- erpnext/accounts/doctype/shipping_rule/shipping_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index a20f5c0872..8c4efbebe8 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -70,7 +70,7 @@ class ShippingRule(Document): def get_shipping_amount_from_rules(self, value): for condition in self.get("conditions"): - if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)): + if not condition.to_value or (flt(condition.from_value) <= flt(value) <= flt(condition.to_value)): return condition.shipping_amount return 0.0 From aff678e376f4d5d9623b7c80eeb93b8b6e9e50d3 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 28 Nov 2019 18:20:48 +0530 Subject: [PATCH 11/36] fix: Changed type of column 'serial_no' in Stock Ledger Entry (#19702) --- .../stock_ledger_entry.json | 653 ++---------------- 1 file changed, 39 insertions(+), 614 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 947f94853e..c9eba71b0d 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -1,874 +1,299 @@ { "allow_copy": 1, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, "autoname": "MAT-SLE-.YYYY.-.#####", - "beta": 0, "creation": "2013-01-29 19:25:42", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Other", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "item_code", + "serial_no", + "batch_no", + "warehouse", + "posting_date", + "posting_time", + "voucher_type", + "voucher_no", + "voucher_detail_no", + "actual_qty", + "incoming_rate", + "outgoing_rate", + "stock_uom", + "qty_after_transaction", + "valuation_rate", + "stock_value", + "stock_value_difference", + "stock_queue", + "project", + "company", + "fiscal_year", + "is_cancelled", + "to_rename" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "item_code", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Item Code", - "length": 0, - "no_copy": 0, "oldfieldname": "item_code", "oldfieldtype": "Link", "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "serial_no", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, + "fieldtype": "Long Text", "in_list_view": 1, - "in_standard_filter": 0, "label": "Serial No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "batch_no", "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": "Batch No", - "length": 0, - "no_copy": 0, "oldfieldname": "batch_no", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "warehouse", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Warehouse", - "length": 0, - "no_copy": 0, "oldfieldname": "warehouse", "oldfieldtype": "Link", "options": "Warehouse", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "posting_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Posting Date", - "length": 0, - "no_copy": 0, "oldfieldname": "posting_date", "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "posting_time", "fieldtype": "Time", - "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": "Posting Time", - "length": 0, - "no_copy": 0, "oldfieldname": "posting_time", "oldfieldtype": "Time", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "voucher_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Voucher Type", - "length": 0, - "no_copy": 0, "oldfieldname": "voucher_type", "oldfieldtype": "Data", "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "voucher_no", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Voucher No", - "length": 0, - "no_copy": 0, "oldfieldname": "voucher_no", "oldfieldtype": "Data", "options": "voucher_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "voucher_detail_no", "fieldtype": "Data", - "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": "Voucher Detail No", - "length": 0, - "no_copy": 0, "oldfieldname": "voucher_detail_no", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "actual_qty", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Actual Quantity", - "length": 0, - "no_copy": 0, "oldfieldname": "actual_qty", "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "incoming_rate", "fieldtype": "Currency", - "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": "Incoming Rate", - "length": 0, - "no_copy": 0, "oldfieldname": "incoming_rate", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "outgoing_rate", "fieldtype": "Currency", - "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": "Outgoing Rate", - "length": 0, - "no_copy": 0, "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_uom", "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": "Stock UOM", - "length": 0, - "no_copy": 0, "oldfieldname": "stock_uom", "oldfieldtype": "Data", "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "qty_after_transaction", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Actual Qty After Transaction", - "length": 0, - "no_copy": 0, "oldfieldname": "bin_aqat", "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "valuation_rate", "fieldtype": "Currency", - "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": "Valuation Rate", - "length": 0, - "no_copy": 0, "oldfieldname": "valuation_rate", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_value", "fieldtype": "Currency", - "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": "Stock Value", - "length": 0, - "no_copy": 0, "oldfieldname": "stock_value", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_value_difference", "fieldtype": "Currency", - "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": "Stock Value Difference", - "length": 0, - "no_copy": 0, "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_queue", "fieldtype": "Text", "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": "Stock Queue (FIFO)", - "length": 0, - "no_copy": 0, "oldfieldname": "fcfs_stack", "oldfieldtype": "Text", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "report_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "project", "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": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "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, - "translatable": 0, - "unique": 0 + "options": "Project" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "company", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Company", - "length": 0, - "no_copy": 0, "oldfieldname": "company", "oldfieldtype": "Data", "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "fiscal_year", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Fiscal Year", - "length": 0, - "no_copy": 0, "oldfieldname": "fiscal_year", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "is_cancelled", "fieldtype": "Select", "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": "Is Cancelled", - "length": 0, - "no_copy": 0, "options": "\nNo\nYes", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "report_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "fieldname": "to_rename", "fieldtype": "Check", "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": "To Rename", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 } ], - "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 1, "icon": "fa fa-list", "idx": 1, - "image_view": 0, "in_create": 1, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-07 07:04:37.523024", + "modified": "2019-11-27 12:17:31.522675", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock User" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, "read": 1, "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Accounts Manager" } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_order": "DESC" } \ No newline at end of file From bcd02069ea2e7ee9d208ee7ac4aef6bcade16e5f Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 28 Nov 2019 18:23:45 +0530 Subject: [PATCH 12/36] fix: Validation for Suppliers in SO to PO (#19686) - Check if there is a Supplier against atleast one item in Sales Order - Validation message earlier was vague --- erpnext/selling/doctype/sales_order/sales_order.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e12b359bdf..e97a4ee461 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -834,6 +834,10 @@ def make_purchase_order(source_name, for_supplier=None, selected_items=[], targe for item in sales_order.items: if item.supplier and item.supplier not in suppliers: suppliers.append(item.supplier) + + if not suppliers: + frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order.")) + for supplier in suppliers: po =frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")}) if len(po) == 0: From 707b83940aeaa60289dc8f8f029faf9363234720 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 28 Nov 2019 18:27:17 +0530 Subject: [PATCH 13/36] fix: due date before posting date for items added to cart yesterday (#19681) --- erpnext/shopping_cart/cart.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 1236ade45f..813d0dd196 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -66,6 +66,7 @@ def place_order(): from erpnext.selling.doctype.quotation.quotation import _make_sales_order sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True)) + sales_order.payment_schedule = [] if not cint(cart_settings.allow_items_not_in_stock): for item in sales_order.get("items"): From 9f3276046ff584843cac3f1a140dbc17f9b45d8e Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 28 Nov 2019 18:29:33 +0530 Subject: [PATCH 14/36] fix: Account type in Handling Difference in Inventory account (#19674) * fix: Account type in Handling Difference in Inventory account * fix: Add Stock Adjustment account * fix: Rename account to stock adjustment --- .../ae_uae_chart_template_standard.json | 435 +++++++++--------- 1 file changed, 218 insertions(+), 217 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json index 8856c8cc90..a8afb55df6 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json @@ -1,465 +1,466 @@ { - "country_code": "ae", - "name": "U.A.E - Chart of Accounts", + "country_code": "ae", + "name": "U.A.E - Chart of Accounts", "tree": { "Assets": { "Current Assets": { "Accounts Receivable": { "Corporate Credit Cards": { "account_type": "Receivable" - }, + }, "Other Receivable": { "Accrued Rebates Due from Suppliers": { "account_type": "Receivable" - }, + }, "Accrued Income from Suppliers": { "account_type": "Receivable" - }, + }, "Other Debtors": { "account_type": "Receivable" - }, + }, "account_type": "Receivable" - }, + }, "Post Dated Cheques Received": { "account_type": "Receivable" - }, + }, "Staff Receivable": { "account_type": "Receivable" - }, + }, "Trade Receivable": { "account_type": "Receivable" - }, + }, "Trade in Opening Fees": { "account_type": "Receivable" - }, + }, "account_type": "Receivable" - }, + }, "Cash in Hand & Banks": { "Banks": { - "Bank Margin On LC & LG": {}, - "Banks Blocked Deposits": {}, - "Banks Call Deposit Accounts": {}, + "Bank Margin On LC & LG": {}, + "Banks Blocked Deposits": {}, + "Banks Call Deposit Accounts": {}, "Banks Current Accounts": { "account_type": "Bank" - }, + }, "account_type": "Bank" - }, + }, "Cash in Hand": { "Cash in Safe": { "Main Safe": { "account_type": "Cash" - }, + }, "Main Safe - Foreign Currency": { "account_type": "Cash" } - }, + }, "Petty Cash": { "Petty Cash - Administration": { "account_type": "Cash" - }, + }, "Petty Cash - Others": { "account_type": "Cash" } - }, + }, "account_type": "Cash" - }, + }, "Cash in Transit": { "Credit Cards": { "Gateway Credit Cards": { "account_type": "Bank" - }, + }, "Manual Visa & Master Cards": { "account_type": "Bank" - }, + }, "PayPal Account": { "account_type": "Bank" - }, + }, "Visa & Master Credit Cards": { "account_type": "Bank" } } } - }, + }, "Inventory": { "Consigned Stock": { - "Handling Difference in Inventory": { - "account_type": "Stock Adjustment" - }, + "Handling Difference in Inventory": {}, "Items Delivered to Customs on temporary Base": {} - }, + }, "Stock in Hand": { "account_type": "Stock" } - }, + }, "Preliminary and Preoperating Expenses": { "Preoperating Expenses": {} - }, + }, "Prepayments & Deposits": { "Deposits": { - "Deposit - Office Rent": {}, - "Deposit Others": {}, - "Deposit to Immigration (Visa)": {}, + "Deposit - Office Rent": {}, + "Deposit Others": {}, + "Deposit to Immigration (Visa)": {}, "Deposits - Customs": {} - }, + }, "Prepaid Taxes": { - "Sales Taxes Receivables": {}, + "Sales Taxes Receivables": {}, "Withholding Tax Receivables": {} - }, + }, "Prepayments": { - "Other Prepayments": {}, - "PrePaid Advertisement Expenses": {}, - "Prepaid Bank Guarantee": {}, - "Prepaid Consultancy Fees": {}, - "Prepaid Employees Housing": {}, - "Prepaid Finance charge for Loans": {}, - "Prepaid Legal Fees": {}, - "Prepaid License Fees": {}, - "Prepaid Life Insurance": {}, - "Prepaid Maintenance": {}, - "Prepaid Medical Insurance": {}, - "Prepaid Office Rent": {}, - "Prepaid Other Insurance": {}, - "Prepaid Schooling Fees": {}, - "Prepaid Site Hosting Fees": {}, + "Other Prepayments": {}, + "PrePaid Advertisement Expenses": {}, + "Prepaid Bank Guarantee": {}, + "Prepaid Consultancy Fees": {}, + "Prepaid Employees Housing": {}, + "Prepaid Finance charge for Loans": {}, + "Prepaid Legal Fees": {}, + "Prepaid License Fees": {}, + "Prepaid Life Insurance": {}, + "Prepaid Maintenance": {}, + "Prepaid Medical Insurance": {}, + "Prepaid Office Rent": {}, + "Prepaid Other Insurance": {}, + "Prepaid Schooling Fees": {}, + "Prepaid Site Hosting Fees": {}, "Prepaid Sponsorship Fees": {} } } - }, + }, "Long Term Assets": { "Fixed Assets": { "Accumulated Depreciation": { "Acc. Depreciation of Motor Vehicles": { "account_type": "Accumulated Depreciation" - }, + }, "Acc. Deprn.Computer Hardware & Software": { "account_type": "Accumulated Depreciation" - }, + }, "Acc.Deprn.of Furniture & Office Equipment": { "account_type": "Accumulated Depreciation" - }, + }, "Amortisation on Leasehold Improvement": { "account_type": "Accumulated Depreciation" - }, + }, "account_type": "Accumulated Depreciation" - }, + }, "Fixed Assets (Cost Price)": { "Computer Hardware & Software": { "account_type": "Fixed Asset" - }, + }, "Furniture and Equipment": { "account_type": "Fixed Asset" - }, - "Leasehold Improvement": {}, + }, + "Leasehold Improvement": {}, "Motor Vehicles": { "account_type": "Fixed Asset" - }, - "Work In Progress": {}, + }, + "Work In Progress": {}, "account_type": "Fixed Asset" } - }, + }, "Intangible Assets": { - "Computer Card Renewal": {}, - "Disposal of Outlets": {}, + "Computer Card Renewal": {}, + "Disposal of Outlets": {}, "Registration of Trademarks": {} - }, - "Intercompany Accounts": {}, + }, + "Intercompany Accounts": {}, "Investments": { "Investments in Subsidiaries": {} } - }, + }, "root_type": "Asset" - }, + }, "Closing And Temporary Accounts": { "Closing Accounts": { "Closing Account": {} - }, + }, "root_type": "Liability" - }, + }, "Expenses": { "Commercial Expenses": { - "Consultancy Fees": {}, + "Consultancy Fees": {}, "Provision for Doubtful Debts": {} - }, + }, "Cost of Sale": { "Cost Of Goods Sold": { - "Cost Of Goods Sold I/C Sales": {}, + "Cost Of Goods Sold I/C Sales": {}, "Cost of Goods Sold in Trading": { "account_type": "Cost of Goods Sold" - }, + }, "account_type": "Cost of Goods Sold" - }, + }, "Expenses Included In Valuation": { "account_type": "Expenses Included In Valuation" + }, + "Stock Adjustment": { + "account_type": "Stock Adjustment" } - }, + }, "Depreciation": { "Depreciation & Amortization": { - "Amortization on Leasehold Improvement": {}, + "Amortization on Leasehold Improvement": {}, "Depreciation Of Computer Hard & Soft": { "account_type": "Depreciation" - }, + }, "Depreciation Of Furniture & Office Equipment\n\t\t\t": { "account_type": "Depreciation" - }, + }, "Depreciation Of Motor Vehicles": { "account_type": "Depreciation" } } - }, + }, "Direct Expenses": { "Financial Charges": { - "Air Miles Card Charges": {}, - "Amex Credit Cards Charges": {}, - "Bank Finance & Loan Charges": {}, - "Credit Card Charges": {}, - "Credit Card Swipe Charges": {}, + "Air Miles Card Charges": {}, + "Amex Credit Cards Charges": {}, + "Bank Finance & Loan Charges": {}, + "Credit Card Charges": {}, + "Credit Card Swipe Charges": {}, "PayPal Charges": {} } - }, + }, "MISC Charges": { "Other Charges": { "Capital Loss": { - "Disposal of Business Branch": {}, - "Loss On Fixed Assets Disposal": {}, + "Disposal of Business Branch": {}, + "Loss On Fixed Assets Disposal": {}, "Loss on Difference on Exchange": {} - }, + }, "Other Non Operating Exp": { "Other Non Operating Expenses": {} - }, + }, "Previous Year Adjustments": { "Previous Year Adjustments Account": {} - }, + }, "Royalty Fees": { "Royalty to Parent Co.": {} - }, + }, "Tax / Zakat Expenses": { "Income Tax": { "account_type": "Tax" - }, - "Zakat": {}, + }, + "Zakat": {}, "account_type": "Tax" } } - }, + }, "Share Resources": { "Share Resource Expenses Account": {} - }, + }, "Store Operating Expenses": { "Selling, General & Admin Expenses": { "Advertising Expenses": { "Other - Advertising Expenses": {} - }, + }, "Bank & Finance Charges": { "Other Bank Charges": {} - }, + }, "Communications": { - "Courier": {}, - "Others - Communication": {}, - "Telephone": {}, + "Courier": {}, + "Others - Communication": {}, + "Telephone": {}, "Web Site Hosting Fees": {} - }, + }, "Office & Various Expenses": { - "Cleaning": {}, - "Conveyance Expenses": {}, - "Gifts & Donations": {}, - "Insurance": {}, - "Kitchen and Buffet Expenses": {}, - "Maintenance": {}, - "Others - Office Various Expenses": {}, - "Security & Guard": {}, - "Stationary From Suppliers": {}, - "Stationary Out Of Stock": {}, - "Subscriptions": {}, - "Training": {}, + "Cleaning": {}, + "Conveyance Expenses": {}, + "Gifts & Donations": {}, + "Insurance": {}, + "Kitchen and Buffet Expenses": {}, + "Maintenance": {}, + "Others - Office Various Expenses": {}, + "Security & Guard": {}, + "Stationary From Suppliers": {}, + "Stationary Out Of Stock": {}, + "Subscriptions": {}, + "Training": {}, "Vehicle Expenses": {} - }, + }, "Personnel Cost": { - "Basic Salary": {}, - "End Of Service Indemnity": {}, - "Housing Allowance": {}, - "Leave Salary": {}, - "Leave Ticket": {}, - "Life Insurance": {}, - "Medical Insurance": {}, - "Personnel Cost Others": {}, - "Sales Commission": {}, - "Staff School Allowances": {}, - "Transportation Allowance": {}, - "Uniform": {}, + "Basic Salary": {}, + "End Of Service Indemnity": {}, + "Housing Allowance": {}, + "Leave Salary": {}, + "Leave Ticket": {}, + "Life Insurance": {}, + "Medical Insurance": {}, + "Personnel Cost Others": {}, + "Sales Commission": {}, + "Staff School Allowances": {}, + "Transportation Allowance": {}, + "Uniform": {}, "Visa Expenses": {} - }, + }, "Professional & Legal Fees": { - "Audit Fees": {}, - "Legal fees": {}, - "Others - Professional Fees": {}, - "Sponsorship Fees": {}, + "Audit Fees": {}, + "Legal fees": {}, + "Others - Professional Fees": {}, + "Sponsorship Fees": {}, "Trade License Fees": {} - }, + }, "Provision & Write Off": { - "Amortisation of Preoperating Expenses": {}, - "Cash Shortage": {}, - "Others - Provision & Write off": {}, - "Write Off Inventory": {}, + "Amortisation of Preoperating Expenses": {}, + "Cash Shortage": {}, + "Others - Provision & Write off": {}, + "Write Off Inventory": {}, "Write Off Receivables & Payables": {} - }, + }, "Rent Expenses": { - "Office Rent": {}, + "Office Rent": {}, "Warehouse Rent": {} - }, + }, "Travel Expenses": { - "Air tickets": {}, - "Hotel": {}, - "Meals": {}, - "Others": {}, + "Air tickets": {}, + "Hotel": {}, + "Meals": {}, + "Others": {}, "Per Diem": {} - }, + }, "Utilities": { - "Other Utility Cahrges": {}, + "Other Utility Cahrges": {}, "Water & Electricity": {} } } - }, + }, "root_type": "Expense" - }, + }, "Liabilities": { "Current Liabilities": { "Accounts Payable": { "Payables": { "Advance Payable to Suppliers": { "account_type": "Payable" - }, + }, "Consigned Payable": { "account_type": "Payable" - }, + }, "Other Payable": { "account_type": "Payable" - }, + }, "Post Dated Cheques Paid": { "account_type": "Payable" - }, - "Staff Payable": {}, + }, + "Staff Payable": {}, "Suppliers Price Protection": { "account_type": "Payable" - }, + }, "Trade Payable": { "account_type": "Payable" - }, + }, "account_type": "Payable" } - }, + }, "Accruals & Provisions": { "Accruals": { "Accrued Personnel Cost": { - "Accrued - Commissions": {}, - "Accrued - Leave Salary": {}, - "Accrued - Leave Tickets": {}, - "Accrued - Salaries": {}, - "Accrued Other Personnel Cost": {}, - "Accrued Salaries Increment": {}, + "Accrued - Commissions": {}, + "Accrued - Leave Salary": {}, + "Accrued - Leave Tickets": {}, + "Accrued - Salaries": {}, + "Accrued Other Personnel Cost": {}, + "Accrued Salaries Increment": {}, "Accrued-Staff Bonus": {} } - }, + }, "Accrued Expenses": { "Accrued Other Expenses": { - "Accrued - Audit Fees": {}, - "Accrued - Office Rent": {}, - "Accrued - Sponsorship": {}, - "Accrued - Telephone": {}, - "Accrued - Utilities": {}, + "Accrued - Audit Fees": {}, + "Accrued - Office Rent": {}, + "Accrued - Sponsorship": {}, + "Accrued - Telephone": {}, + "Accrued - Utilities": {}, "Accrued Others": {} } - }, + }, "Other Current Liabilities": { - "Accrued Dubai Customs": {}, - "Deferred income": {}, + "Accrued Dubai Customs": {}, + "Deferred income": {}, "Shipping & Handling": {} - }, + }, "Provisions": { "Tax Payables": { - "Income Tax Payable": {}, - "Sales Tax Payable": {}, + "Income Tax Payable": {}, + "Sales Tax Payable": {}, "Withholding Tax Payable": {} } - }, + }, "Short Term Loan": {} - }, + }, "Duties and Taxes": { - "account_type": "Tax", + "account_type": "Tax", "is_group": 1 - }, + }, "Reservations & Credit Notes": { "Credit Notes": { - "Credit Notes to Customers": {}, + "Credit Notes to Customers": {}, "Reservations": {} } - }, + }, "Stock Liabilities": { "Stock Received But Not Billed": { "account_type": "Stock Received But Not Billed" } - }, + }, "Unearned Income": {} - }, + }, "Long Term Liabilities": { "Long Term Loans & Provisions": {} - }, + }, "root_type": "Liability" - }, + }, "Revenue": { "Direct Revenue": { "Other Direct Revenue": { "Other Revenue - Operating": { - "Advertising Income": {}, - "Branding Income": {}, - "Early Setmt Margin from Suppliers": {}, - "Marketing Rebate from Suppliers": {}, - "Rebate from Suppliers": {}, - "Service Income": {}, + "Advertising Income": {}, + "Branding Income": {}, + "Early Setmt Margin from Suppliers": {}, + "Marketing Rebate from Suppliers": {}, + "Rebate from Suppliers": {}, + "Service Income": {}, "Space Rental Income": {} } } - }, + }, "Indirect Revenue": { "Other Indirect Revenue": { - "Capital Gain": {}, - "Excess In Till": {}, - "Gain On Difference Of Exchange": {}, - "Management Consultancy Fees": {}, + "Capital Gain": {}, + "Excess In Till": {}, + "Gain On Difference Of Exchange": {}, + "Management Consultancy Fees": {}, "Other Income": {} - }, + }, "Other Revenue - Non Operating": { - "Interest Revenue": {}, - "Interest from FD": {}, - "Products Listing Fees from Suppliers": {}, + "Interest Revenue": {}, + "Interest from FD": {}, + "Products Listing Fees from Suppliers": {}, "Trade Opening Fees from suppliers": {} } - }, + }, "Sales": { "Sales from Other Regions": { "Sales from Other Region": {} - }, + }, "Sales of same region": { - "Management Consultancy Fees 1": {}, - "Sales Account": {}, + "Management Consultancy Fees 1": {}, + "Sales Account": {}, "Sales of I/C": {} } - }, + }, "root_type": "Income" - }, + }, "Share Holder Equity": { "Capital": { - "Contributed Capital": {}, - "Share Capital": {}, - "Shareholders Current A/c": {}, - "Sub Ordinated Loan": {}, + "Contributed Capital": {}, + "Share Capital": {}, + "Shareholders Current A/c": {}, + "Sub Ordinated Loan": {}, "Treasury Stocks": {} - }, + }, "Retained Earnings": { - "Current Year Results": {}, - "Dividends Paid": {}, + "Current Year Results": {}, + "Dividends Paid": {}, "Previous Years Results": {} - }, - "account_type": "Equity", + }, + "account_type": "Equity", "root_type": "Equity" } } From 5c2ba0ef9c6e6424af95b841dade3e0b873184aa Mon Sep 17 00:00:00 2001 From: Joseph Marie Alba <54699674+erpjosephalba@users.noreply.github.com> Date: Thu, 28 Nov 2019 21:03:20 +0800 Subject: [PATCH 15/36] Fix: Logic bug (#19692) account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] not in ('', 1)] - if accounts[d]['is_group'] not in ('', 1) is wrong because it returns false even if the account is a group. - should be if accounts[d]['is_group'] not in ('', 0) However, the correction provided here: account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1] is more consistent with the prior statement that extracts ledger (and not group) accounts. account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1] --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 9bf5887b38..34070b01ae 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -185,7 +185,8 @@ def validate_account_types(accounts): return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)) account_types_for_group = ["Bank", "Cash", "Stock"] - account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] not in ('', 1)] + # fix logic bug + account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1] missing = list(set(account_types_for_group) - set(account_groups)) if missing: From 7cdde9364502a8bd28a744119a96b48a494d05b6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 28 Nov 2019 18:37:51 +0530 Subject: [PATCH 16/36] fix: Validation for parent cost center (#19664) * fix: Validation for parent cost center * fix: Minor modification in condition * fix: Update test cases for invalid cost center creation --- .../doctype/cost_center/cost_center.py | 7 +++++++ .../doctype/cost_center/test_cost_center.py | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py index 584e11c53f..0294e78111 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -18,6 +18,7 @@ class CostCenter(NestedSet): def validate(self): self.validate_mandatory() + self.validate_parent_cost_center() def validate_mandatory(self): if self.cost_center_name != self.company and not self.parent_cost_center: @@ -25,6 +26,12 @@ class CostCenter(NestedSet): elif self.cost_center_name == self.company and self.parent_cost_center: frappe.throw(_("Root cannot have a parent cost center")) + def validate_parent_cost_center(self): + if self.parent_cost_center: + if not frappe.db.get_value('Cost Center', self.parent_cost_center, 'is_group'): + frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format( + frappe.bold(self.parent_cost_center))) + def convert_group_to_ledger(self): if self.check_if_child_exists(): frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes")) diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py index c4fad75375..8f23d90676 100644 --- a/erpnext/accounts/doctype/cost_center/test_cost_center.py +++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py @@ -1,12 +1,26 @@ # 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('Cost Center') +class TestCostCenter(unittest.TestCase): + def test_cost_center_creation_against_child_node(self): + if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}): + frappe.get_doc(test_records[1]).insert() + + cost_center = frappe.get_doc({ + 'doctype': 'Cost Center', + 'cost_center_name': '_Test Cost Center 3', + 'parent_cost_center': '_Test Cost Center 2 - _TC', + 'is_group': 0, + 'company': '_Test Company' + }) + + self.assertRaises(frappe.ValidationError, cost_center.save) def create_cost_center(**args): args = frappe._dict(args) From f3b393f5e0e4cd7e0864be1c9791301c980bd841 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 28 Nov 2019 18:52:16 +0530 Subject: [PATCH 17/36] fix: UOM was not fetching in purchase invoice (#19732) * fix: UOM was not fetching in purchase invoice * fix: Changes requested Co-authored-by: Marica --- .../purchase_invoice/purchase_invoice.js | 17 ----------------- .../doctype/asset_category/asset_category.py | 3 ++- erpnext/stock/get_item_details.py | 8 +++++++- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index e4e2c7b10f..d7e64cf36f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -330,23 +330,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ frm: cur_frm }) }, - - item_code: function(frm, cdt, cdn) { - var row = locals[cdt][cdn]; - if(row.item_code) { - frappe.call({ - method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account", - args: { - "item": row.item_code, - "fieldname": "fixed_asset_account", - "company": frm.doc.company - }, - callback: function(r, rt) { - frappe.model.set_value(cdt, cdn, "expense_account", r.message); - } - }) - } - } }); cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice); diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 2a42894623..fc08841be9 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -29,7 +29,8 @@ def get_asset_category_account(fieldname, item=None, asset=None, account=None, a account=None if not account: - asset_category, company = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) + asset_details = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) + asset_category, company = asset_details or [None, None] account = frappe.db.get_value("Asset Category Account", filters={"parent": asset_category, "company_name": company}, fieldname=fieldname) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 9f47edc774..55f4be136b 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -254,6 +254,12 @@ def get_basic_details(args, item, overwrite_warehouse=True): args['material_request_type'] = frappe.db.get_value('Material Request', args.get('name'), 'material_request_type', cache=True) + expense_account = None + + if args.get('doctype') == 'Purchase Invoice' and item.is_fixed_asset: + from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account + expense_account = get_asset_category_account(fieldname = "fixed_asset_account", item = args.item_code, company= args.company) + #Set the UOM to the Default Sales UOM or Default Purchase UOM if configured in the Item Master if not args.uom: if args.get('doctype') in sales_doctypes: @@ -271,7 +277,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "image": cstr(item.image).strip(), "warehouse": warehouse, "income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults), - "expense_account": get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults), + "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) , "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults), 'has_serial_no': item.has_serial_no, 'has_batch_no': item.has_batch_no, From 8f48896261615f2ea9880e3ccb9d6d538e59d053 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 28 Nov 2019 19:39:49 +0530 Subject: [PATCH 18/36] fix: Permission issue in Stock Entry (#19738) --- erpnext/accounts/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 94697be02f..89c8467da2 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -569,7 +569,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None) warehouse_account = get_warehouse_account_map(company) - account_balance = get_balance_on(account, posting_date, in_account_currency=False) + account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True) related_warehouses = [wh for wh, wh_details in warehouse_account.items() if wh_details.account == account and not wh_details.is_group] From 9d6d95c4a20f5ef6ef6a366473c461de934a91d7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Fri, 29 Nov 2019 13:26:52 +0530 Subject: [PATCH 19/36] fix: valuation of "finished good" item in purchase receipt (#19268) * fix: Remove redundant purchase orders and unwanted condition * fix: [WIP] Purchase receipt value * fix: Add raw material cost based on transfered raw material * fix: get_qty_to_be_received * fix: Remove debugger statement * fix: Reset rm_supp_cost before setting subcontracted raw_materials * test: Fix and modify tests for backflush_based_on_stock_entry * fix: Add non stock items to Purchase Receipt from Purchase Order * fix: Ignore valuation rate check for non stock raw material * fix: Rename check all rows * fix: Remove amount from test * test: Fix item rate error * fix: handling of serial nos in backflush * fix: Add serial no. of raw materials * fix: [WIP] Handle Batch nos for purchase reciept backflushed raw material * fix: Raw material batch number selection in purchase receipt * Update test_purchase_order.py --- .../doctype/purchase_order/purchase_order.js | 3 + .../purchase_order/test_purchase_order.py | 53 ++- erpnext/controllers/buying_controller.py | 336 +++++++++++++++--- .../bom_update_tool/bom_update_tool.py | 4 +- ..._order_items_to_be_received_or_billed.json | 2 +- .../stock/report/stock_ledger/stock_ledger.py | 4 +- 6 files changed, 322 insertions(+), 80 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index c5fa98da09..7b5e5c5cca 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -18,6 +18,7 @@ frappe.ui.form.on("Purchase Order", { return { filters: { "company": frm.doc.company, + "name": ['!=', frm.doc.supplier_warehouse], "is_group": 0 } } @@ -283,6 +284,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }) } + me.dialog.get_field('sub_con_rm_items').check_all_rows() + me.dialog.show() this.dialog.set_primary_action(__('Transfer'), function() { me.values = me.dialog.get_values(); diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 4506db6405..a0a1e8ed5c 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -519,47 +519,62 @@ class TestPurchaseOrder(unittest.TestCase): def test_backflush_based_on_stock_entry(self): item_code = "_Test Subcontracted FG Item 1" make_subcontracted_item(item_code) + make_item('Sub Contracted Raw Material 1', { + 'is_stock_item': 1, + 'is_sub_contracted_item': 1 + }) update_backflush_based_on("Material Transferred for Subcontract") - po = create_purchase_order(item_code=item_code, qty=1, + + order_qty = 5 + po = create_purchase_order(item_code=item_code, qty=order_qty, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") - make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code = "Test Extra Item 1", qty=100, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code = "Test Extra Item 2", qty=10, basic_rate=100) + make_stock_entry(target="_Test Warehouse - _TC", + item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100) - rm_item = [ - {"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item", - "qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"}, + rm_items = [ + {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item", + "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, {"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100", - "qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}, + "qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, {"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1", - "qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}] + "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, + {'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos', + 'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}] - rm_item_string = json.dumps(rm_item) + rm_item_string = json.dumps(rm_items) se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) - se.append('items', { - 'item_code': "Test Extra Item 2", - "qty": 1, - "rate": 100, - "s_warehouse": "_Test Warehouse - _TC", - "t_warehouse": "_Test Warehouse 1 - _TC" - }) - se.set_missing_values() se.submit() pr = make_purchase_receipt(po.name) + + received_qty = 2 + # partial receipt + pr.get('items')[0].qty = received_qty pr.save() pr.submit() - se_items = sorted([d.item_code for d in se.get('items')]) - supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) + transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name]) + issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) + + self.assertEquals(transferred_items, issued_items) + self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000) + + + transferred_rm_map = frappe._dict() + for item in rm_items: + transferred_rm_map[item.get('rm_item_code')] = item + + for item in pr.get('supplied_items'): + self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty) - self.assertEquals(se_items, supplied_items) update_backflush_based_on("BOM") def test_advance_payment_entry_unlink_against_purchase_order(self): diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index d12643af82..3ec7aff9cb 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -221,7 +221,7 @@ class BuyingController(StockController): "backflush_raw_materials_of_subcontract_based_on") if (self.doctype == 'Purchase Receipt' and backflush_raw_materials_based_on != 'BOM'): - self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table) + self.update_raw_materials_supplied_based_on_stock_entries() else: for item in self.get("items"): if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: @@ -241,41 +241,95 @@ class BuyingController(StockController): if self.is_subcontracted == "No" and self.get("supplied_items"): self.set('supplied_items', []) - def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table): - self.set(raw_material_table, []) - purchase_orders = [d.purchase_order for d in self.items] - if purchase_orders: - items = get_subcontracted_raw_materials_from_se(purchase_orders) - backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name) + def update_raw_materials_supplied_based_on_stock_entries(self): + self.set('supplied_items', []) - for d in items: - qty = d.qty - backflushed_raw_materials.get(d.item_code, 0) - rm = self.append(raw_material_table, {}) - rm.rm_item_code = d.item_code - rm.item_name = d.item_name - rm.main_item_code = d.main_item_code - rm.description = d.description - rm.stock_uom = d.stock_uom - rm.required_qty = qty - rm.consumed_qty = qty - rm.serial_no = d.serial_no - rm.batch_no = d.batch_no + purchase_orders = set([d.purchase_order for d in self.items]) - # get raw materials rate - from erpnext.stock.utils import get_incoming_rate - rm.rate = get_incoming_rate({ - "item_code": d.item_code, - "warehouse": self.supplier_warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, - "qty": -1 * qty, - "serial_no": rm.serial_no - }) - if not rm.rate: - rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse, - self.doctype, self.name, currency=self.company_currency, company = self.company) + # qty of raw materials backflushed (for each item per purchase order) + backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders) - rm.amount = qty * flt(rm.rate) + # qty of "finished good" item yet to be received + qty_to_be_received_map = get_qty_to_be_received(purchase_orders) + + for item in self.get('items'): + # reset raw_material cost + item.rm_supp_cost = 0 + + # qty of raw materials transferred to the supplier + transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code) + + non_stock_items = get_non_stock_items(item.purchase_order, item.item_code) + + item_key = '{}{}'.format(item.item_code, item.purchase_order) + + fg_yet_to_be_received = qty_to_be_received_map.get(item_key) + + raw_material_data = backflushed_raw_materials_map.get(item_key, {}) + + consumed_qty = raw_material_data.get('qty', 0) + consumed_serial_nos = raw_material_data.get('serial_nos', '') + consumed_batch_nos = raw_material_data.get('batch_nos', '') + + transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code) + backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code) + + for raw_material in transferred_raw_materials + non_stock_items: + transferred_qty = raw_material.qty + + rm_qty_to_be_consumed = transferred_qty - consumed_qty + + # backflush all remaining transferred qty in the last Purchase Receipt + if fg_yet_to_be_received == item.qty: + qty = rm_qty_to_be_consumed + else: + qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty + + if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'): + qty = frappe.utils.ceil(qty) + + if qty > rm_qty_to_be_consumed: + qty = rm_qty_to_be_consumed + + if not qty: continue + + if raw_material.serial_nos: + set_serial_nos(raw_material, consumed_serial_nos, qty) + + if raw_material.batch_nos: + batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code, + qty, transferred_batch_qty_map, backflushed_batch_qty_map) + for batch_data in batches_qty: + qty = batch_data['qty'] + raw_material.batch_no = batch_data['batch'] + self.append_raw_material_to_be_backflushed(item, raw_material, qty) + else: + self.append_raw_material_to_be_backflushed(item, raw_material, qty) + + def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty): + rm = self.append('supplied_items', {}) + rm.update(raw_material_data) + + rm.required_qty = qty + rm.consumed_qty = qty + + if not raw_material_data.get('non_stock_item'): + from erpnext.stock.utils import get_incoming_rate + rm.rate = get_incoming_rate({ + "item_code": raw_material_data.rm_item_code, + "warehouse": self.supplier_warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": -1 * qty, + "serial_no": rm.serial_no + }) + + if not rm.rate: + rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse, + self.doctype, self.name, currency=self.company_currency, company=self.company) + + rm.amount = qty * flt(rm.rate) + fg_item_doc.rm_supp_cost += rm.amount def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table): exploded_item = 1 @@ -387,9 +441,11 @@ class BuyingController(StockController): item_codes = list(set(item.item_code for item in self.get("items"))) if item_codes: - self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name - from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \ - (", ".join((["%s"]*len(item_codes))),), item_codes)] + items = frappe.get_all('Item', filters={ + 'name': ['in', item_codes], + 'is_sub_contracted_item': 1 + }) + self._sub_contracted_items = [item.name for item in items] return self._sub_contracted_items @@ -722,28 +778,72 @@ def get_items_from_bom(item_code, bom, exploded_item=1): return bom_items -def get_subcontracted_raw_materials_from_se(purchase_orders): - return frappe.db.sql(""" - select - sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description, - sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no - from `tabStock Entry` se,`tabStock Entry Detail` sed - where - se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor' - and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != '' - group by sed.item_code, sed.t_warehouse - """ % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1) +def get_subcontracted_raw_materials_from_se(purchase_order, fg_item): + common_query = """ + SELECT + sed.item_code AS rm_item_code, + SUM(sed.qty) AS qty, + sed.description, + sed.stock_uom, + sed.subcontracted_item AS main_item_code, + {serial_no_concat_syntax} AS serial_nos, + {batch_no_concat_syntax} AS batch_nos + FROM `tabStock Entry` se,`tabStock Entry Detail` sed + WHERE + se.name = sed.parent + AND se.docstatus=1 + AND se.purpose='Send to Subcontractor' + AND se.purchase_order = %s + AND IFNULL(sed.t_warehouse, '') != '' + AND sed.subcontracted_item = %s + GROUP BY sed.item_code, sed.subcontracted_item + """ + raw_materials = frappe.db.multisql({ + 'mariadb': common_query.format( + serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)", + batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)" + ), + 'postgres': common_query.format( + serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')", + batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')" + ) + }, (purchase_order, fg_item), as_dict=1) -def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt): - return frappe._dict(frappe.db.sql(""" - select - prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty - from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi - where - pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s) - and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1 - group by prsi.rm_item_code - """ % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders))) + return raw_materials + +def get_backflushed_subcontracted_raw_materials(purchase_orders): + common_query = """ + SELECT + CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key, + SUM(prsi.consumed_qty) AS qty, + {serial_no_concat_syntax} AS serial_nos, + {batch_no_concat_syntax} AS batch_nos + FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi + WHERE + pr.name = pri.parent + AND pr.name = prsi.parent + AND pri.purchase_order IN %s + AND pri.item_code = prsi.main_item_code + AND pr.docstatus = 1 + GROUP BY prsi.rm_item_code, pri.purchase_order + """ + + backflushed_raw_materials = frappe.db.multisql({ + 'mariadb': common_query.format( + serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)", + batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)" + ), + 'postgres': common_query.format( + serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')", + batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')" + ) + }, (purchase_orders, ), as_dict=1) + + backflushed_raw_materials_map = frappe._dict() + for item in backflushed_raw_materials: + backflushed_raw_materials_map.setdefault(item.item_key, item) + + return backflushed_raw_materials_map def get_asset_item_details(asset_items): asset_items_data = {} @@ -776,3 +876,125 @@ def validate_item_type(doc, fieldname, message): error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message)) frappe.throw(error_message) + +def get_qty_to_be_received(purchase_orders): + return frappe._dict(frappe.db.sql(""" + SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key, + SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received + FROM `tabPurchase Order Item` poi + WHERE + poi.`parent` in %s + GROUP BY poi.`item_code`, poi.`parent` + HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`) + """, (purchase_orders))) + +def get_non_stock_items(purchase_order, fg_item_code): + return frappe.db.sql(""" + SELECT + pois.main_item_code, + pois.rm_item_code, + item.description, + pois.required_qty AS qty, + pois.rate, + 1 as non_stock_item, + pois.stock_uom + FROM `tabPurchase Order Item Supplied` pois, `tabItem` item + WHERE + pois.`rm_item_code` = item.`name` + AND item.is_stock_item = 0 + AND pois.`parent` = %s + AND pois.`main_item_code` = %s + """, (purchase_order, fg_item_code), as_dict=1) + + +def set_serial_nos(raw_material, consumed_serial_nos, qty): + serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \ + set(get_serial_nos(consumed_serial_nos)) + if serial_nos and qty <= len(serial_nos): + raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)]) + +def get_transferred_batch_qty_map(purchase_order, fg_item): + # returns + # { + # (item_code, fg_code): { + # batch1: 10, # qty + # batch2: 16 + # }, + # } + transferred_batch_qty_map = {} + transferred_batches = frappe.db.sql(""" + SELECT + sed.batch_no, + SUM(sed.qty) AS qty, + sed.item_code + FROM `tabStock Entry` se,`tabStock Entry Detail` sed + WHERE + se.name = sed.parent + AND se.docstatus=1 + AND se.purpose='Send to Subcontractor' + AND se.purchase_order = %s + AND sed.subcontracted_item = %s + AND sed.batch_no IS NOT NULL + GROUP BY + sed.batch_no, + sed.item_code + """, (purchase_order, fg_item), as_dict=1) + + for batch_data in transferred_batches: + transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) + transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty + + return transferred_batch_qty_map + +def get_backflushed_batch_qty_map(purchase_order, fg_item): + # returns + # { + # (item_code, fg_code): { + # batch1: 10, # qty + # batch2: 16 + # }, + # } + backflushed_batch_qty_map = {} + backflushed_batches = frappe.db.sql(""" + SELECT + pris.batch_no, + SUM(pris.consumed_qty) AS qty, + pris.rm_item_code AS item_code + FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris + WHERE + pr.name = pri.parent + AND pri.parent = pris.parent + AND pri.purchase_order = %s + AND pri.item_code = pris.main_item_code + AND pr.docstatus = 1 + AND pris.main_item_code = %s + AND pris.batch_no IS NOT NULL + GROUP BY + pris.rm_item_code, pris.batch_no + """, (purchase_order, fg_item), as_dict=1) + + for batch_data in backflushed_batches: + backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) + backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty + + return backflushed_batch_qty_map + +def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map): + # Returns available batches to be backflushed based on requirements + transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {}) + backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {}) + + available_batches = [] + + for (batch, transferred_qty) in transferred_batches.items(): + backflushed_qty = backflushed_batches.get(batch, 0) + available_qty = transferred_qty - backflushed_qty + + if available_qty >= required_qty: + available_batches.append({'batch': batch, 'qty': required_qty}) + break + else: + available_batches.append({'batch': batch, 'qty': available_qty}) + required_qty -= available_qty + + return available_batches \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 2ca4d16a07..31a9fdb28a 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -9,6 +9,7 @@ from frappe import _ from six import string_types from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order from frappe.model.document import Document +import click class BOMUpdateTool(Document): def replace_bom(self): @@ -17,7 +18,8 @@ class BOMUpdateTool(Document): frappe.cache().delete_key('bom_children') bom_list = self.get_parent_boms(self.new_bom) updated_bom = [] - + with click.progressbar(bom_list) as bom_list: + pass for bom in bom_list: try: bom_obj = frappe.get_cached_doc('BOM', bom) diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json index caf7eb8863..48c0f423fd 100644 --- a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json +++ b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json @@ -15,7 +15,7 @@ "prepared_report": 0, "query": "SELECT\n\t`poi_pri`.`purchase_order` as \"Purchase Order:Link/Purchase Order:120\",\n\t`poi_pri`.`status` as \"Status:Data:120\",\n\t`poi_pri`.`transaction_date` as \"Date:Date:100\",\n\t`poi_pri`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`poi_pri`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`poi_pri`.`supplier_name` as \"Supplier Name::150\",\n\t`poi_pri`.`item_code` as \"Item Code:Link/Item:120\",\n\t`poi_pri`.`qty` as \"Qty:Float:100\",\n\t`poi_pri`.`base_amount` as \"Base Amount:Currency:100\",\n\t`poi_pri`.`received_qty` as \"Received Qty:Float:100\",\n\t`poi_pri`.`received_amount` as \"Received Qty Amount:Currency:100\",\n\t`poi_pri`.`qty_to_receive` as \"Qty to Receive:Float:100\",\n\t`poi_pri`.`amount_to_be_received` as \"Amount to Receive:Currency:100\",\n\t`poi_pri`.`billed_amount` as \"Billed Amount:Currency:100\",\n\t`poi_pri`.`amount_to_be_billed` as \"Amount To Be Billed:Currency:100\",\n\tSUM(`pii`.`qty`) AS \"Billed Qty:Float:100\",\n\t`poi_pri`.qty - SUM(`pii`.`qty`) AS \"Qty To Be Billed:Float:100\",\n\t`poi_pri`.`warehouse` as \"Warehouse:Link/Warehouse:150\",\n\t`poi_pri`.`item_name` as \"Item Name::150\",\n\t`poi_pri`.`description` as \"Description::200\",\n\t`poi_pri`.`brand` as \"Brand::100\",\n\t`poi_pri`.`project` as \"Project\",\n\t`poi_pri`.`company` as \"Company:Link/Company:\"\nFROM\n\t(SELECT\n\t\t`po`.`name` AS 'purchase_order',\n\t\t`po`.`status`,\n\t\t`po`.`company`,\n\t\t`poi`.`warehouse`,\n\t\t`poi`.`brand`,\n\t\t`poi`.`description`,\n\t\t`po`.`transaction_date`,\n\t\t`poi`.`schedule_date`,\n\t\t`po`.`supplier`,\n\t\t`po`.`supplier_name`,\n\t\t`poi`.`project`,\n\t\t`poi`.`item_code`,\n\t\t`poi`.`item_name`,\n\t\t`poi`.`qty`,\n\t\t`poi`.`base_amount`,\n\t\t`poi`.`received_qty`,\n\t\t(`poi`.billed_amt * ifnull(`po`.conversion_rate, 1)) as billed_amount,\n\t\t(`poi`.base_amount - (`poi`.billed_amt * ifnull(`po`.conversion_rate, 1))) as amount_to_be_billed,\n\t\t`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0) AS 'qty_to_receive',\n\t\t(`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0)) * `poi`.`rate` AS 'amount_to_be_received',\n\t\tSUM(`pri`.`amount`) AS 'received_amount',\n\t\t`poi`.`name` AS 'poi_name',\n\t\t`pri`.`name` AS 'pri_name'\n\tFROM\n\t\t`tabPurchase Order` po\n\t\tLEFT JOIN `tabPurchase Order Item` poi\n\t\tON `poi`.`parent` = `po`.`name`\n\t\tLEFT JOIN `tabPurchase Receipt Item` pri\n\t\tON `pri`.`purchase_order_item` = `poi`.`name`\n\t\t\tAND `pri`.`docstatus`=1\n\tWHERE\n\t\t`po`.`status` not in ('Stopped', 'Closed')\n\t\tAND `po`.`docstatus` = 1\n\t\tAND IFNULL(`poi`.`received_qty`, 0) < IFNULL(`poi`.`qty`, 0)\n\tGROUP BY `poi`.`name`\n\tORDER BY `po`.`transaction_date` ASC\n\t) poi_pri\n\tLEFT JOIN `tabPurchase Invoice Item` pii\n\tON `pii`.`po_detail` = `poi_pri`.`poi_name`\n\t\tAND `pii`.`docstatus`=1\nGROUP BY `poi_pri`.`poi_name`", "ref_doctype": "Purchase Order", - "report_name": "Purchase Order Items To Be Received or Billed1", + "report_name": "Purchase Order Items To Be Received or Billed", "report_type": "Query Report", "roles": [ { diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index db7f6ad1b9..d757ecb293 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -122,8 +122,8 @@ def get_item_details(items, sl_entries, include_uom): cf_field = cf_join = "" if include_uom: cf_field = ", ucd.conversion_factor" - cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \ - % (include_uom) + cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s" \ + % frappe.db.escape(include_uom) res = frappe.db.sql(""" select From bf8fe06f86e513edc52a59da851180c7e48ebeb3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 29 Nov 2019 14:14:11 +0530 Subject: [PATCH 20/36] fix: please specify customer error in sales invoice if patient is blank --- .../doctype/sales_invoice/sales_invoice.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 3c85210663..2ea74f6d85 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -789,22 +789,21 @@ frappe.ui.form.on('Sales Invoice', { method: "frappe.client.get_value", args:{ doctype: "Patient", - filters: {"name": frm.doc.patient}, + filters: { + "name": frm.doc.patient + }, fieldname: "customer" }, - callback:function(patient_customer) { - if(patient_customer){ - frm.set_value("customer", patient_customer.message.customer); - frm.refresh_fields(); + callback:function(r) { + if(r && r.message.customer){ + frm.set_value("customer", r.message.customer); } } }); } - else{ - frm.set_value("customer", ''); - } } }, + refresh: function(frm) { if (frappe.boot.active_domains.includes("Healthcare")){ frm.set_df_property("patient", "hidden", 0); From 45f57f273ca8d1367686cbbfeeb3d57669a9629d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 26 Nov 2019 15:13:23 +0530 Subject: [PATCH 21/36] fix: Serial no validation against sales invoice --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 70a80ca184..9d2f133da4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1048,9 +1048,14 @@ class SalesInvoice(SellingController): continue for serial_no in item.serial_no.split("\n"): - sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no, - ["sales_invoice", "item_code"]) - if sales_invoice and item_code == item.item_code and self.name != sales_invoice: + serial_no_details = frappe.db.get_value("Serial No", serial_no, + ["sales_invoice", "item_code"], as_dict=1) + + if not serial_no_details: + continue + + if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \ + and self.name != serial_no_details.sales_invoice: sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company") if sales_invoice_company == self.company: frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" From c51dd2989eb6319bddd9a1c0e0750b1905da6a2b Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 26 Nov 2019 16:12:29 +0530 Subject: [PATCH 22/36] fix: Validation msg --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9d2f133da4..def671c19b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1056,10 +1056,10 @@ class SalesInvoice(SellingController): if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \ and self.name != serial_no_details.sales_invoice: - sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company") + sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company") if sales_invoice_company == self.company: frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" - .format(serial_no, sales_invoice))) + .format(serial_no, serial_no_details.sales_invoice))) def update_project(self): if self.project: From 20c31dd579f369b260777574994ad06a4f6c8d56 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 29 Nov 2019 16:55:10 +0530 Subject: [PATCH 23/36] fix: tax templates from all companies fetching in receipt (#19682) * fix: tax templates from all companies fetching in receipt * Update purchase_receipt.js --- .../stock/doctype/purchase_receipt/purchase_receipt.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index d5914f9b28..6b5e40e628 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -34,6 +34,12 @@ frappe.ui.form.on("Purchase Receipt", { filters: {'company': frm.doc.company } } }); + + frm.set_query("taxes_and_charges", function() { + return { + filters: {'company': frm.doc.company } + } + }); }, onload: function(frm) { @@ -296,4 +302,4 @@ var validate_sample_quantity = function(frm, cdt, cdn) { } }); } -}; \ No newline at end of file +}; From 06a6fa4cfc88cbe6c05abe0000826b78ddecdfbf Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 29 Nov 2019 16:56:27 +0530 Subject: [PATCH 24/36] feat: Accounts Payable report based on payment terms (#19672) --- erpnext/accounts/report/accounts_payable/accounts_payable.js | 5 +++++ .../report/accounts_receivable/accounts_receivable.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 8eb670de51..b1f427ca7f 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = { "fieldtype": "Link", "options": "Supplier Group" }, + { + "fieldname":"based_on_payment_terms", + "label": __("Based On Payment Terms"), + "fieldtype": "Check", + }, { "fieldname":"tax_id", "label": __("Tax Id"), diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 14906f2c2e..41989bf863 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -318,7 +318,7 @@ class ReceivablePayableReport(object): self.append_payment_term(row, d, term) def append_payment_term(self, row, d, term): - if self.filters.get("customer") and d.currency == d.party_account_currency: + if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency: invoiced = d.payment_amount else: invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) From e3c05290c8f2aa58bfa7ab83723b1967dffbf1e2 Mon Sep 17 00:00:00 2001 From: Khushal Trivedi Date: Fri, 29 Nov 2019 17:00:09 +0530 Subject: [PATCH 25/36] fix: date validation on inpatient record, else condition removing on clinical prcd templ which is not req (#19691) --- .../clinical_procedure_template.py | 5 +++-- .../doctype/inpatient_record/inpatient_record.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py index 141329b3db..7cec362200 100644 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py +++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py @@ -63,10 +63,11 @@ def updating_rate(self): item_code=%s""",(self.template, self.rate, self.item)) def create_item_from_template(doc): + disabled = 1 + if(doc.is_billable == 1): disabled = 0 - else: - disabled = 1 + #insert item item = frappe.get_doc({ "doctype": "Item", diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index c107cd7335..835b38bedf 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import today, now_datetime +from frappe.utils import today, now_datetime, getdate from frappe.model.document import Document from frappe.desk.reportview import get_match_cond @@ -15,11 +15,20 @@ class InpatientRecord(Document): frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name) def validate(self): + self.validate_dates() self.validate_already_scheduled_or_admitted() if self.status == "Discharged": frappe.db.set_value("Patient", self.patient, "inpatient_status", None) frappe.db.set_value("Patient", self.patient, "inpatient_record", None) + def validate_dates(self): + if (getdate(self.scheduled_date) < getdate(today())) or \ + (getdate(self.admitted_datetime) < getdate(today())): + frappe.throw(_("Scheduled and Admitted dates can not be less than today")) + if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \ + (getdate(self.discharge_date) < getdate(self.scheduled_date)): + frappe.throw(_("Expected and Discharge dates cannot be less than Admission Schedule date")) + def validate_already_scheduled_or_admitted(self): query = """ select name, status From 98627f46f69df70e727c79b4b34169c62ded03b3 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 29 Nov 2019 17:29:38 +0530 Subject: [PATCH 26/36] fix: remove asset movement mandatory fields (#19670) * fix: pending on review date (#19609) * fix: On Specific case if no item code in name * fix: pending on review date * feat: fetch leave approver from both employee and department approvers (#19613) * fix: fetch leave approvers from both department and employee master * fix: creaate a set of approvers * fix(expense claim): fetch outstanding documents based on party account type * fix: not able to select item in sales order * fix: code cleanup * fix: asset movement ux fixes (#19641) * fix: Multiple fixes related to landed cost accounting (#19656) * fix: last purchase rate greater than selling price * Fixed Asset Refactor Review fixes (#19665) * fix: fixed asset item creation ux fixes * fix: auto creation of asset ux fixes * fix: [LCV] incorrect condition when checking assets linked with PR * fix: bulk update assets * refac: remove company level cwip enabling * cwip can be enabled only on category level * fix: #19649 * fix: remove asset movement mandatory fields * fix: label for reference doctype From 750b3a594671151d7edeba770a9a5d5906085a73 Mon Sep 17 00:00:00 2001 From: Prasad Ramesh Date: Fri, 29 Nov 2019 17:30:47 +0530 Subject: [PATCH 27/36] fix: Button label case (#19706) Json -> JSON --- erpnext/regional/report/gstr_1/gstr_1.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index 9682768280..ce559218cb 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -52,7 +52,7 @@ frappe.query_reports["GSTR-1"] = { ], onload: function (report) { - report.page.add_inner_button(__("Download as Json"), function () { + report.page.add_inner_button(__("Download as JSON"), function () { var filters = report.get_values(); const args = { From 0de066c3b190be6f8eac06f547feba0c0750226a Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Fri, 29 Nov 2019 13:02:17 +0100 Subject: [PATCH 28/36] feat(regional): Add master data to DATEV Export (#18755) * Add master data to export * add SQL statements to get customers and suppliers * make data category a string * fix SQL error * fix SQL errors * unique column names * add encoding of constants * get customer primary address and contact * fix typo * fix typo * binary response * add filename * add filecontent * rename account columns * exclude account groups * use compression, close file before transfer * fix StringIO * add basic tests * fix assertion, merge test methods * fix indentation * relative import of constants * fix path * import os * Add default currency to test company * root accounts with parent = null * move account-related things to setup() * add: test headers * company and filters become class properties * add: test csv creation * (fix): add missing account * (fix): remove wrong space * add items to sales invoice * refactor: create test data * fix: create cost center * fix: doctype Accoutn * fix: make sure account belongs to company * fix: remove customer group and territory, save on a new line * create default warehouses * fix: make Item myself * fix: item defaults are a list * fix: use my own warehouse * fix: use my own expense account * fix: let you take care of the Sales Invoice Item * fix: import zipfile * add TODOs * fix: workaround for pandas bug * SQL: utf-8 everywhere to make conversion in tests unnecessary * tests: zipfile must be encoded string * fix(tests): invalid start byte * fix(test): give is_zipfile() the file-like object it expects * fix(test): fix encoding of colums * fix(get_transactions): as_dict is 1 by default * fix(tests): allow empty data * refactor: rename columns in get_account_names * fix(pandas): keep sorting columns * fix: "lineterminator" must be a string * fix(test): check if cost center exists * fix: credit limit became a child table * fix: save company after creation * insert instead of save * tests: setup_fiscal_year * fix(test): import cstr * fix(tests): fiscal year * fix: can't concat str to bytes * fix: make csv-encoding work for py2 and py3 * fix(test): use frappe.as_unicode instead of unicode * fix: use BytesIO instead of StringIO for py3 compatibility * fix(tests): use BytesIO instead of StringIO for py3 compatibility --- erpnext/regional/report/datev/datev.py | 470 +++++++--------- .../regional/report/datev/datev_constants.py | 512 ++++++++++++++++++ erpnext/regional/report/datev/test_datev.py | 244 +++++++++ 3 files changed, 961 insertions(+), 265 deletions(-) create mode 100644 erpnext/regional/report/datev/datev_constants.py create mode 100644 erpnext/regional/report/datev/test_datev.py diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index ee8735fb1f..bd70639ef2 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -10,17 +10,26 @@ Provide a report and downloadable CSV according to the German DATEV format. from __future__ import unicode_literals import datetime import json +import zlib +import zipfile +import six +from six import BytesIO from six import string_types import frappe from frappe import _ import pandas as pd +from .datev_constants import DataCategory +from .datev_constants import Transactions +from .datev_constants import DebtorsCreditors +from .datev_constants import AccountNames +from .datev_constants import QUERY_REPORT_COLUMNS def execute(filters=None): """Entry point for frappe.""" validate(filters) - result = get_gl_entries(filters, as_dict=0) - columns = get_columns() + result = get_transactions(filters, as_dict=0) + columns = QUERY_REPORT_COLUMNS return columns, result @@ -41,65 +50,8 @@ def validate(filters): except frappe.DoesNotExistError: frappe.throw(_('Please create DATEV Settings for Company {}.').format(filters.get('company'))) -def get_columns(): - """Return the list of columns that will be shown in query report.""" - columns = [ - { - "label": "Umsatz (ohne Soll/Haben-Kz)", - "fieldname": "Umsatz (ohne Soll/Haben-Kz)", - "fieldtype": "Currency", - }, - { - "label": "Soll/Haben-Kennzeichen", - "fieldname": "Soll/Haben-Kennzeichen", - "fieldtype": "Data", - }, - { - "label": "Kontonummer", - "fieldname": "Kontonummer", - "fieldtype": "Data", - }, - { - "label": "Gegenkonto (ohne BU-Schlüssel)", - "fieldname": "Gegenkonto (ohne BU-Schlüssel)", - "fieldtype": "Data", - }, - { - "label": "Belegdatum", - "fieldname": "Belegdatum", - "fieldtype": "Date", - }, - { - "label": "Buchungstext", - "fieldname": "Buchungstext", - "fieldtype": "Text", - }, - { - "label": "Beleginfo - Art 1", - "fieldname": "Beleginfo - Art 1", - "fieldtype": "Data", - }, - { - "label": "Beleginfo - Inhalt 1", - "fieldname": "Beleginfo - Inhalt 1", - "fieldtype": "Data", - }, - { - "label": "Beleginfo - Art 2", - "fieldname": "Beleginfo - Art 2", - "fieldtype": "Data", - }, - { - "label": "Beleginfo - Inhalt 2", - "fieldname": "Beleginfo - Inhalt 2", - "fieldtype": "Data", - } - ] - return columns - - -def get_gl_entries(filters, as_dict): +def get_transactions(filters, as_dict=1): """ Get a list of accounting entries. @@ -111,7 +63,7 @@ def get_gl_entries(filters, as_dict): as_dict -- return as list of dicts [0,1] """ gl_entries = frappe.db.sql(""" - select + SELECT /* either debit or credit amount; always positive */ case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)', @@ -132,7 +84,7 @@ def get_gl_entries(filters, as_dict): gl.against_voucher_type as 'Beleginfo - Art 2', gl.against_voucher as 'Beleginfo - Inhalt 2' - from `tabGL Entry` gl + FROM `tabGL Entry` gl /* Statistisches Konto (Debitoren/Kreditoren) */ left join `tabParty Account` pa @@ -155,15 +107,127 @@ def get_gl_entries(filters, as_dict): left join `tabAccount` acc_against_pa on pa.account = acc_against_pa.name - where gl.company = %(company)s - and DATE(gl.posting_date) >= %(from_date)s - and DATE(gl.posting_date) <= %(to_date)s - order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict) + WHERE gl.company = %(company)s + AND DATE(gl.posting_date) >= %(from_date)s + AND DATE(gl.posting_date) <= %(to_date)s + ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict, as_utf8=1) return gl_entries -def get_datev_csv(data, filters): +def get_customers(filters): + """ + Get a list of Customers. + + Arguments: + filters -- dict of filters to be passed to the sql query + """ + return frappe.db.sql(""" + SELECT + + acc.account_number as 'Konto', + cus.customer_name as 'Name (Adressatentyp Unternehmen)', + case cus.customer_type when 'Individual' then 1 when 'Company' then 2 else 0 end as 'Adressatentyp', + adr.address_line1 as 'Straße', + adr.pincode as 'Postleitzahl', + adr.city as 'Ort', + UPPER(country.code) as 'Land', + adr.address_line2 as 'Adresszusatz', + con.email_id as 'E-Mail', + coalesce(con.mobile_no, con.phone) as 'Telefon', + cus.website as 'Internet', + cus.tax_id as 'Steuernummer', + ccl.credit_limit as 'Kreditlimit (Debitor)' + + FROM `tabParty Account` par + + left join `tabAccount` acc + on acc.name = par.account + + left join `tabCustomer` cus + on cus.name = par.parent + + left join `tabAddress` adr + on adr.name = cus.customer_primary_address + + left join `tabCountry` country + on country.name = adr.country + + left join `tabContact` con + on con.name = cus.customer_primary_contact + + left join `tabCustomer Credit Limit` ccl + on ccl.parent = cus.name + and ccl.company = par.company + + WHERE par.company = %(company)s + AND par.parenttype = 'Customer'""", filters, as_dict=1, as_utf8=1) + + +def get_suppliers(filters): + """ + Get a list of Suppliers. + + Arguments: + filters -- dict of filters to be passed to the sql query + """ + return frappe.db.sql(""" + SELECT + + acc.account_number as 'Konto', + sup.supplier_name as 'Name (Adressatentyp Unternehmen)', + case sup.supplier_type when 'Individual' then '1' when 'Company' then '2' else '0' end as 'Adressatentyp', + adr.address_line1 as 'Straße', + adr.pincode as 'Postleitzahl', + adr.city as 'Ort', + UPPER(country.code) as 'Land', + adr.address_line2 as 'Adresszusatz', + con.email_id as 'E-Mail', + coalesce(con.mobile_no, con.phone) as 'Telefon', + sup.website as 'Internet', + sup.tax_id as 'Steuernummer', + case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis' + + FROM `tabParty Account` par + + left join `tabAccount` acc + on acc.name = par.account + + left join `tabSupplier` sup + on sup.name = par.parent + + left join `tabDynamic Link` dyn_adr + on dyn_adr.link_name = sup.name + and dyn_adr.link_doctype = 'Supplier' + and dyn_adr.parenttype = 'Address' + + left join `tabAddress` adr + on adr.name = dyn_adr.parent + and adr.is_primary_address = '1' + + left join `tabCountry` country + on country.name = adr.country + + left join `tabDynamic Link` dyn_con + on dyn_con.link_name = sup.name + and dyn_con.link_doctype = 'Supplier' + and dyn_con.parenttype = 'Contact' + + left join `tabContact` con + on con.name = dyn_con.parent + and con.is_primary_contact = '1' + + WHERE par.company = %(company)s + AND par.parenttype = 'Supplier'""", filters, as_dict=1, as_utf8=1) + + +def get_account_names(filters): + return frappe.get_list("Account", + fields=["account_number as Konto", "name as Kontenbeschriftung"], + filters={"company": filters.get("company"), "is_group": "0"}) + + +def get_datev_csv(data, filters, csv_class): """ Fill in missing columns and return a CSV in DATEV Format. @@ -174,7 +238,46 @@ def get_datev_csv(data, filters): Arguments: data -- array of dictionaries filters -- dict + csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS """ + header = get_header(filters, csv_class) + + empty_df = pd.DataFrame(columns=csv_class.COLUMNS) + data_df = pd.DataFrame.from_records(data) + + result = empty_df.append(data_df, sort=True) + + if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS: + result['Belegdatum'] = pd.to_datetime(result['Belegdatum']) + + if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES: + result['Sprach-ID'] = 'de-DE' + + header = ';'.join(header).encode('latin_1') + data = result.to_csv( + # Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035 + sep=str(';'), + # European decimal seperator + decimal=',', + # Windows "ANSI" encoding + encoding='latin_1', + # format date as DDMM + date_format='%d%m', + # Windows line terminator + line_terminator='\r\n', + # Do not number rows + index=False, + # Use all columns defined above + columns=csv_class.COLUMNS + ) + + if not six.PY2: + data = data.encode('latin_1') + + return header + b'\r\n' + data + + +def get_header(filters, csv_class): header = [ # A = DATEV format # DTVF = created by DATEV software, @@ -185,18 +288,8 @@ def get_datev_csv(data, filters): # 510 = 5.10, # 720 = 7.20 "510", - # C = Data category - # 21 = Transaction batch (Buchungsstapel), - # 67 = Buchungstextkonstanten, - # 16 = Debitors/Creditors, - # 20 = Account names (Kontenbeschriftungen) - "21", - # D = Format name - # Buchungsstapel, - # Buchungstextkonstanten, - # Debitoren/Kreditoren, - # Kontenbeschriftungen - "Buchungsstapel", + csv_class.DATA_CATEGORY, + csv_class.FORMAT_NAME, # E = Format version (regarding format name) "", # F = Generated on @@ -224,16 +317,17 @@ def get_datev_csv(data, filters): # P = Transaction batch end date (YYYYMMDD) frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"), # Q = Description (for example, "January - February 2019 Transactions") - "{} - {} Buchungsstapel".format( - frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"), - frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy") + "{} - {} {}".format( + frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"), + frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy"), + csv_class.FORMAT_NAME ), # R = Diktatkürzel "", # S = Buchungstyp # 1 = Transaction batch (Buchungsstapel), # 2 = Annual financial statement (Jahresabschluss) - "1", + "1" if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else "", # T = Rechnungslegungszweck "", # U = Festschreibung @@ -241,185 +335,8 @@ def get_datev_csv(data, filters): # V = Kontoführungs-Währungskennzeichen des Geldkontos frappe.get_value("Company", filters.get("company"), "default_currency") ] - columns = [ - # All possible columns must tbe listed here, because DATEV requires them to - # be present in the CSV. - # --- - # Umsatz - "Umsatz (ohne Soll/Haben-Kz)", - "Soll/Haben-Kennzeichen", - "WKZ Umsatz", - "Kurs", - "Basis-Umsatz", - "WKZ Basis-Umsatz", - # Konto/Gegenkonto - "Kontonummer", - "Gegenkonto (ohne BU-Schlüssel)", - "BU-Schlüssel", - # Datum - "Belegdatum", - # Belegfelder - "Belegfeld 1", - "Belegfeld 2", - # Weitere Felder - "Skonto", - "Buchungstext", - # OPOS-Informationen - "Postensperre", - "Diverse Adressnummer", - "Geschäftspartnerbank", - "Sachverhalt", - "Zinssperre", - # Digitaler Beleg - "Beleglink", - # Beleginfo - "Beleginfo - Art 1", - "Beleginfo - Inhalt 1", - "Beleginfo - Art 2", - "Beleginfo - Inhalt 2", - "Beleginfo - Art 3", - "Beleginfo - Inhalt 3", - "Beleginfo - Art 4", - "Beleginfo - Inhalt 4", - "Beleginfo - Art 5", - "Beleginfo - Inhalt 5", - "Beleginfo - Art 6", - "Beleginfo - Inhalt 6", - "Beleginfo - Art 7", - "Beleginfo - Inhalt 7", - "Beleginfo - Art 8", - "Beleginfo - Inhalt 8", - # Kostenrechnung - "Kost 1 - Kostenstelle", - "Kost 2 - Kostenstelle", - "Kost-Menge", - # Steuerrechnung - "EU-Land u. UStID", - "EU-Steuersatz", - "Abw. Versteuerungsart", - # L+L Sachverhalt - "Sachverhalt L+L", - "Funktionsergänzung L+L", - # Funktion Steuerschlüssel 49 - "BU 49 Hauptfunktionstyp", - "BU 49 Hauptfunktionsnummer", - "BU 49 Funktionsergänzung", - # Zusatzinformationen - "Zusatzinformation - Art 1", - "Zusatzinformation - Inhalt 1", - "Zusatzinformation - Art 2", - "Zusatzinformation - Inhalt 2", - "Zusatzinformation - Art 3", - "Zusatzinformation - Inhalt 3", - "Zusatzinformation - Art 4", - "Zusatzinformation - Inhalt 4", - "Zusatzinformation - Art 5", - "Zusatzinformation - Inhalt 5", - "Zusatzinformation - Art 6", - "Zusatzinformation - Inhalt 6", - "Zusatzinformation - Art 7", - "Zusatzinformation - Inhalt 7", - "Zusatzinformation - Art 8", - "Zusatzinformation - Inhalt 8", - "Zusatzinformation - Art 9", - "Zusatzinformation - Inhalt 9", - "Zusatzinformation - Art 10", - "Zusatzinformation - Inhalt 10", - "Zusatzinformation - Art 11", - "Zusatzinformation - Inhalt 11", - "Zusatzinformation - Art 12", - "Zusatzinformation - Inhalt 12", - "Zusatzinformation - Art 13", - "Zusatzinformation - Inhalt 13", - "Zusatzinformation - Art 14", - "Zusatzinformation - Inhalt 14", - "Zusatzinformation - Art 15", - "Zusatzinformation - Inhalt 15", - "Zusatzinformation - Art 16", - "Zusatzinformation - Inhalt 16", - "Zusatzinformation - Art 17", - "Zusatzinformation - Inhalt 17", - "Zusatzinformation - Art 18", - "Zusatzinformation - Inhalt 18", - "Zusatzinformation - Art 19", - "Zusatzinformation - Inhalt 19", - "Zusatzinformation - Art 20", - "Zusatzinformation - Inhalt 20", - # Mengenfelder LuF - "Stück", - "Gewicht", - # Forderungsart - "Zahlweise", - "Forderungsart", - "Veranlagungsjahr", - "Zugeordnete Fälligkeit", - # Weitere Felder - "Skontotyp", - # Anzahlungen - "Auftragsnummer", - "Buchungstyp", - "USt-Schlüssel (Anzahlungen)", - "EU-Land (Anzahlungen)", - "Sachverhalt L+L (Anzahlungen)", - "EU-Steuersatz (Anzahlungen)", - "Erlöskonto (Anzahlungen)", - # Stapelinformationen - "Herkunft-Kz", - # Technische Identifikation - "Buchungs GUID", - # Kostenrechnung - "Kost-Datum", - # OPOS-Informationen - "SEPA-Mandatsreferenz", - "Skontosperre", - # Gesellschafter und Sonderbilanzsachverhalt - "Gesellschaftername", - "Beteiligtennummer", - "Identifikationsnummer", - "Zeichnernummer", - # OPOS-Informationen - "Postensperre bis", - # Gesellschafter und Sonderbilanzsachverhalt - "Bezeichnung SoBil-Sachverhalt", - "Kennzeichen SoBil-Buchung", - # Stapelinformationen - "Festschreibung", - # Datum - "Leistungsdatum", - "Datum Zuord. Steuerperiode", - # OPOS-Informationen - "Fälligkeit", - # Konto/Gegenkonto - "Generalumkehr (GU)", - # Steuersatz für Steuerschlüssel - "Steuersatz", - "Land" - ] + return header - empty_df = pd.DataFrame(columns=columns) - data_df = pd.DataFrame.from_records(data) - - result = empty_df.append(data_df) - result['Belegdatum'] = pd.to_datetime(result['Belegdatum']) - - header = ';'.join(header).encode('latin_1') - data = result.to_csv( - sep=b';', - # European decimal seperator - decimal=',', - # Windows "ANSI" encoding - encoding='latin_1', - # format date as DDMM - date_format='%d%m', - # Windows line terminator - line_terminator=b'\r\n', - # Do not number rows - index=False, - # Use all columns defined above - columns=columns - ) - - return header + b'\r\n' + data @frappe.whitelist() def download_datev_csv(filters=None): @@ -438,8 +355,31 @@ def download_datev_csv(filters=None): filters = json.loads(filters) validate(filters) - data = get_gl_entries(filters, as_dict=1) - frappe.response['result'] = get_datev_csv(data, filters) - frappe.response['doctype'] = 'EXTF_Buchungsstapel' - frappe.response['type'] = 'csv' + # This is where my zip will be written + zip_buffer = BytesIO() + # This is my zip file + datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED) + + transactions = get_transactions(filters) + transactions_csv = get_datev_csv(transactions, filters, csv_class=Transactions) + datev_zip.writestr('EXTF_Buchungsstapel.csv', transactions_csv) + + account_names = get_account_names(filters) + account_names_csv = get_datev_csv(account_names, filters, csv_class=AccountNames) + datev_zip.writestr('EXTF_Kontenbeschriftungen.csv', account_names_csv) + + customers = get_customers(filters) + customers_csv = get_datev_csv(customers, filters, csv_class=DebtorsCreditors) + datev_zip.writestr('EXTF_Kunden.csv', customers_csv) + + suppliers = get_suppliers(filters) + suppliers_csv = get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors) + datev_zip.writestr('EXTF_Lieferanten.csv', suppliers_csv) + + # You must call close() before exiting your program or essential records will not be written. + datev_zip.close() + + frappe.response['filecontent'] = zip_buffer.getvalue() + frappe.response['filename'] = 'DATEV.zip' + frappe.response['type'] = 'binary' diff --git a/erpnext/regional/report/datev/datev_constants.py b/erpnext/regional/report/datev/datev_constants.py new file mode 100644 index 0000000000..1c9bd23ee1 --- /dev/null +++ b/erpnext/regional/report/datev/datev_constants.py @@ -0,0 +1,512 @@ +# coding: utf-8 +"""Constants used in datev.py.""" + +TRANSACTION_COLUMNS = [ + # All possible columns must tbe listed here, because DATEV requires them to + # be present in the CSV. + # --- + # Umsatz + "Umsatz (ohne Soll/Haben-Kz)", + "Soll/Haben-Kennzeichen", + "WKZ Umsatz", + "Kurs", + "Basis-Umsatz", + "WKZ Basis-Umsatz", + # Konto/Gegenkonto + "Kontonummer", + "Gegenkonto (ohne BU-Schlüssel)", + "BU-Schlüssel", + # Datum + "Belegdatum", + # Belegfelder + "Belegfeld 1", + "Belegfeld 2", + # Weitere Felder + "Skonto", + "Buchungstext", + # OPOS-Informationen + "Postensperre", + "Diverse Adressnummer", + "Geschäftspartnerbank", + "Sachverhalt", + "Zinssperre", + # Digitaler Beleg + "Beleglink", + # Beleginfo + "Beleginfo - Art 1", + "Beleginfo - Inhalt 1", + "Beleginfo - Art 2", + "Beleginfo - Inhalt 2", + "Beleginfo - Art 3", + "Beleginfo - Inhalt 3", + "Beleginfo - Art 4", + "Beleginfo - Inhalt 4", + "Beleginfo - Art 5", + "Beleginfo - Inhalt 5", + "Beleginfo - Art 6", + "Beleginfo - Inhalt 6", + "Beleginfo - Art 7", + "Beleginfo - Inhalt 7", + "Beleginfo - Art 8", + "Beleginfo - Inhalt 8", + # Kostenrechnung + "Kost 1 - Kostenstelle", + "Kost 2 - Kostenstelle", + "Kost-Menge", + # Steuerrechnung + "EU-Land u. UStID", + "EU-Steuersatz", + "Abw. Versteuerungsart", + # L+L Sachverhalt + "Sachverhalt L+L", + "Funktionsergänzung L+L", + # Funktion Steuerschlüssel 49 + "BU 49 Hauptfunktionstyp", + "BU 49 Hauptfunktionsnummer", + "BU 49 Funktionsergänzung", + # Zusatzinformationen + "Zusatzinformation - Art 1", + "Zusatzinformation - Inhalt 1", + "Zusatzinformation - Art 2", + "Zusatzinformation - Inhalt 2", + "Zusatzinformation - Art 3", + "Zusatzinformation - Inhalt 3", + "Zusatzinformation - Art 4", + "Zusatzinformation - Inhalt 4", + "Zusatzinformation - Art 5", + "Zusatzinformation - Inhalt 5", + "Zusatzinformation - Art 6", + "Zusatzinformation - Inhalt 6", + "Zusatzinformation - Art 7", + "Zusatzinformation - Inhalt 7", + "Zusatzinformation - Art 8", + "Zusatzinformation - Inhalt 8", + "Zusatzinformation - Art 9", + "Zusatzinformation - Inhalt 9", + "Zusatzinformation - Art 10", + "Zusatzinformation - Inhalt 10", + "Zusatzinformation - Art 11", + "Zusatzinformation - Inhalt 11", + "Zusatzinformation - Art 12", + "Zusatzinformation - Inhalt 12", + "Zusatzinformation - Art 13", + "Zusatzinformation - Inhalt 13", + "Zusatzinformation - Art 14", + "Zusatzinformation - Inhalt 14", + "Zusatzinformation - Art 15", + "Zusatzinformation - Inhalt 15", + "Zusatzinformation - Art 16", + "Zusatzinformation - Inhalt 16", + "Zusatzinformation - Art 17", + "Zusatzinformation - Inhalt 17", + "Zusatzinformation - Art 18", + "Zusatzinformation - Inhalt 18", + "Zusatzinformation - Art 19", + "Zusatzinformation - Inhalt 19", + "Zusatzinformation - Art 20", + "Zusatzinformation - Inhalt 20", + # Mengenfelder LuF + "Stück", + "Gewicht", + # Forderungsart + "Zahlweise", + "Forderungsart", + "Veranlagungsjahr", + "Zugeordnete Fälligkeit", + # Weitere Felder + "Skontotyp", + # Anzahlungen + "Auftragsnummer", + "Buchungstyp", + "USt-Schlüssel (Anzahlungen)", + "EU-Land (Anzahlungen)", + "Sachverhalt L+L (Anzahlungen)", + "EU-Steuersatz (Anzahlungen)", + "Erlöskonto (Anzahlungen)", + # Stapelinformationen + "Herkunft-Kz", + # Technische Identifikation + "Buchungs GUID", + # Kostenrechnung + "Kost-Datum", + # OPOS-Informationen + "SEPA-Mandatsreferenz", + "Skontosperre", + # Gesellschafter und Sonderbilanzsachverhalt + "Gesellschaftername", + "Beteiligtennummer", + "Identifikationsnummer", + "Zeichnernummer", + # OPOS-Informationen + "Postensperre bis", + # Gesellschafter und Sonderbilanzsachverhalt + "Bezeichnung SoBil-Sachverhalt", + "Kennzeichen SoBil-Buchung", + # Stapelinformationen + "Festschreibung", + # Datum + "Leistungsdatum", + "Datum Zuord. Steuerperiode", + # OPOS-Informationen + "Fälligkeit", + # Konto/Gegenkonto + "Generalumkehr (GU)", + # Steuersatz für Steuerschlüssel + "Steuersatz", + "Land" +] + +DEBTOR_CREDITOR_COLUMNS = [ + # All possible columns must tbe listed here, because DATEV requires them to + # be present in the CSV. + # Columns "Leerfeld" have been replaced with "Leerfeld #" to not confuse pandas + # --- + "Konto", + "Name (Adressatentyp Unternehmen)", + "Unternehmensgegenstand", + "Name (Adressatentyp natürl. Person)", + "Vorname (Adressatentyp natürl. Person)", + "Name (Adressatentyp keine Angabe)", + "Adressatentyp", + "Kurzbezeichnung", + "EU-Land", + "EU-USt-IdNr.", + "Anrede", + "Titel/Akad. Grad", + "Adelstitel", + "Namensvorsatz", + "Adressart", + "Straße", + "Postfach", + "Postleitzahl", + "Ort", + "Land", + "Versandzusatz", + "Adresszusatz", + "Abweichende Anrede", + "Abw. Zustellbezeichnung 1", + "Abw. Zustellbezeichnung 2", + "Kennz. Korrespondenzadresse", + "Adresse gültig von", + "Adresse gültig bis", + "Telefon", + "Bemerkung (Telefon)", + "Telefon Geschäftsleitung", + "Bemerkung (Telefon GL)", + "E-Mail", + "Bemerkung (E-Mail)", + "Internet", + "Bemerkung (Internet)", + "Fax", + "Bemerkung (Fax)", + "Sonstige", + "Bemerkung (Sonstige)", + "Bankleitzahl 1", + "Bankbezeichnung 1", + "Bankkonto-Nummer 1", + "Länderkennzeichen 1", + "IBAN 1", + "Leerfeld 1", + "SWIFT-Code 1", + "Abw. Kontoinhaber 1", + "Kennz. Haupt-Bankverb. 1", + "Bankverb. 1 Gültig von", + "Bankverb. 1 Gültig bis", + "Bankleitzahl 2", + "Bankbezeichnung 2", + "Bankkonto-Nummer 2", + "Länderkennzeichen 2", + "IBAN 2", + "Leerfeld 2", + "SWIFT-Code 2", + "Abw. Kontoinhaber 2", + "Kennz. Haupt-Bankverb. 2", + "Bankverb. 2 gültig von", + "Bankverb. 2 gültig bis", + "Bankleitzahl 3", + "Bankbezeichnung 3", + "Bankkonto-Nummer 3", + "Länderkennzeichen 3", + "IBAN 3", + "Leerfeld 3", + "SWIFT-Code 3", + "Abw. Kontoinhaber 3", + "Kennz. Haupt-Bankverb. 3", + "Bankverb. 3 gültig von", + "Bankverb. 3 gültig bis", + "Bankleitzahl 4", + "Bankbezeichnung 4", + "Bankkonto-Nummer 4", + "Länderkennzeichen 4", + "IBAN 4", + "Leerfeld 4", + "SWIFT-Code 4", + "Abw. Kontoinhaber 4", + "Kennz. Haupt-Bankverb. 4", + "Bankverb. 4 Gültig von", + "Bankverb. 4 Gültig bis", + "Bankleitzahl 5", + "Bankbezeichnung 5", + "Bankkonto-Nummer 5", + "Länderkennzeichen 5", + "IBAN 5", + "Leerfeld 5", + "SWIFT-Code 5", + "Abw. Kontoinhaber 5", + "Kennz. Haupt-Bankverb. 5", + "Bankverb. 5 gültig von", + "Bankverb. 5 gültig bis", + "Leerfeld 6", + "Briefanrede", + "Grußformel", + "Kundennummer", + "Steuernummer", + "Sprache", + "Ansprechpartner", + "Vertreter", + "Sachbearbeiter", + "Diverse-Konto", + "Ausgabeziel", + "Währungssteuerung", + "Kreditlimit (Debitor)", + "Zahlungsbedingung", + "Fälligkeit in Tagen (Debitor)", + "Skonto in Prozent (Debitor)", + "Kreditoren-Ziel 1 (Tage)", + "Kreditoren-Skonto 1 (%)", + "Kreditoren-Ziel 2 (Tage)", + "Kreditoren-Skonto 2 (%)", + "Kreditoren-Ziel 3 Brutto (Tage)", + "Kreditoren-Ziel 4 (Tage)", + "Kreditoren-Skonto 4 (%)", + "Kreditoren-Ziel 5 (Tage)", + "Kreditoren-Skonto 5 (%)", + "Mahnung", + "Kontoauszug", + "Mahntext 1", + "Mahntext 2", + "Mahntext 3", + "Kontoauszugstext", + "Mahnlimit Betrag", + "Mahnlimit %", + "Zinsberechnung", + "Mahnzinssatz 1", + "Mahnzinssatz 2", + "Mahnzinssatz 3", + "Lastschrift", + "Verfahren", + "Mandantenbank", + "Zahlungsträger", + "Indiv. Feld 1", + "Indiv. Feld 2", + "Indiv. Feld 3", + "Indiv. Feld 4", + "Indiv. Feld 5", + "Indiv. Feld 6", + "Indiv. Feld 7", + "Indiv. Feld 8", + "Indiv. Feld 9", + "Indiv. Feld 10", + "Indiv. Feld 11", + "Indiv. Feld 12", + "Indiv. Feld 13", + "Indiv. Feld 14", + "Indiv. Feld 15", + "Abweichende Anrede (Rechnungsadresse)", + "Adressart (Rechnungsadresse)", + "Straße (Rechnungsadresse)", + "Postfach (Rechnungsadresse)", + "Postleitzahl (Rechnungsadresse)", + "Ort (Rechnungsadresse)", + "Land (Rechnungsadresse)", + "Versandzusatz (Rechnungsadresse)", + "Adresszusatz (Rechnungsadresse)", + "Abw. Zustellbezeichnung 1 (Rechnungsadresse)", + "Abw. Zustellbezeichnung 2 (Rechnungsadresse)", + "Adresse Gültig von (Rechnungsadresse)", + "Adresse Gültig bis (Rechnungsadresse)", + "Bankleitzahl 6", + "Bankbezeichnung 6", + "Bankkonto-Nummer 6", + "Länderkennzeichen 6", + "IBAN 6", + "Leerfeld 7", + "SWIFT-Code 6", + "Abw. Kontoinhaber 6", + "Kennz. Haupt-Bankverb. 6", + "Bankverb 6 gültig von", + "Bankverb 6 gültig bis", + "Bankleitzahl 7", + "Bankbezeichnung 7", + "Bankkonto-Nummer 7", + "Länderkennzeichen 7", + "IBAN 7", + "Leerfeld 8", + "SWIFT-Code 7", + "Abw. Kontoinhaber 7", + "Kennz. Haupt-Bankverb. 7", + "Bankverb 7 gültig von", + "Bankverb 7 gültig bis", + "Bankleitzahl 8", + "Bankbezeichnung 8", + "Bankkonto-Nummer 8", + "Länderkennzeichen 8", + "IBAN 8", + "Leerfeld 9", + "SWIFT-Code 8", + "Abw. Kontoinhaber 8", + "Kennz. Haupt-Bankverb. 8", + "Bankverb 8 gültig von", + "Bankverb 8 gültig bis", + "Bankleitzahl 9", + "Bankbezeichnung 9", + "Bankkonto-Nummer 9", + "Länderkennzeichen 9", + "IBAN 9", + "Leerfeld 10", + "SWIFT-Code 9", + "Abw. Kontoinhaber 9", + "Kennz. Haupt-Bankverb. 9", + "Bankverb 9 gültig von", + "Bankverb 9 gültig bis", + "Bankleitzahl 10", + "Bankbezeichnung 10", + "Bankkonto-Nummer 10", + "Länderkennzeichen 10", + "IBAN 10", + "Leerfeld 11", + "SWIFT-Code 10", + "Abw. Kontoinhaber 10", + "Kennz. Haupt-Bankverb. 10", + "Bankverb 10 gültig von", + "Bankverb 10 gültig bis", + "Nummer Fremdsystem", + "Insolvent", + "SEPA-Mandatsreferenz 1", + "SEPA-Mandatsreferenz 2", + "SEPA-Mandatsreferenz 3", + "SEPA-Mandatsreferenz 4", + "SEPA-Mandatsreferenz 5", + "SEPA-Mandatsreferenz 6", + "SEPA-Mandatsreferenz 7", + "SEPA-Mandatsreferenz 8", + "SEPA-Mandatsreferenz 9", + "SEPA-Mandatsreferenz 10", + "Verknüpftes OPOS-Konto", + "Mahnsperre bis", + "Lastschriftsperre bis", + "Zahlungssperre bis", + "Gebührenberechnung", + "Mahngebühr 1", + "Mahngebühr 2", + "Mahngebühr 3", + "Pauschalberechnung", + "Verzugspauschale 1", + "Verzugspauschale 2", + "Verzugspauschale 3", + "Alternativer Suchname", + "Status", + "Anschrift manuell geändert (Korrespondenzadresse)", + "Anschrift individuell (Korrespondenzadresse)", + "Anschrift manuell geändert (Rechnungsadresse)", + "Anschrift individuell (Rechnungsadresse)", + "Fristberechnung bei Debitor", + "Mahnfrist 1", + "Mahnfrist 2", + "Mahnfrist 3", + "Letzte Frist" +] + +ACCOUNT_NAME_COLUMNS = [ + # Account number + "Konto", + # Account name + "Kontenbeschriftung", + # Language of the account name + # "de-DE" or "en-GB" + "Sprach-ID" +] + +QUERY_REPORT_COLUMNS = [ + { + "label": "Umsatz (ohne Soll/Haben-Kz)", + "fieldname": "Umsatz (ohne Soll/Haben-Kz)", + "fieldtype": "Currency", + }, + { + "label": "Soll/Haben-Kennzeichen", + "fieldname": "Soll/Haben-Kennzeichen", + "fieldtype": "Data", + }, + { + "label": "Kontonummer", + "fieldname": "Kontonummer", + "fieldtype": "Data", + }, + { + "label": "Gegenkonto (ohne BU-Schlüssel)", + "fieldname": "Gegenkonto (ohne BU-Schlüssel)", + "fieldtype": "Data", + }, + { + "label": "Belegdatum", + "fieldname": "Belegdatum", + "fieldtype": "Date", + }, + { + "label": "Buchungstext", + "fieldname": "Buchungstext", + "fieldtype": "Text", + }, + { + "label": "Beleginfo - Art 1", + "fieldname": "Beleginfo - Art 1", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Inhalt 1", + "fieldname": "Beleginfo - Inhalt 1", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Art 2", + "fieldname": "Beleginfo - Art 2", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Inhalt 2", + "fieldname": "Beleginfo - Inhalt 2", + "fieldtype": "Data", + } +] + +class DataCategory(): + """Field of the CSV Header.""" + + DEBTORS_CREDITORS = "16" + ACCOUNT_NAMES = "20" + TRANSACTIONS = "21" + POSTING_TEXT_CONSTANTS = "67" + +class FormatName(): + """Field of the CSV Header, corresponds to DataCategory.""" + + DEBTORS_CREDITORS = "Debitoren/Kreditoren" + ACCOUNT_NAMES = "Kontenbeschriftungen" + TRANSACTIONS = "Buchungsstapel" + POSTING_TEXT_CONSTANTS = "Buchungstextkonstanten" + +class Transactions(): + DATA_CATEGORY = DataCategory.TRANSACTIONS + FORMAT_NAME = FormatName.TRANSACTIONS + COLUMNS = TRANSACTION_COLUMNS + +class DebtorsCreditors(): + DATA_CATEGORY = DataCategory.DEBTORS_CREDITORS + FORMAT_NAME = FormatName.DEBTORS_CREDITORS + COLUMNS = DEBTOR_CREDITOR_COLUMNS + +class AccountNames(): + DATA_CATEGORY = DataCategory.ACCOUNT_NAMES + FORMAT_NAME = FormatName.ACCOUNT_NAMES + COLUMNS = ACCOUNT_NAME_COLUMNS diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py new file mode 100644 index 0000000000..3cc65fe9d3 --- /dev/null +++ b/erpnext/regional/report/datev/test_datev.py @@ -0,0 +1,244 @@ +# coding=utf-8 +from __future__ import unicode_literals + +import os +import json +import zipfile +from six import BytesIO +from unittest import TestCase + +import frappe +from frappe.utils import getdate, today, now_datetime, cstr +from frappe.test_runner import make_test_objects +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts + +from erpnext.regional.report.datev.datev import validate +from erpnext.regional.report.datev.datev import get_transactions +from erpnext.regional.report.datev.datev import get_customers +from erpnext.regional.report.datev.datev import get_suppliers +from erpnext.regional.report.datev.datev import get_account_names +from erpnext.regional.report.datev.datev import get_datev_csv +from erpnext.regional.report.datev.datev import get_header +from erpnext.regional.report.datev.datev import download_datev_csv + +from erpnext.regional.report.datev.datev_constants import DataCategory +from erpnext.regional.report.datev.datev_constants import Transactions +from erpnext.regional.report.datev.datev_constants import DebtorsCreditors +from erpnext.regional.report.datev.datev_constants import AccountNames +from erpnext.regional.report.datev.datev_constants import QUERY_REPORT_COLUMNS + +def make_company(company_name, abbr): + if not frappe.db.exists("Company", company_name): + company = frappe.get_doc({ + "doctype": "Company", + "company_name": company_name, + "abbr": abbr, + "default_currency": "EUR", + "country": "Germany", + "create_chart_of_accounts_based_on": "Standard Template", + "chart_of_accounts": "SKR04 mit Kontonummern" + }) + company.insert() + else: + company = frappe.get_doc("Company", company_name) + + # indempotent + company.create_default_warehouses() + + if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}): + company.create_default_cost_center() + + company.save() + return company + +def setup_fiscal_year(): + fiscal_year = None + year = cstr(now_datetime().year) + if not frappe.db.get_value("Fiscal Year", {"year": year}, "name"): + try: + fiscal_year = frappe.get_doc({ + "doctype": "Fiscal Year", + "year": year, + "year_start_date": "{0}-01-01".format(year), + "year_end_date": "{0}-12-31".format(year) + }) + fiscal_year.insert() + except frappe.NameError: + pass + + if fiscal_year: + fiscal_year.set_as_default() + +def make_customer_with_account(customer_name, company): + acc_name = frappe.db.get_value("Account", { + "account_name": customer_name, + "company": company.name + }, "name") + + if not acc_name: + acc = frappe.get_doc({ + "doctype": "Account", + "parent_account": "1 - Forderungen aus Lieferungen und Leistungen - _TG", + "account_name": customer_name, + "company": company.name, + "account_type": "Receivable", + "account_number": "10001" + }) + acc.insert() + acc_name = acc.name + + if not frappe.db.exists("Customer", customer_name): + customer = frappe.get_doc({ + "doctype": "Customer", + "customer_name": customer_name, + "customer_type": "Company", + "accounts": [{ + "company": company.name, + "account": acc_name + }] + }) + customer.insert() + else: + customer = frappe.get_doc("Customer", customer_name) + + return customer + +def make_item(item_code, company): + warehouse_name = frappe.db.get_value("Warehouse", { + "warehouse_name": "Stores", + "company": company.name + }, "name") + + if not frappe.db.exists("Item", item_code): + item = frappe.get_doc({ + "doctype": "Item", + "item_code": item_code, + "item_name": item_code, + "description": item_code, + "item_group": "All Item Groups", + "is_stock_item": 0, + "is_purchase_item": 0, + "is_customer_provided_item": 0, + "item_defaults": [{ + "default_warehouse": warehouse_name, + "company": company.name + }] + }) + item.insert() + else: + item = frappe.get_doc("Item", item_code) + return item + +def make_datev_settings(company): + if not frappe.db.exists("DATEV Settings", company.name): + frappe.get_doc({ + "doctype": "DATEV Settings", + "client": company.name, + "client_number": "12345", + "consultant_number": "67890" + }).insert() + + +class TestDatev(TestCase): + def setUp(self): + self.company = make_company("_Test GmbH", "_TG") + self.customer = make_customer_with_account("_Test Kunde GmbH", self.company) + self.filters = { + "company": self.company.name, + "from_date": today(), + "to_date": today() + } + + make_datev_settings(self.company) + item = make_item("_Test Item", self.company) + setup_fiscal_year() + + warehouse = frappe.db.get_value("Item Default", { + "parent": item.name, + "company": self.company.name + }, "default_warehouse") + + income_account = frappe.db.get_value("Account", { + "account_number": "4200", + "company": self.company.name + }, "name") + + tax_account = frappe.db.get_value("Account", { + "account_number": "3806", + "company": self.company.name + }, "name") + + si = create_sales_invoice( + company=self.company.name, + customer=self.customer.name, + currency=self.company.default_currency, + debit_to=self.customer.accounts[0].account, + income_account="4200 - Erlöse - _TG", + expense_account="6990 - Herstellungskosten - _TG", + cost_center=self.company.cost_center, + warehouse=warehouse, + item=item.name, + do_not_save=1 + ) + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": tax_account, + "description": "Umsatzsteuer 19 %", + "rate": 19 + }) + + si.save() + si.submit() + + def test_columns(self): + def is_subset(get_data, allowed_keys): + """ + Validate that the dict contains only allowed keys. + + Params: + get_data -- Function that returns a list of dicts. + allowed_keys -- List of allowed keys + """ + data = get_data(self.filters) + if data == []: + # No data and, therefore, no columns is okay + return True + actual_set = set(data[0].keys()) + # allowed set must be interpreted as unicode to match the actual set + allowed_set = set({frappe.as_unicode(key) for key in allowed_keys}) + return actual_set.issubset(allowed_set) + + self.assertTrue(is_subset(get_transactions, Transactions.COLUMNS)) + self.assertTrue(is_subset(get_customers, DebtorsCreditors.COLUMNS)) + self.assertTrue(is_subset(get_suppliers, DebtorsCreditors.COLUMNS)) + self.assertTrue(is_subset(get_account_names, AccountNames.COLUMNS)) + + def test_header(self): + self.assertTrue(Transactions.DATA_CATEGORY in get_header(self.filters, Transactions)) + self.assertTrue(AccountNames.DATA_CATEGORY in get_header(self.filters, AccountNames)) + self.assertTrue(DebtorsCreditors.DATA_CATEGORY in get_header(self.filters, DebtorsCreditors)) + + def test_csv(self): + test_data = [{ + "Umsatz (ohne Soll/Haben-Kz)": 100, + "Soll/Haben-Kennzeichen": "H", + "Kontonummer": "4200", + "Gegenkonto (ohne BU-Schlüssel)": "10000", + "Belegdatum": today(), + "Buchungstext": "No remark", + "Beleginfo - Art 1": "Sales Invoice", + "Beleginfo - Inhalt 1": "SINV-0001" + }] + get_datev_csv(data=test_data, filters=self.filters, csv_class=Transactions) + + def test_download(self): + """Assert that the returned file is a ZIP file.""" + download_datev_csv(self.filters) + + # zipfile.is_zipfile() expects a file-like object + zip_buffer = BytesIO() + zip_buffer.write(frappe.response['filecontent']) + + self.assertTrue(zipfile.is_zipfile(zip_buffer)) From 10017c14f3c976fe1ef579c76289f1ee1cf4be21 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 29 Nov 2019 17:33:10 +0530 Subject: [PATCH 29/36] feat: HSN Code wise Item Tax (#19478) * feat: HSN Code wise Item Tax * feat: enqueued task of updating all items --- .../doctype/gst_hsn_code/gst_hsn_code.js | 24 ++- .../doctype/gst_hsn_code/gst_hsn_code.json | 138 +++++------------- .../doctype/gst_hsn_code/gst_hsn_code.py | 19 +++ erpnext/stock/doctype/item/item.js | 14 ++ 4 files changed, 95 insertions(+), 100 deletions(-) diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js index e7cc91952d..7ff4de4863 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js @@ -3,6 +3,26 @@ frappe.ui.form.on('GST HSN Code', { refresh: function(frm) { - + if(! frm.doc.__islocal && frm.doc.taxes.length){ + frm.add_custom_button(__('Update Taxes for Items'), function(){ + frappe.confirm( + 'Are you sure? It will overwrite taxes for all items with HSN Code '+frm.doc.name+'.', + function(){ + frappe.call({ + args:{ + taxes: frm.doc.taxes, + hsn_code: frm.doc.name + }, + method: 'erpnext.regional.doctype.gst_hsn_code.gst_hsn_code.update_taxes_in_item_master', + callback: function(r) { + if(r.message){ + frappe.show_alert(__('Item taxes updated')); + } + } + }); + } + ); + }); + } } -}); +}); \ No newline at end of file diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json index 2a2145c7f7..06dab3726d 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json @@ -1,104 +1,46 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:hsn_code", - "beta": 0, - "creation": "2017-06-21 10:48:56.422086", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "autoname": "field:hsn_code", + "creation": "2017-06-21 10:48:56.422086", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "hsn_code", + "description", + "taxes" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hsn_code", - "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": "HSN Code", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "hsn_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "HSN Code", + "reqd": 1, + "unique": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Small Text", - "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": "Description", - "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 + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + }, + { + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Taxes", + "options": "Item Tax" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-09-29 14:38:52.220743", - "modified_by": "Administrator", - "module": "Regional", - "name": "GST HSN Code", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "hsn_code, description", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "hsn_code", - "track_changes": 1, - "track_seen": 0 + ], + "modified": "2019-11-01 11:18:59.556931", + "modified_by": "Administrator", + "module": "Regional", + "name": "GST HSN Code", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "search_fields": "hsn_code, description", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "hsn_code", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py index 9637c2e502..fa2cb1299a 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py @@ -8,3 +8,22 @@ from frappe.model.document import Document class GSTHSNCode(Document): pass + +@frappe.whitelist() +def update_taxes_in_item_master(taxes, hsn_code): + items = frappe.get_list("Item", filters={ + 'gst_hsn_code': hsn_code + }) + + taxes = frappe.parse_json(taxes) + frappe.enqueue(update_item_document, items=items, taxes=taxes) + return 1 + +def update_item_document(items, taxes): + for item in items: + item_to_be_updated=frappe.get_doc("Item", item.name) + item_to_be_updated.taxes = [] + for tax in taxes: + tax = frappe._dict(tax) + item_to_be_updated.append("taxes", {'item_tax_template': tax.item_tax_template, 'tax_category': tax.tax_category}) + item_to_be_updated.save() \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 410d9f1b45..e3d356f93b 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -136,6 +136,20 @@ frappe.ui.form.on("Item", { frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0); }, + gst_hsn_code: function(frm){ + if(!frm.doc.taxes){ + frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc=>{ + frm.doc.taxes = []; + $.each(hsn_doc.taxes || [], function(i, tax) { + let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes'); + a.item_tax_template = tax.item_tax_template; + a.tax_category = tax.tax_category; + frm.refresh_field('taxes'); + }); + }); + } + }, + is_fixed_asset: function(frm) { // set serial no to false & toggles its visibility frm.set_value('has_serial_no', 0); From 611d2ccd04edf186718704b60f6f0f125c3f9e32 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 Nov 2019 10:11:03 +0530 Subject: [PATCH 30/36] chore: updated path for set_request in v13-develop --- erpnext/portal/doctype/homepage/test_homepage.py | 2 +- .../portal/doctype/homepage_section/test_homepage_section.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py index b262c4640c..bf5c4025a0 100644 --- a/erpnext/portal/doctype/homepage/test_homepage.py +++ b/erpnext/portal/doctype/homepage/test_homepage.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.tests.test_website import set_request +from frappe.utils import set_request from frappe.website.render import render class TestHomepage(unittest.TestCase): diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py index c52b7a96c6..5b3196def2 100644 --- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py +++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe import unittest from bs4 import BeautifulSoup -from frappe.tests.test_website import set_request +from frappe.utils import set_request from frappe.website.render import render class TestHomepageSection(unittest.TestCase): From 54694dd8161b959cf558c10180b3564ac043477c Mon Sep 17 00:00:00 2001 From: Sammish Thundiyil Date: Fri, 29 Nov 2019 15:30:30 +0300 Subject: [PATCH 31/36] modified: erpnext/hr/doctype/repayment_schedule/repayment_schedule.json (#19470) --- .../repayment_schedule.json | 293 +++++------------- 1 file changed, 72 insertions(+), 221 deletions(-) diff --git a/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json index a1161851d0..5bb2d370fa 100644 --- a/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json +++ b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json @@ -1,231 +1,82 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-12-20 15:32:25.078334", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2016-12-20 15:32:25.078334", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "payment_date", + "principal_amount", + "interest_amount", + "total_payment", + "balance_loan_amount", + "paid" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "payment_date", - "fieldtype": "Date", - "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": "Payment Date", - "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, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "columns": 2, + "fieldname": "payment_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Payment Date" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "principal_amount", - "fieldtype": "Currency", - "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": "Principal Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "principal_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Principal Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "interest_amount", - "fieldtype": "Currency", - "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": "Interest Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "interest_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Interest Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "total_payment", - "fieldtype": "Currency", - "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": "Total Payment", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "total_payment", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Payment", + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "balance_loan_amount", - "fieldtype": "Currency", - "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": "Balance Loan Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "balance_loan_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Balance Loan Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "paid", - "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": "Paid", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "0", + "fieldname": "paid", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Paid", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-03-30 17:37:31.834792", - "modified_by": "Administrator", - "module": "HR", - "name": "Repayment Schedule", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "istable": 1, + "modified": "2019-10-29 11:45:10.694557", + "modified_by": "Administrator", + "module": "HR", + "name": "Repayment Schedule", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From 935517d914f5fe0fc594c838901975d804e62c36 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 29 Nov 2019 18:08:42 +0530 Subject: [PATCH 32/36] fix: added bank reconciliation page in the accounting module (#19719) * fix: added bank reconcilation page inaccounting module * Update accounts.py --- erpnext/config/accounts.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index ab75f211c0..08711fc09e 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -197,6 +197,11 @@ def get_data(): "name": "Bank Reconciliation Statement", "is_query_report": True, "doctype": "Journal Entry" + },{ + "type": "page", + "name": "bank-reconciliation", + "label": _("Bank Reconciliation"), + "icon": "fa fa-bar-chart" }, { "type": "report", From b3af2adc4a145b5d540fbaef030c9a4a1a6ced47 Mon Sep 17 00:00:00 2001 From: Victor Munene Date: Fri, 29 Nov 2019 15:42:13 +0300 Subject: [PATCH 33/36] use program.courses instead of get_all_children to get course list (#19695) --- .../education/doctype/program_enrollment/program_enrollment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index d5348ffd06..7536172891 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -71,7 +71,7 @@ class ProgramEnrollment(Document): def create_course_enrollments(self): student = frappe.get_doc("Student", self.student) program = frappe.get_doc("Program", self.program) - course_list = [course.course for course in program.get_all_children()] + course_list = [course.course for course in program.courses] for course_name in course_list: student.enroll_in_course(course_name=course_name, program_enrollment=self.name) From 23b1dae6a7218df6a4220e905dbebcf00488aecb Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 Nov 2019 20:22:06 +0530 Subject: [PATCH 34/36] chore: dropped dead setup_wizard test fix: fixed imports for teste utils --- .../test_product_configurator.py | 2 +- .../setup/setup_wizard/test_setup_wizard.py | 71 ------------------- 2 files changed, 1 insertion(+), 72 deletions(-) delete mode 100644 erpnext/setup/setup_wizard/test_setup_wizard.py diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py index a534e5f838..97042dba92 100644 --- a/erpnext/portal/product_configurator/test_product_configurator.py +++ b/erpnext/portal/product_configurator/test_product_configurator.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from bs4 import BeautifulSoup import frappe, unittest -from frappe.tests.test_website import set_request, get_html_for_route +from frappe.utils import set_request, get_html_for_route from frappe.website.render import render from erpnext.portal.product_configurator.utils import get_products_for_website from erpnext.stock.doctype.item.test_item import make_item_variant diff --git a/erpnext/setup/setup_wizard/test_setup_wizard.py b/erpnext/setup/setup_wizard/test_setup_wizard.py deleted file mode 100644 index a489133aba..0000000000 --- a/erpnext/setup/setup_wizard/test_setup_wizard.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - -import frappe, time -from frappe.utils.selenium_testdriver import TestDriver - -def run_setup_wizard_test(): - driver = TestDriver() - frappe.db.set_default('in_selenium', '1') - frappe.db.commit() - - driver.login('#page-setup-wizard') - print('Running Setup Wizard Test...') - - # Language slide - driver.wait_for_ajax(True) - time.sleep(1) - - driver.set_select("language", "English (United States)") - driver.wait_for_ajax(True) - time.sleep(1) - driver.click(".next-btn") - - # Region slide - driver.wait_for_ajax(True) - driver.set_select("country", "India") - driver.wait_for_ajax(True) - time.sleep(1) - driver.click(".next-btn") - - # Profile slide - driver.set_field("full_name", "Great Tester") - driver.set_field("email", "great@example.com") - driver.set_field("password", "test") - driver.wait_for_ajax(True) - time.sleep(1) - driver.click(".next-btn") - time.sleep(1) - - # domain slide - driver.set_multicheck("domains", ["Manufacturing"]) - time.sleep(1) - driver.click(".next-btn") - - # Org slide - driver.set_field("company_name", "For Testing") - time.sleep(1) - driver.print_console() - driver.click(".next-btn") - - driver.set_field("company_tagline", "Just for GST") - driver.set_field("bank_account", "HDFC") - time.sleep(3) - driver.click(".complete-btn") - - # Wait for desktop - driver.wait_for('#page-desktop', timeout=600) - - driver.print_console() - time.sleep(3) - - frappe.db.set_default('in_selenium', None) - frappe.db.set_value("Company", "For Testing", "write_off_account", "Write Off - FT") - frappe.db.set_value("Company", "For Testing", "exchange_gain_loss_account", "Exchange Gain/Loss - FT") - frappe.db.commit() - - driver.close() - - return True From 98a54355ae5c88fdfcec488a5c12eb58f52b721a Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sun, 1 Dec 2019 10:06:16 +0530 Subject: [PATCH 35/36] fix: Post GL entry fix for asset (#19751) --- erpnext/assets/doctype/asset/asset.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 40f1e1efc9..d32f834f0f 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -517,15 +517,18 @@ def update_maintenance_status(): asset.set_status('Out of Order') def make_post_gl_entry(): - if not is_cwip_accounting_enabled(self.asset_category): - return - assets = frappe.db.sql_list(""" select name from `tabAsset` - where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate()) + asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting']) - for asset in assets: - doc = frappe.get_doc('Asset', asset) - doc.make_gl_entries() + for asset_category in asset_categories: + if cint(asset_category.enable_cwip_accounting): + assets = frappe.db.sql_list(""" select name from `tabAsset` + where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0 + and available_for_use_date = %s""", (asset_category.name, nowdate())) + + for asset in assets: + doc = frappe.get_doc('Asset', asset) + doc.make_gl_entries() def get_asset_naming_series(): meta = frappe.get_meta('Asset') From 5249a6348fbbc861652b9a70c23dedd8502f1c00 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Sun, 1 Dec 2019 06:17:21 +0100 Subject: [PATCH 36/36] Update de.csv (#19755) Changed all translations from "Aufgabe" to "Vorgang" in singular and plural where the word "Task" is project related. Left all agricultural and others how they are (as Aufgabe). --- erpnext/translations/de.csv | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 2e25a127d8..cdff3ff422 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1329,7 +1329,7 @@ apps/erpnext/erpnext/hr/doctype/job_offer/job_offer.js,Create Employee,Mitarbeit apps/erpnext/erpnext/utilities/transaction_base.py,Invalid Posting Time,Ungültige Buchungszeit DocType: Salary Component,Condition and Formula,Zustand und Formel DocType: Lead,Campaign Name,Kampagnenname -apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss der Aufgabe +apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss des Vorgangs apps/erpnext/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py,There is no leave period in between {0} and {1},Es gibt keinen Urlaub zwischen {0} und {1} DocType: Fee Validity,Healthcare Practitioner,praktischer Arzt DocType: Hotel Room,Capacity,Kapazität @@ -1353,7 +1353,7 @@ DocType: Payment Entry,Received Amount (Company Currency),Erhaltene Menge (Gesel apps/erpnext/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py,Payment Cancelled. Please check your GoCardless Account for more details,Zahlung abgebrochen. Bitte überprüfen Sie Ihr GoCardless Konto für weitere Details DocType: Work Order,Skip Material Transfer to WIP Warehouse,Überspringen Sie die Materialübertragung in das WIP-Lager DocType: Contract,N/A,nicht verfügbar -DocType: Task Type,Task Type,Aufgabentyp +DocType: Task Type,Task Type,Vorgangstyp DocType: Topic,Topic Content,Themeninhalt DocType: Delivery Settings,Send with Attachment,Senden mit Anhang DocType: Service Level,Priorities,Prioritäten @@ -2449,7 +2449,7 @@ apps/erpnext/erpnext/public/js/utils/serial_no_batch_selector.js,Please select b DocType: Asset,Depreciation Schedules,Abschreibungen Termine apps/erpnext/erpnext/projects/doctype/timesheet/timesheet.js,Create Sales Invoice,Verkaufsrechnung erstellen apps/erpnext/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html,Ineligible ITC,Nicht förderfähiges ITC -DocType: Task,Dependent Tasks,Abhängige Aufgaben +DocType: Task,Dependent Tasks,Abhängige Vorgänge apps/erpnext/erpnext/regional/report/gstr_1/gstr_1.py,Following accounts might be selected in GST Settings:,In den GST-Einstellungen können folgende Konten ausgewählt werden: apps/erpnext/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js,Quantity to Produce,Menge zu produzieren apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Application period cannot be outside leave allocation period,Beantragter Zeitraum kann nicht außerhalb der beantragten Urlaubszeit liegen @@ -2846,7 +2846,7 @@ DocType: Loan,Applicant Type,Bewerbertyp DocType: Purchase Invoice,03-Deficiency in services,03-Mangel an Dienstleistungen DocType: Healthcare Settings,Default Medical Code Standard,Default Medical Code Standard DocType: Purchase Invoice Item,HSN/SAC,HSN / SAC -DocType: Project Template Task,Project Template Task,Projektvorlagenaufgabe +DocType: Project Template Task,Project Template Task,Projektvorgangsvorlage DocType: Accounts Settings,Over Billing Allowance (%),Mehr als Abrechnungsbetrag (%) apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py,Purchase Receipt {0} is not submitted,Kaufbeleg {0} wurde nicht übertragen DocType: Company,Default Payable Account,Standard-Verbindlichkeitenkonto @@ -3323,7 +3323,7 @@ DocType: Soil Texture,Silt,Schlick ,Qty to Order,Zu bestellende Menge DocType: Period Closing Voucher,"The account head under Liability or Equity, in which Profit/Loss will be booked","Der Kontenkopf unter Eigen- oder Fremdkapital, in dem Gewinn / Verlust verbucht wird" apps/erpnext/erpnext/accounts/doctype/budget/budget.py,Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4},Ein weiterer Budgeteintrag '{0}' existiert bereits für {1} '{2}' und für '{3}' für das Geschäftsjahr {4} -apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Aufgaben +apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Vorgänge DocType: Opportunity,Mins to First Response,Minuten zum First Response DocType: Pricing Rule,Margin Type,Margenart apps/erpnext/erpnext/projects/doctype/project/project_dashboard.html,{0} hours,{0} Stunden @@ -3961,7 +3961,7 @@ apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Maintenance Sche apps/erpnext/erpnext/education/doctype/student/student_dashboard.py,Student LMS Activity,Student LMS Aktivität DocType: POS Profile,Applicable for Users,Anwendbar für Benutzer DocType: Supplier Quotation,PUR-SQTN-.YYYY.-,PUR-SQTN-.JJJJ.- -apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Aufgaben auf Status {0} setzen? +apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Vorgänge auf Status {0} setzen? DocType: Purchase Invoice,Set Advances and Allocate (FIFO),Vorschüsse setzen und zuordnen (FIFO) apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.py,No Work Orders created,Keine Arbeitsaufträge erstellt apps/erpnext/erpnext/hr/doctype/salary_slip/salary_slip.py,Salary Slip of employee {0} already created for this period,Gehaltsabrechnung der Mitarbeiter {0} für diesen Zeitraum bereits erstellt @@ -4418,7 +4418,7 @@ DocType: Normal Test Items,Result Value,Ergebnis Wert DocType: Hotel Room,Hotels,Hotels apps/erpnext/erpnext/accounts/doctype/cost_center/cost_center_tree.js,New Cost Center Name,Neuer Kostenstellenname DocType: Leave Control Panel,Leave Control Panel,Urlaubsverwaltung -DocType: Project,Task Completion,Aufgabenerledigung +DocType: Project,Task Completion,Vorgangserfüllung apps/erpnext/erpnext/templates/generators/item/item_add_to_cart.html,Not in Stock,Nicht lagernd DocType: Volunteer,Volunteer Skills,Freiwillige Fähigkeiten DocType: Additional Salary,HR User,Nutzer Personalabteilung @@ -5197,7 +5197,7 @@ DocType: Work Order,Material Transferred for Manufacturing,Material zur Herstell apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.py,Account {0} does not exists,Konto {0} existiert nicht apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.js,Select Loyalty Program,Wählen Sie Treueprogramm DocType: Project,Project Type,Projekttyp -apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diese Aufgabe existiert eine untergeordnete Aufgabe. Sie können diese Aufgabe daher nicht löschen. +apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diesen Vorgang existiert ein untergeordneter Vorgang. Sie können diese Aufgabe daher nicht löschen. apps/erpnext/erpnext/setup/doctype/sales_person/sales_person.py,Either target qty or target amount is mandatory.,Entweder Zielstückzahl oder Zielmenge ist zwingend erforderlich. apps/erpnext/erpnext/config/projects.py,Cost of various activities,Aufwendungen für verschiedene Tätigkeiten apps/erpnext/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py,"Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}","Einstellen Events auf {0}, da die Mitarbeiter auf die beigefügten unter Verkaufs Personen keine Benutzer-ID {1}" @@ -5597,7 +5597,7 @@ apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py,Paid apps/erpnext/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py,{0} is not a valid Batch Number for Item {1},{0} ist keine gültige Chargennummer für Artikel {1} apps/erpnext/erpnext/shopping_cart/cart.py,Please enter valid coupon code !!,Bitte geben Sie einen gültigen Gutscheincode ein !! apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Note: There is not enough leave balance for Leave Type {0},Hinweis: Es gibt nicht genügend Urlaubsguthaben für Abwesenheitstyp {0} -DocType: Task,Task Description,Aufgabenbeschreibung +DocType: Task,Task Description,Vorgangsbeschreibung DocType: Training Event,Seminar,Seminar DocType: Program Enrollment Fee,Program Enrollment Fee,Programm Einschreibegebühr DocType: Item,Supplier Items,Lieferantenartikel @@ -5754,7 +5754,7 @@ apps/erpnext/erpnext/accounts/doctype/sales_invoice/pos.py,All Territories,Alle DocType: Lost Reason Detail,Lost Reason Detail,Verlorene Begründung Detail apps/erpnext/erpnext/hr/utils.py,Please set leave policy for employee {0} in Employee / Grade record,Legen Sie die Abwesenheitsrichtlinie für den Mitarbeiter {0} im Mitarbeiter- / Notensatz fest apps/erpnext/erpnext/public/js/controllers/transaction.js,Invalid Blanket Order for the selected Customer and Item,Ungültiger Blankoauftrag für den ausgewählten Kunden und Artikel -apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Aufgaben hinzufügen +apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Vorgänge hinzufügen DocType: Purchase Invoice,Items,Artikel apps/erpnext/erpnext/crm/doctype/contract/contract.py,End Date cannot be before Start Date.,Das Enddatum darf nicht vor dem Startdatum liegen. apps/erpnext/erpnext/education/doctype/course_enrollment/course_enrollment.py,Student is already enrolled.,Student ist bereits eingetragen sind.