From b503896594808df62ca56961bf9cfdbaadc8b2e9 Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Sat, 27 Apr 2019 01:39:50 +0530 Subject: [PATCH 01/10] fix(demo): make demo work again definitely not a maga rebranding. * import erpnext and set default company for accounts * replace job card start/end date with job card time log * create a doc for lost_reason * pass opportunity/quotation lost_reason as a list of dicts * fix company in stock reconciliation Signed-off-by: Chinmay Pai --- erpnext/demo/user/accounts.py | 7 +++++-- erpnext/demo/user/manufacturing.py | 18 +++++++++++++----- erpnext/demo/user/sales.py | 13 +++++++++++-- erpnext/demo/user/stock.py | 4 ++-- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/erpnext/demo/user/accounts.py b/erpnext/demo/user/accounts.py index 18855041ed..6206dfd2d0 100644 --- a/erpnext/demo/user/accounts.py +++ b/erpnext/demo/user/accounts.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals +import erpnext import frappe import random from frappe.utils import random_string @@ -72,8 +73,10 @@ def work(): make_pos_invoice() def make_payment_entries(ref_doctype, report): - outstanding_invoices = list(set([r[3] for r in query_report.run(report, - {"report_date": frappe.flags.current_date })["result"] if r[2]==ref_doctype])) + outstanding_invoices = list(set([r[3] for r in query_report.run(report, { + "report_date": frappe.flags.current_date, + "company": erpnext.get_default_company() + })["result"] if r[2]==ref_doctype])) # make Payment Entry for inv in outstanding_invoices[:random.randint(1, 2)]: diff --git a/erpnext/demo/user/manufacturing.py b/erpnext/demo/user/manufacturing.py index a28d061f38..bece0798fa 100644 --- a/erpnext/demo/user/manufacturing.py +++ b/erpnext/demo/user/manufacturing.py @@ -102,10 +102,18 @@ def submit_job_cards(): for operation in work_order.operations: job = job_map[operation.operation] - job.actual_start_date = start_date + job_time_log = frappe.new_doc("Job Card Time Log") + job_time_log.from_time = start_date minutes = operation.get("time_in_mins") - random_minutes = random.randint(int(minutes/2), minutes) - job.actual_end_date = job.actual_start_date + timedelta(minutes=random_minutes) - start_date = job.actual_end_date - job.save() + job_time_log.time_in_mins = random.randint(int(minutes/2), minutes) + job_time_log.to_time = job_time_log.from_time + \ + timedelta(minutes=job_time_log.time_in_mins) + job_time_log.parent = job.name + job_time_log.parenttype = 'Job Card' + job_time_log.parentfield = 'time_logs' + job_time_log.completed_qty = work_order.qty + job_time_log.save(ignore_permissions=True) + job.time_logs.append(job_time_log) + job.save(ignore_permissions=True) job.submit() + start_date = job_time_log.to_time diff --git a/erpnext/demo/user/sales.py b/erpnext/demo/user/sales.py index 69ba9007a6..d4b55e8e2c 100644 --- a/erpnext/demo/user/sales.py +++ b/erpnext/demo/user/sales.py @@ -21,17 +21,26 @@ def work(domain="Manufacturing"): if random.random() < 0.5: make_quotation(domain) + try: + lost_reason = frappe.get_doc({ + "doctype": "Opportunity Lost Reason", + "lost_reason": "Did not ask" + }) + lost_reason.save(ignore_permissions=True) + except frappe.exceptions.DuplicateEntryError: + pass + # lost quotations / inquiries if random.random() < 0.3: for i in range(random.randint(1,3)): quotation = get_random('Quotation', doc=True) if quotation and quotation.status == 'Submitted': - quotation.declare_order_lost('Did not ask') + quotation.declare_order_lost([{'lost_reason': 'Did not ask'}]) for i in range(random.randint(1,3)): opportunity = get_random('Opportunity', doc=True) if opportunity and opportunity.status in ('Open', 'Replied'): - opportunity.declare_enquiry_lost('Did not ask') + opportunity.declare_enquiry_lost([{'lost_reason': 'Did not ask'}]) for i in range(random.randint(1,3)): if random.random() < 0.6: diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py index 60b1edcac5..f95a6b8331 100644 --- a/erpnext/demo/user/stock.py +++ b/erpnext/demo/user/stock.py @@ -73,13 +73,13 @@ def make_stock_reconciliation(): stock_reco = frappe.new_doc("Stock Reconciliation") stock_reco.posting_date = frappe.flags.current_date stock_reco.company = erpnext.get_default_company() - stock_reco.get_items_for("Stores - WP") + stock_reco.get_items_for("Stores - WPL") if stock_reco.items: for item in stock_reco.items: if item.qty: item.qty = item.qty - round(random.randint(1, item.qty)) try: - stock_reco.insert() + stock_reco.insert(ignore_permissions=True) stock_reco.submit() frappe.db.commit() except OpeningEntryAccountError: From 9a84a99ac027724c429e3b62b9a9eb68e23a6f70 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 20 Feb 2019 13:04:35 +0530 Subject: [PATCH 02/10] feat(tally): Add Tally Migration DocType --- .../doctype/tally_migration/__init__.py | 0 .../tally_migration/tally_migration.js | 8 + .../tally_migration/tally_migration.json | 159 ++++++++++++++++++ .../tally_migration/tally_migration.py | 12 ++ .../tally_migration/test_tally_migration.js | 23 +++ .../tally_migration/test_tally_migration.py | 10 ++ 6 files changed, 212 insertions(+) create mode 100644 erpnext/erpnext_integrations/doctype/tally_migration/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js create mode 100644 erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json create mode 100644 erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py create mode 100644 erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js create mode 100644 erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.py diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/__init__.py b/erpnext/erpnext_integrations/doctype/tally_migration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js new file mode 100644 index 0000000000..065fcc722e --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Tally Migration', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json new file mode 100644 index 0000000000..b54e6beb20 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json @@ -0,0 +1,159 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2019-02-01 14:27:09.485238", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "master_data", + "fieldtype": "Attach", + "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": "Master Data", + "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_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "day_book", + "fieldtype": "Attach", + "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": "Day Book", + "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 + } + ], + "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": "2019-02-20 12:59:17.746113", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "Tally Migration", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "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, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py new file mode 100644 index 0000000000..f4e064ae1a --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +import frappe +from frappe.model.document import Document + + +class TallyMigration(Document): + pass diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js new file mode 100644 index 0000000000..433c5e2cda --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Tally Migration", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Tally Migration + () => frappe.tests.make('Tally Migration', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.py new file mode 100644 index 0000000000..9f67e55ca1 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestTallyMigration(unittest.TestCase): + pass From 55d465f3ad15f75e972bacfd602f58927b676b9d Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Fri, 1 Mar 2019 15:34:47 +0530 Subject: [PATCH 03/10] feat(tally): Preprocess master data to generate COA --- .../tally_migration/tally_migration.js | 33 ++- .../tally_migration/tally_migration.json | 230 +++++++++++++++++- .../tally_migration/tally_migration.py | 122 +++++++++- 3 files changed, 381 insertions(+), 4 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js index 065fcc722e..4652d60bff 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js @@ -3,6 +3,35 @@ frappe.ui.form.on('Tally Migration', { refresh: function(frm) { - - } + if (frm.doc.master_data && frm.doc.day_book) { + frm.disable_save(); + if(frm.doc.status != "In Progress") { + frm.page.set_primary_action("Preprocess", () => frm.trigger("preprocess")); + } + } else { + frm.set_value("status", "Attach File"); + } + if (frm.doc.tally_company && frm.doc.erpnext_company) { + frm.set_df_property("company_section", "hidden", 0); + frm.page.set_primary_action("Start Import", () => frm.trigger("start_import")); + } + }, + preprocess: function(frm) { + frm.call({ + doc: frm.doc, + method: "preprocess", + freeze: true + }).then((r) => { + frm.set_value("status", "Preprocessing In Progress"); + }); + }, + start_import: function(frm) { + frm.call({ + doc: frm.doc, + method: "start_import", + freeze: true + }).then((r) => { + frm.set_value("status", "Import In Progress"); + }); + }, }); diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json index b54e6beb20..7d51850aff 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json @@ -14,6 +14,38 @@ "editable_grid": 1, "engine": "InnoDB", "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Data", + "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": "Status", + "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_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -46,6 +78,40 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Sundry Creditors", + "fieldname": "tally_creditors_account", + "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": "Tally Creditors Account", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -108,6 +174,168 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Sundry Debtors", + "fieldname": "tally_debtors_account", + "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": "Tally Debtors Account", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company_section", + "fieldtype": "Section Break", + "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": "Company Section", + "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_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "tally_company", + "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": "Tally Company", + "length": 0, + "no_copy": 0, + "options": "", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_8", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "erpnext_company", + "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": "ERPNext Company", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -120,7 +348,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-02-20 12:59:17.746113", + "modified": "2019-03-01 15:02:47.992385", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Tally Migration", diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index f4e064ae1a..4c27d743fb 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -4,9 +4,129 @@ from __future__ import unicode_literals +import json +import re +import zipfile import frappe from frappe.model.document import Document +from bs4 import BeautifulSoup as bs +PRIMARY_ACCOUNT = "Primary" class TallyMigration(Document): - pass + def _preprocess(self): + company, chart_of_accounts_tree, customers, suppliers = self._process_master_data() + self.tally_company = company + self.erpnext_company = company + self.status = "Preprocessed" + self.save() + + def _process_master_data(self): + def get_master_collection(master_data): + master_file = frappe.get_doc("File", {"file_url": master_data}) + + with zipfile.ZipFile(master_file.get_full_path()) as zf: + content = zf.read(zf.namelist()[0]).decode("utf-16") + + master = bs(sanitize(emptify(content)), "xml") + collection = master.BODY.IMPORTDATA.REQUESTDATA + return collection + + def get_company_name(collection): + return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string + + def get_coa_customers_suppliers(collection): + root_type_map = { + "Application of Funds (Assets)": "Asset", + "Expenses": "Expense", + "Income": "Income", + "Source of Funds (Liabilities)": "Liability" + } + roots = set(root_type_map.keys()) + accounts = list(get_groups(collection.find_all("GROUP"))) + list(get_ledgers(collection.find_all("LEDGER"))) + children, parents = get_children_and_parent_dict(accounts) + group_set = [acc[1] for acc in accounts if acc[2]] + children, customers, suppliers = remove_parties(parents, children, group_set) + coa = traverse({}, children, roots, roots, group_set) + + for account in coa: + coa[account]["root_type"] = root_type_map[account] + + return coa, customers, suppliers + + def get_groups(accounts): + for account in accounts: + if account["NAME"] in (self.tally_creditors_account, self.tally_debtors_account): + yield get_parent(account), account["NAME"], 0 + else: + yield get_parent(account), account["NAME"], 1 + + def get_ledgers(accounts): + for account in accounts: + # If Ledger doesn't have PARENT field then don't create Account + # For example "Profit & Loss A/c" + if account.PARENT: + yield account.PARENT.string, account["NAME"], 0 + + def get_parent(account): + if account.PARENT: + return account.PARENT.string + return { + ("Yes", "No"): "Application of Funds (Assets)", + ("Yes", "Yes"): "Expenses", + ("No", "Yes"): "Income", + ("No", "No"): "Source of Funds (Liabilities)", + }[(account.ISDEEMEDPOSITIVE.string, account.ISREVENUE.string)] + + def get_children_and_parent_dict(accounts): + children, parents = {}, {} + for parent, account, is_group in accounts: + children.setdefault(parent, set()).add(account) + parents.setdefault(account, set()).add(parent) + return children, parents + + def remove_parties(parents, children, group_set): + customers, suppliers = set(), set() + for account in parents: + if self.tally_creditors_account in parents[account]: + children.pop(account, None) + if account not in group_set: + customers.add(account) + elif self.tally_debtors_account in parents[account]: + children.pop(account, None) + if account not in group_set: + suppliers.add(account) + return children, customers, suppliers + + def traverse(tree, children, accounts, roots, group_set): + for account in accounts: + if account in group_set or account in roots: + if account in children: + tree[account] = traverse({}, children, children[account], roots, group_set) + else: + tree[account] = {"is_group": 1} + else: + tree[account] = {} + return tree + + collection = get_master_collection(self.master_data) + + company = get_company_name(collection) + chart_of_accounts_tree, customer_names, supplier_names = get_coa_customers_suppliers(collection) + + return company, chart_of_accounts_tree, customer_names, supplier_names + + def preprocess(self): + frappe.enqueue_doc(self.doctype, self.name, "_preprocess") + + def start_import(self): + pass + +def sanitize(string): + return re.sub("", "", string) + +def emptify(string): + string = re.sub(r"<\w+/>", "", string) + string = re.sub(r"<([\w.]+)>\s*<\/\1>", "", string) + string = re.sub(r"\r\n", "", string) + return string From 0668482f82e447c628ff8ae00e39e2505228bf26 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Fri, 1 Mar 2019 21:35:56 +0530 Subject: [PATCH 04/10] feat(tally): Create chart of accounts from tally master data --- .../tally_migration/tally_migration.json | 34 ++++++++++++++++++- .../tally_migration/tally_migration.py | 24 ++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json index 7d51850aff..2087c7f7be 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json @@ -328,6 +328,38 @@ "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "chart_of_accounts", + "fieldtype": "Attach", + "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": "Chart of Accounts", + "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, @@ -348,7 +380,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-03-01 15:02:47.992385", + "modified": "2019-03-01 20:58:04.320605", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Tally Migration", diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 4c27d743fb..aac371dd3e 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -10,6 +10,7 @@ import zipfile import frappe from frappe.model.document import Document from bs4 import BeautifulSoup as bs +from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts PRIMARY_ACCOUNT = "Primary" @@ -19,6 +20,15 @@ class TallyMigration(Document): self.tally_company = company self.erpnext_company = company self.status = "Preprocessed" + + coa_file = frappe.get_doc({ + "doctype": "File", + "file_name": "COA.json", + "attached_to_doctype": self.doctype, + "attached_to_name": self.name, + "content": json.dumps(chart_of_accounts_tree) + }).insert() + self.chart_of_accounts = coa_file.file_url self.save() def _process_master_data(self): @@ -120,7 +130,19 @@ class TallyMigration(Document): frappe.enqueue_doc(self.doctype, self.name, "_preprocess") def start_import(self): - pass + def create_company_and_coa(coa_file_url): + coa_file = frappe.get_doc("File", {"file_url": coa_file_url}) + frappe.local.flags.ignore_chart_of_accounts = True + company = frappe.get_doc({ + "doctype": "Company", + "company_name": self.erpnext_company, + "default_currency": "INR", + }).insert() + frappe.local.flags.ignore_chart_of_accounts = False + create_charts(company.name, json.loads(coa_file.get_content())) + + create_company_and_coa(self.chart_of_accounts) + def sanitize(string): return re.sub("", "", string) From f2202cf638a789c379fb0fc38d6d7adb57728e18 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Sat, 2 Mar 2019 17:23:23 +0530 Subject: [PATCH 05/10] feat(tally): Preprocess and create parties and addresses --- .../tally_migration/tally_migration.json | 66 ++++++++++- .../tally_migration/tally_migration.py | 103 +++++++++++++++++- 2 files changed, 165 insertions(+), 4 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json index 2087c7f7be..6478cdb9cf 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json @@ -368,6 +368,70 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "parties", + "fieldtype": "Attach", + "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": "Parties", + "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_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "addresses", + "fieldtype": "Attach", + "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": "Addresses", + "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 } ], "has_web_view": 0, @@ -380,7 +444,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-03-01 20:58:04.320605", + "modified": "2019-03-01 22:44:04.042954", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Tally Migration", diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index aac371dd3e..c8e2eba67a 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import json import re +import traceback import zipfile import frappe from frappe.model.document import Document @@ -17,6 +18,7 @@ PRIMARY_ACCOUNT = "Primary" class TallyMigration(Document): def _preprocess(self): company, chart_of_accounts_tree, customers, suppliers = self._process_master_data() + parties, addresses = self._process_parties(customers, suppliers) self.tally_company = company self.erpnext_company = company self.status = "Preprocessed" @@ -29,6 +31,25 @@ class TallyMigration(Document): "content": json.dumps(chart_of_accounts_tree) }).insert() self.chart_of_accounts = coa_file.file_url + + parties_file = frappe.get_doc({ + "doctype": "File", + "file_name": "Parties.json", + "attached_to_doctype": self.doctype, + "attached_to_name": self.name, + "content": json.dumps(parties) + }).insert() + self.parties = parties_file.file_url + + addresses_file = frappe.get_doc({ + "doctype": "File", + "file_name": "Addresses.json", + "attached_to_doctype": self.doctype, + "attached_to_name": self.name, + "content": json.dumps(addresses) + }).insert() + self.addresses = addresses_file.file_url + self.save() def _process_master_data(self): @@ -93,6 +114,7 @@ class TallyMigration(Document): for parent, account, is_group in accounts: children.setdefault(parent, set()).add(account) parents.setdefault(account, set()).add(parent) + parents[account].update(parents.get(parent, [])) return children, parents def remove_parties(parents, children, group_set): @@ -105,7 +127,7 @@ class TallyMigration(Document): elif self.tally_debtors_account in parents[account]: children.pop(account, None) if account not in group_set: - suppliers.add(account) + suppliers.add(account) return children, customers, suppliers def traverse(tree, children, accounts, roots, group_set): @@ -126,10 +148,63 @@ class TallyMigration(Document): return company, chart_of_accounts_tree, customer_names, supplier_names + def _process_parties(self, customers, suppliers): + def get_master_collection(master_data): + master_file = frappe.get_doc("File", {"file_url": master_data}) + + with zipfile.ZipFile(master_file.get_full_path()) as zf: + content = zf.read(zf.namelist()[0]).decode("utf-16") + + master = bs(sanitize(emptify(content)), "xml") + collection = master.BODY.IMPORTDATA.REQUESTDATA + return collection + + def get_parties_addresses(collection, customers, suppliers): + parties, addresses = [], [] + for account in collection.find_all("LEDGER"): + party_type = None + if account.NAME.string in customers: + party_type = "Customer" + parties.append({ + "doctype": party_type, + "customer_name": account.NAME.string, + "tax_id": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None, + "customer_group": "All Customer Groups", + "territory": "All Territories", + "customer_type": "Individual", + }) + elif account.NAME.string in suppliers: + party_type = "Supplier" + parties.append({ + "doctype": party_type, + "supplier_name": account.NAME.string, + "pan": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None, + "supplier_group": "All Supplier Groups", + "supplier_type": "Individual", + }) + if party_type: + address = "\n".join([a.string for a in account.find_all("ADDRESS")[:2]]) + addresses.append({ + "doctype": "Address", + "address_line1": address[:140].strip(), + "address_line2": address[140:].strip(), + "country": account.COUNTRYNAME.string if account.COUNTRYNAME else None, + "state": account.STATENAME.string if account.STATENAME else None, + "gst_state": account.STATENAME.string if account.STATENAME else None, + "pin_code": account.PINCODE.string if account.PINCODE else None, + "gstin": account.PARTYGSTIN.string if account.PARTYGSTIN else None, + "links": [{"link_doctype": party_type, "link_name": account["NAME"]}], + }) + return parties, addresses + + collection = get_master_collection(self.master_data) + parties, addresses = get_parties_addresses(collection, customers, suppliers) + return parties, addresses + def preprocess(self): frappe.enqueue_doc(self.doctype, self.name, "_preprocess") - def start_import(self): + def _start_import(self): def create_company_and_coa(coa_file_url): coa_file = frappe.get_doc("File", {"file_url": coa_file_url}) frappe.local.flags.ignore_chart_of_accounts = True @@ -141,8 +216,30 @@ class TallyMigration(Document): frappe.local.flags.ignore_chart_of_accounts = False create_charts(company.name, json.loads(coa_file.get_content())) - create_company_and_coa(self.chart_of_accounts) + def create_parties_addresses(parties_file_url, addresses_file_url): + parties_file = frappe.get_doc("File", {"file_url": parties_file_url}) + for party in json.loads(parties_file.get_content()): + try: + frappe.get_doc(party).insert() + except: + log(party) + addresses_file = frappe.get_doc("File", {"file_url": addresses_file_url}) + for address in json.loads(addresses_file.get_content()): + try: + frappe.get_doc(address).insert(ignore_mandatory=True) + except: + log(address) + create_company_and_coa(self.chart_of_accounts) + create_parties_addresses(self.parties, self.addresses) + + def start_import(self): + frappe.enqueue_doc(self.doctype, self.name, "_start_import") + + +def log(data=None): + message = json.dumps({"data": data, "exception": traceback.format_exc()}, indent=4) + frappe.log_error(title="Tally Migration Error", message=message) def sanitize(string): return re.sub("", "", string) From 27ab5b540b78212ccd16b528d9770977e364f946 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Sat, 2 Mar 2019 20:36:11 +0530 Subject: [PATCH 06/10] fix(tally): Use LEDSTATENAME instead of STATENAME --- .../doctype/tally_migration/tally_migration.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index c8e2eba67a..8f3bf5d6cd 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -189,9 +189,11 @@ class TallyMigration(Document): "address_line1": address[:140].strip(), "address_line2": address[140:].strip(), "country": account.COUNTRYNAME.string if account.COUNTRYNAME else None, - "state": account.STATENAME.string if account.STATENAME else None, - "gst_state": account.STATENAME.string if account.STATENAME else None, + "state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, + "gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, "pin_code": account.PINCODE.string if account.PINCODE else None, + "mobile": account.LEDGERPHONE.string if account.LEDGERPHONE else None, + "phone": account.LEDGERPHONE.string if account.LEDGERPHONE else None, "gstin": account.PARTYGSTIN.string if account.PARTYGSTIN else None, "links": [{"link_doctype": party_type, "link_name": account["NAME"]}], }) From b6e9eb9784dfa70f14b2f2ab07599418aa7934ee Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Sat, 2 Mar 2019 22:12:41 +0530 Subject: [PATCH 07/10] feat(tally): Preprocess and create items and uoms --- .../tally_migration/tally_migration.json | 129 +++++++++++++++++- .../tally_migration/tally_migration.py | 88 +++++++++--- 2 files changed, 195 insertions(+), 22 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json index 6478cdb9cf..54e162b4af 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json @@ -337,6 +337,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "processed_files", + "fieldtype": "Section Break", + "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": "Processed Files", + "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_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -432,6 +464,101 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_17", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "uoms", + "fieldtype": "Attach", + "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": "UOMs", + "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_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "items", + "fieldtype": "Attach", + "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": "Items", + "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 } ], "has_web_view": 0, @@ -444,7 +571,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-03-01 22:44:04.042954", + "modified": "2019-03-02 21:51:48.869051", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Tally Migration", diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 8f3bf5d6cd..cedf0167ab 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -19,6 +19,7 @@ class TallyMigration(Document): def _preprocess(self): company, chart_of_accounts_tree, customers, suppliers = self._process_master_data() parties, addresses = self._process_parties(customers, suppliers) + items, uoms = self._process_stock_items() self.tally_company = company self.erpnext_company = company self.status = "Preprocessed" @@ -50,18 +51,38 @@ class TallyMigration(Document): }).insert() self.addresses = addresses_file.file_url + uoms_file = frappe.get_doc({ + "doctype": "File", + "file_name": "UOMs.json", + "attached_to_doctype": self.doctype, + "attached_to_name": self.name, + "content": json.dumps(uoms) + }).insert() + self.uoms = uoms_file.file_url + + items_file = frappe.get_doc({ + "doctype": "File", + "file_name": "Items.json", + "attached_to_doctype": self.doctype, + "attached_to_name": self.name, + "content": json.dumps(items) + }).insert() + self.items = items_file.file_url + + self.save() + def get_master_collection(self): + master_file = frappe.get_doc("File", {"file_url": self.master_data}) + + with zipfile.ZipFile(master_file.get_full_path()) as zf: + content = zf.read(zf.namelist()[0]).decode("utf-16") + + master = bs(sanitize(emptify(content)), "xml") + collection = master.BODY.IMPORTDATA.REQUESTDATA + return collection + def _process_master_data(self): - def get_master_collection(master_data): - master_file = frappe.get_doc("File", {"file_url": master_data}) - - with zipfile.ZipFile(master_file.get_full_path()) as zf: - content = zf.read(zf.namelist()[0]).decode("utf-16") - - master = bs(sanitize(emptify(content)), "xml") - collection = master.BODY.IMPORTDATA.REQUESTDATA - return collection def get_company_name(collection): return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string @@ -141,7 +162,7 @@ class TallyMigration(Document): tree[account] = {} return tree - collection = get_master_collection(self.master_data) + collection = self.get_master_collection() company = get_company_name(collection) chart_of_accounts_tree, customer_names, supplier_names = get_coa_customers_suppliers(collection) @@ -149,16 +170,6 @@ class TallyMigration(Document): return company, chart_of_accounts_tree, customer_names, supplier_names def _process_parties(self, customers, suppliers): - def get_master_collection(master_data): - master_file = frappe.get_doc("File", {"file_url": master_data}) - - with zipfile.ZipFile(master_file.get_full_path()) as zf: - content = zf.read(zf.namelist()[0]).decode("utf-16") - - master = bs(sanitize(emptify(content)), "xml") - collection = master.BODY.IMPORTDATA.REQUESTDATA - return collection - def get_parties_addresses(collection, customers, suppliers): parties, addresses = [], [] for account in collection.find_all("LEDGER"): @@ -199,10 +210,29 @@ class TallyMigration(Document): }) return parties, addresses - collection = get_master_collection(self.master_data) + collection = self.get_master_collection() parties, addresses = get_parties_addresses(collection, customers, suppliers) return parties, addresses + def _process_stock_items(self): + collection = self.get_master_collection() + uoms = [] + for uom in collection.find_all("UNIT"): + uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string}) + + items = [] + for item in collection.find_all("STOCKITEM"): + hsn_code = item.find_all("GSTDETAILS.LIST")[0].HSNCODE + items.append({ + "doctype": "Item", + "item_code" : item.NAME.string, + "stock_uom": item.BASEUNITS.string, + "gst_hsn_code": hsn_code.string if hsn_code else None, + "is_stock_item": 0, + "item_group": "All Item Groups", + }) + return items, uoms + def preprocess(self): frappe.enqueue_doc(self.doctype, self.name, "_preprocess") @@ -232,8 +262,24 @@ class TallyMigration(Document): except: log(address) + def create_items_uoms(items_file_url, uoms_file_url): + uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url}) + for uom in json.loads(uoms_file.get_content()): + try: + frappe.get_doc(uom).insert() + except: + log(uom) + + items_file = frappe.get_doc("File", {"file_url": items_file_url}) + for item in json.loads(items_file.get_content()): + try: + frappe.get_doc(item).insert() + except: + log(item) + create_company_and_coa(self.chart_of_accounts) create_parties_addresses(self.parties, self.addresses) + create_items_uoms(self.items, self.uoms) def start_import(self): frappe.enqueue_doc(self.doctype, self.name, "_start_import") From ba61be93ae305e42b7b0411de16804d5a4549813 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 5 Mar 2019 19:16:00 +0530 Subject: [PATCH 08/10] refactor: Major changes --- .../tally_migration/tally_migration.js | 69 +- .../tally_migration/tally_migration.json | 639 ++++-------------- .../tally_migration/tally_migration.py | 439 ++++++++---- 3 files changed, 491 insertions(+), 656 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js index 4652d60bff..104ac570c6 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js @@ -2,36 +2,49 @@ // For license information, please see license.txt frappe.ui.form.on('Tally Migration', { - refresh: function(frm) { - if (frm.doc.master_data && frm.doc.day_book) { - frm.disable_save(); - if(frm.doc.status != "In Progress") { - frm.page.set_primary_action("Preprocess", () => frm.trigger("preprocess")); + onload: function(frm) { + frappe.realtime.on("tally_migration_progress_update", function (data) { + frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message); + if (data.count == data.total) { + window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title); } - } else { - frm.set_value("status", "Attach File"); - } - if (frm.doc.tally_company && frm.doc.erpnext_company) { - frm.set_df_property("company_section", "hidden", 0); - frm.page.set_primary_action("Start Import", () => frm.trigger("start_import")); - } - }, - preprocess: function(frm) { - frm.call({ - doc: frm.doc, - method: "preprocess", - freeze: true - }).then((r) => { - frm.set_value("status", "Preprocessing In Progress"); }); }, - start_import: function(frm) { - frm.call({ - doc: frm.doc, - method: "start_import", - freeze: true - }).then((r) => { - frm.set_value("status", "Import In Progress"); - }); + refresh: function(frm) { + if (frm.doc.master_data && !frm.doc.is_master_data_imported) { + if (frm.doc.is_master_data_processed) { + if (frm.doc.status != "Importing Master Data") { + frm.events.add_button(frm, __("Import Master Data"), "import_master_data"); + } + } else { + if (frm.doc.status != "Processing Master Data") { + frm.events.add_button(frm, __("Process Master Data"), "process_master_data"); + } + } + } + if (frm.doc.day_book_data && !frm.doc.is_day_book_data_imported) { + if (frm.doc.is_day_book_data_processed) { + if (frm.doc.status != "Importing Day Book Data") { + frm.events.add_button(frm, __("Import Day Book Data"), "import_day_book_data"); + } + } else { + if (frm.doc.status != "Processing Day Book Data") { + frm.events.add_button(frm, __("Process Day Book Data"), "process_day_book_data"); + } + } + } }, + add_button: function(frm, label, method) { + frm.add_custom_button( + label, + () => frm.call({ + doc: frm.doc, + method: method, + freeze: true, + callback: () => { + frm.remove_custom_button(label); + } + }) + ); + } }); diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json index 54e162b4af..26415caf8d 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json @@ -1,610 +1,219 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, + "beta": 1, "creation": "2019-02-01 14:27:09.485238", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "status", + "master_data", + "is_master_data_processed", + "is_master_data_imported", + "column_break_2", + "tally_creditors_account", + "tally_debtors_account", + "company_section", + "tally_company", + "column_break_8", + "erpnext_company", + "processed_files_section", + "chart_of_accounts", + "parties", + "addresses", + "column_break_17", + "uoms", + "items", + "vouchers", + "accounts_section", + "default_warehouse", + "round_off_account", + "column_break_21", + "default_cost_center", + "day_book_section", + "day_book_data", + "column_break_27", + "is_day_book_data_processed", + "is_day_book_data_imported" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "status", "fieldtype": "Data", "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": "Status", - "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 + "label": "Status" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "master_data", "fieldtype": "Attach", - "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": "Master Data", - "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 + "label": "Master Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Sundry Creditors", "fieldname": "tally_creditors_account", "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": "Tally Creditors Account", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "day_book", - "fieldtype": "Attach", - "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": "Day Book", - "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_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Sundry Debtors", "fieldname": "tally_debtors_account", "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": "Tally Debtors Account", - "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, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "depends_on": "is_master_data_processed", "fieldname": "company_section", - "fieldtype": "Section Break", - "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": "Company Section", - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "tally_company", "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": "Tally Company", - "length": 0, - "no_copy": 0, - "options": "", - "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": "column_break_8", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "erpnext_company", "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": "ERPNext Company", - "length": 0, - "no_copy": 0, - "options": "", - "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 + "label": "ERPNext Company" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "processed_files", + "fieldname": "processed_files_section", "fieldtype": "Section Break", "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": "Processed Files", - "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 + "label": "Processed Files" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "chart_of_accounts", "fieldtype": "Attach", - "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": "Chart of Accounts", - "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 + "label": "Chart of Accounts" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "parties", "fieldtype": "Attach", - "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": "Parties", - "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 + "label": "Parties" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "addresses", "fieldtype": "Attach", - "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": "Addresses", - "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 + "label": "Addresses" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_17", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "uoms", "fieldtype": "Attach", - "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": "UOMs", - "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 + "label": "UOMs" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "items", "fieldtype": "Attach", - "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": "Items", - "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 + "label": "Items" + }, + { + "fieldname": "vouchers", + "fieldtype": "Attach", + "label": "Vouchers" + }, + { + "depends_on": "is_master_data_imported", + "fieldname": "accounts_section", + "fieldtype": "Section Break", + "label": "Accounts" + }, + { + "fieldname": "default_warehouse", + "fieldtype": "Link", + "label": "Default Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "round_off_account", + "fieldtype": "Link", + "label": "Round Off Account", + "options": "Account" + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_cost_center", + "fieldtype": "Link", + "label": "Default Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "is_master_data_processed", + "fieldtype": "Check", + "label": "Is Master Data Processed", + "read_only": 1 + }, + { + "fieldname": "is_day_book_data_processed", + "fieldtype": "Check", + "label": "Is Day Book Data Processed", + "read_only": 1 + }, + { + "fieldname": "is_day_book_data_imported", + "fieldtype": "Check", + "label": "Is Day Book Data Imported", + "read_only": 1 + }, + { + "fieldname": "is_master_data_imported", + "fieldtype": "Check", + "label": "Is Master Data Imported", + "read_only": 1 + }, + { + "depends_on": "is_master_data_imported", + "fieldname": "day_book_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "fieldname": "day_book_data", + "fieldtype": "Attach", + "in_list_view": 1, + "label": "Day Book Data" } ], - "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": "2019-03-02 21:51:48.869051", + "modified": "2019-04-29 05:46:54.394967", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Tally Migration", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "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, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index cedf0167ab..5b96ad36f1 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -4,86 +4,65 @@ from __future__ import unicode_literals +from decimal import Decimal import json import re import traceback import zipfile import frappe +from frappe import _ +from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.model.document import Document +from frappe.model.naming import getseries, revert_series_if_last +from frappe.utils.data import format_datetime from bs4 import BeautifulSoup as bs -from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts +from erpnext import encode_company_abbr +from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts PRIMARY_ACCOUNT = "Primary" +VOUCHER_CHUNK_SIZE = 500 + class TallyMigration(Document): - def _preprocess(self): - company, chart_of_accounts_tree, customers, suppliers = self._process_master_data() - parties, addresses = self._process_parties(customers, suppliers) - items, uoms = self._process_stock_items() - self.tally_company = company - self.erpnext_company = company - self.status = "Preprocessed" + def autoname(self): + if not self.name: + self.name = "Tally Migration on " + format_datetime(self.creation) - coa_file = frappe.get_doc({ - "doctype": "File", - "file_name": "COA.json", - "attached_to_doctype": self.doctype, - "attached_to_name": self.name, - "content": json.dumps(chart_of_accounts_tree) - }).insert() - self.chart_of_accounts = coa_file.file_url + def get_collection(self, data_file): + def sanitize(string): + return re.sub("", "", string) - parties_file = frappe.get_doc({ - "doctype": "File", - "file_name": "Parties.json", - "attached_to_doctype": self.doctype, - "attached_to_name": self.name, - "content": json.dumps(parties) - }).insert() - self.parties = parties_file.file_url + def emptify(string): + string = re.sub(r"<\w+/>", "", string) + string = re.sub(r"<([\w.]+)>\s*<\/\1>", "", string) + string = re.sub(r"\r\n", "", string) + return string - addresses_file = frappe.get_doc({ - "doctype": "File", - "file_name": "Addresses.json", - "attached_to_doctype": self.doctype, - "attached_to_name": self.name, - "content": json.dumps(addresses) - }).insert() - self.addresses = addresses_file.file_url - - uoms_file = frappe.get_doc({ - "doctype": "File", - "file_name": "UOMs.json", - "attached_to_doctype": self.doctype, - "attached_to_name": self.name, - "content": json.dumps(uoms) - }).insert() - self.uoms = uoms_file.file_url - - items_file = frappe.get_doc({ - "doctype": "File", - "file_name": "Items.json", - "attached_to_doctype": self.doctype, - "attached_to_name": self.name, - "content": json.dumps(items) - }).insert() - self.items = items_file.file_url - - - self.save() - - def get_master_collection(self): - master_file = frappe.get_doc("File", {"file_url": self.master_data}) + master_file = frappe.get_doc("File", {"file_url": data_file}) with zipfile.ZipFile(master_file.get_full_path()) as zf: - content = zf.read(zf.namelist()[0]).decode("utf-16") + encoded_content = zf.read(zf.namelist()[0]) + try: + content = encoded_content.decode("utf-8-sig") + except UnicodeDecodeError: + content = encoded_content.decode("utf-16") master = bs(sanitize(emptify(content)), "xml") collection = master.BODY.IMPORTDATA.REQUESTDATA return collection - def _process_master_data(self): + def dump_processed_data(self, data): + for key, value in data.items(): + f = frappe.get_doc({ + "doctype": "File", + "file_name": key + ".json", + "attached_to_doctype": self.doctype, + "attached_to_name": self.name, + "content": json.dumps(value) + }).insert() + setattr(self, key, f.file_url) + def _process_master_data(self): def get_company_name(collection): return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string @@ -144,11 +123,11 @@ class TallyMigration(Document): if self.tally_creditors_account in parents[account]: children.pop(account, None) if account not in group_set: - customers.add(account) + suppliers.add(account) elif self.tally_debtors_account in parents[account]: children.pop(account, None) if account not in group_set: - suppliers.add(account) + customers.add(account) return children, customers, suppliers def traverse(tree, children, accounts, roots, group_set): @@ -162,14 +141,6 @@ class TallyMigration(Document): tree[account] = {} return tree - collection = self.get_master_collection() - - company = get_company_name(collection) - chart_of_accounts_tree, customer_names, supplier_names = get_coa_customers_suppliers(collection) - - return company, chart_of_accounts_tree, customer_names, supplier_names - - def _process_parties(self, customers, suppliers): def get_parties_addresses(collection, customers, suppliers): parties, addresses = [], [] for account in collection.find_all("LEDGER"): @@ -210,33 +181,49 @@ class TallyMigration(Document): }) return parties, addresses - collection = self.get_master_collection() + def get_stock_items_uoms(collection): + uoms = [] + for uom in collection.find_all("UNIT"): + uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string}) + + items = [] + for item in collection.find_all("STOCKITEM"): + items.append({ + "doctype": "Item", + "item_code" : item.NAME.string, + "stock_uom": item.BASEUNITS.string, + "is_stock_item": 0, + "item_group": "All Item Groups", + "item_defaults": [{"company": self.erpnext_company}] + }) + return items, uoms + + + self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5) + collection = self.get_collection(self.master_data) + + company = get_company_name(collection) + self.tally_company = company + self.erpnext_company = company + + self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5) + chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection) + self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5) parties, addresses = get_parties_addresses(collection, customers, suppliers) - return parties, addresses + self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5) + items, uoms = get_stock_items_uoms(collection) + data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms} + self.publish("Process Master Data", _("Done"), 5, 5) - def _process_stock_items(self): - collection = self.get_master_collection() - uoms = [] - for uom in collection.find_all("UNIT"): - uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string}) + self.dump_processed_data(data) + self.is_master_data_processed = 1 + self.status = "" + self.save() - items = [] - for item in collection.find_all("STOCKITEM"): - hsn_code = item.find_all("GSTDETAILS.LIST")[0].HSNCODE - items.append({ - "doctype": "Item", - "item_code" : item.NAME.string, - "stock_uom": item.BASEUNITS.string, - "gst_hsn_code": hsn_code.string if hsn_code else None, - "is_stock_item": 0, - "item_group": "All Item Groups", - }) - return items, uoms + def publish(self, title, message, count, total): + frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total}) - def preprocess(self): - frappe.enqueue_doc(self.doctype, self.name, "_preprocess") - - def _start_import(self): + def _import_master_data(self): def create_company_and_coa(coa_file_url): coa_file = frappe.get_doc("File", {"file_url": coa_file_url}) frappe.local.flags.ignore_chart_of_accounts = True @@ -244,56 +231,282 @@ class TallyMigration(Document): "doctype": "Company", "company_name": self.erpnext_company, "default_currency": "INR", + "enable_perpetual_inventory": 0, }).insert() frappe.local.flags.ignore_chart_of_accounts = False - create_charts(company.name, json.loads(coa_file.get_content())) + create_charts(company.name, custom_chart=json.loads(coa_file.get_content())) + company.create_default_warehouses() - def create_parties_addresses(parties_file_url, addresses_file_url): + def create_parties_and_addresses(parties_file_url, addresses_file_url): parties_file = frappe.get_doc("File", {"file_url": parties_file_url}) for party in json.loads(parties_file.get_content()): try: frappe.get_doc(party).insert() except: - log(party) + self.log(party) addresses_file = frappe.get_doc("File", {"file_url": addresses_file_url}) for address in json.loads(addresses_file.get_content()): try: frappe.get_doc(address).insert(ignore_mandatory=True) except: - log(address) + try: + gstin = address.pop("gstin", None) + frappe.get_doc(address).insert(ignore_mandatory=True) + self.log({"address": address, "message": "Invalid GSTIN: {}. Address was created without GSTIN".format(gstin)}) + except: + self.log(address) + def create_items_uoms(items_file_url, uoms_file_url): uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url}) for uom in json.loads(uoms_file.get_content()): - try: - frappe.get_doc(uom).insert() - except: - log(uom) + if not frappe.db.exists(uom): + try: + frappe.get_doc(uom).insert() + except: + self.log(uom) items_file = frappe.get_doc("File", {"file_url": items_file_url}) for item in json.loads(items_file.get_content()): try: frappe.get_doc(item).insert() except: - log(item) + self.log(item) + self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4) create_company_and_coa(self.chart_of_accounts) - create_parties_addresses(self.parties, self.addresses) + self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4) + create_parties_and_addresses(self.parties, self.addresses) + self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4) create_items_uoms(self.items, self.uoms) + self.publish("Import Master Data", _("Done"), 4, 4) + self.status = "" + self.is_master_data_imported = 1 + self.save() - def start_import(self): - frappe.enqueue_doc(self.doctype, self.name, "_start_import") + def _process_day_book_data(self): + def get_vouchers(collection): + vouchers = [] + for voucher in collection.find_all("VOUCHER"): + if voucher.ISCANCELLED.string == "Yes": + continue + inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST") + if voucher.VOUCHERTYPENAME.string not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries: + function = voucher_to_invoice + else: + function = voucher_to_journal_entry + try: + vouchers.append(function(voucher)) + except: + self.log(voucher) + return vouchers + def voucher_to_journal_entry(voucher): + accounts = [] + ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST") + for entry in ledger_entries: + account = {"account": encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company), "cost_center": self.default_cost_center} + if entry.ISPARTYLEDGER.string == "Yes": + party_details = get_party(entry.LEDGERNAME.string) + if party_details: + party_type, party_account = party_details + account["party_type"] = party_type + account["account"] = party_account + account["party"] = entry.LEDGERNAME.string + amount = Decimal(entry.AMOUNT.string) + if amount > 0: + account["credit_in_account_currency"] = str(abs(amount)) + else: + account["debit_in_account_currency"] = str(abs(amount)) + accounts.append(account) -def log(data=None): - message = json.dumps({"data": data, "exception": traceback.format_exc()}, indent=4) - frappe.log_error(title="Tally Migration Error", message=message) + journal_entry = { + "doctype": "Journal Entry", + "tally_guid": voucher.GUID.string, + "posting_date": voucher.DATE.string, + "company": self.erpnext_company, + "accounts": accounts, + } + return journal_entry -def sanitize(string): - return re.sub("", "", string) + def voucher_to_invoice(voucher): + if voucher.VOUCHERTYPENAME.string in ["Sales", "Credit Note"]: + doctype = "Sales Invoice" + party_field = "customer" + account_field = "debit_to" + account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company) + price_list_field = "selling_price_list" + elif voucher.VOUCHERTYPENAME.string in ["Purchase", "Debit Note"]: + doctype = "Purchase Invoice" + party_field = "supplier" + account_field = "credit_to" + account_name = encode_company_abbr(self.tally_creditors_account, self.erpnext_company) + price_list_field = "buying_price_list" -def emptify(string): - string = re.sub(r"<\w+/>", "", string) - string = re.sub(r"<([\w.]+)>\s*<\/\1>", "", string) - string = re.sub(r"\r\n", "", string) - return string + invoice = { + "doctype": doctype, + party_field: voucher.PARTYNAME.string, + "tally_guid": voucher.GUID.string, + "posting_date": voucher.DATE.string, + "due_date": voucher.DATE.string, + "items": get_voucher_items(voucher, doctype), + "taxes": get_voucher_taxes(voucher), + account_field: account_name, + price_list_field: "Tally Price List", + "set_posting_time": 1, + "disable_rounded_total": 1, + "company": self.erpnext_company, + } + return invoice + + def get_voucher_items(voucher, doctype): + inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST") + if doctype == "Sales Invoice": + account_field = "income_account" + elif doctype == "Purchase Invoice": + account_field = "expense_account" + items = [] + for entry in inventory_entries: + qty, uom = entry.ACTUALQTY.string.strip().split() + items.append({ + "item_code": entry.STOCKITEMNAME.string, + "description": entry.STOCKITEMNAME.string, + "qty": qty.strip(), + "uom": uom.strip(), + "conversion_factor": 1, + "price_list_rate": entry.RATE.string.split("/")[0], + "cost_center": self.default_cost_center, + "warehouse": self.default_warehouse, + account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string, self.erpnext_company), + }) + return items + + def get_voucher_taxes(voucher): + ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST") + taxes = [] + for entry in ledger_entries: + if entry.ISPARTYLEDGER.string == "No": + tax_account = encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company) + taxes.append({ + "charge_type": "Actual", + "account_head": tax_account, + "description": tax_account, + "tax_amount": entry.AMOUNT.string, + "cost_center": self.default_cost_center, + }) + return taxes + + def get_party(party): + if frappe.db.exists({"doctype": "Supplier", "supplier_name": party}): + return "Supplier", encode_company_abbr(self.tally_creditors_account, self.erpnext_company) + elif frappe.db.exists({"doctype": "Customer", "customer_name": party}): + return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company) + + self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3) + collection = self.get_collection(self.day_book_data) + self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3) + vouchers = get_vouchers(collection) + self.publish("Process Day Book Data", _("Done"), 3, 3) + self.dump_processed_data({"vouchers": vouchers}) + self.status = "" + self.is_day_book_data_processed = 1 + self.save() + + def _import_day_book_data(self): + def create_fiscal_years(vouchers): + from frappe.utils.data import add_years, getdate + earliest_date = getdate(min(voucher["posting_date"] for voucher in vouchers)) + oldest_year = frappe.get_all("Fiscal Year", fields=["year_start_date", "year_end_date"], order_by="year_start_date")[0] + while earliest_date < oldest_year.year_start_date: + new_year = frappe.get_doc({"doctype": "Fiscal Year"}) + new_year.year_start_date = add_years(oldest_year.year_start_date, -1) + new_year.year_end_date = add_years(oldest_year.year_end_date, -1) + if new_year.year_start_date.year == new_year.year_end_date.year: + new_year.year = new_year.year_start_date.year + else: + new_year.year = "{}-{}".format(new_year.year_start_date.year, new_year.year_end_date.year) + new_year.save() + oldest_year = new_year + + def create_custom_fields(doctypes): + for doctype in doctypes: + df = { + "fieldtype": "Data", + "fieldname": "tally_guid", + "read_only": 1, + "label": "Tally GUID" + } + create_custom_field(doctype, df) + + def create_price_list(): + frappe.get_doc({ + "doctype": "Price List", + "price_list_name": "Tally Price List", + "selling": 1, + "buying": 1, + "enabled": 1, + "currency": "INR" + }).insert() + + frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable") + frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable") + frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account) + + vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers}) + vouchers = json.loads(vouchers_file.get_content()) + + create_fiscal_years(vouchers) + create_price_list() + create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"]) + + total = len(vouchers) + is_last = False + for index in range(0, total, VOUCHER_CHUNK_SIZE): + if index + VOUCHER_CHUNK_SIZE >= total: + is_last = True + frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last) + + def _import_vouchers(self, start, total, is_last=False): + frappe.flags.in_migrate = True + vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers}) + vouchers = json.loads(vouchers_file.get_content()) + chunk = vouchers[start: start + VOUCHER_CHUNK_SIZE] + + for index, voucher in enumerate(chunk, start=start): + try: + doc = frappe.get_doc(voucher).insert() + doc.submit() + self.publish("Importing Vouchers", _("{} of {}").format(index, total), index, total) + except: + self.log(voucher) + + if is_last: + self.status = "" + self.is_day_book_data_imported = 1 + self.save() + frappe.db.set_value("Price List", "Tally Price List", "enabled", 0) + frappe.flags.in_migrate = False + + def process_master_data(self): + self.status = "Processing Master Data" + self.save() + frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600) + + def import_master_data(self): + self.status = "Importing Master Data" + self.save() + frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600) + + def process_day_book_data(self): + self.status = "Processing Day Book Data" + self.save() + frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600) + + def import_day_book_data(self): + self.status = "Importing Day Book Data" + self.save() + frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600) + + def log(self, data=None): + message = "\n".join(["Data", json.dumps(data, default=str, indent=4), "Exception", traceback.format_exc()]) + return frappe.log_error(title="Tally Migration Error", message=message) From 8afeda924d0a4485dc5e1922aaf7c60aadfe6785 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 29 Apr 2019 13:09:23 +0530 Subject: [PATCH 09/10] fix: Use all address lines instead of only first two --- .../doctype/tally_migration/tally_migration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 5b96ad36f1..12b646dad7 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -165,7 +165,7 @@ class TallyMigration(Document): "supplier_type": "Individual", }) if party_type: - address = "\n".join([a.string for a in account.find_all("ADDRESS")[:2]]) + address = "\n".join([a.string for a in account.find_all("ADDRESS")]) addresses.append({ "doctype": "Address", "address_line1": address[:140].strip(), From 1efade34081d85cd864a60e2a964f9407e44465c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 29 Apr 2019 15:00:20 +0530 Subject: [PATCH 10/10] fix(task): weight editable --- erpnext/projects/doctype/task/task.json | 1301 ++--------------------- 1 file changed, 87 insertions(+), 1214 deletions(-) diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 4012346da1..bba258a49a 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -1,1505 +1,378 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, "autoname": "TASK-.YYYY.-.#####", - "beta": 0, "creation": "2013-01-29 19:25:50", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, + "field_order": [ + "subject", + "project", + "issue", + "type", + "is_group", + "column_break0", + "status", + "priority", + "task_weight", + "color", + "parent_task", + "sb_timeline", + "exp_start_date", + "expected_time", + "column_break_11", + "exp_end_date", + "progress", + "is_milestone", + "sb_details", + "description", + "sb_depends_on", + "depends_on", + "depends_on_tasks", + "sb_actual", + "act_start_date", + "actual_time", + "column_break_15", + "act_end_date", + "sb_costing", + "total_costing_amount", + "total_expense_claim", + "column_break_20", + "total_billing_amount", + "sb_more_info", + "review_date", + "closing_date", + "column_break_22", + "department", + "company", + "lft", + "rgt", + "old_parent" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "subject", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Subject", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "project", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Project", - "length": 0, - "no_copy": 0, "oldfieldname": "project", "oldfieldtype": "Link", "options": "Project", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "issue", "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": "Issue", - "length": 0, - "no_copy": 0, - "options": "Issue", - "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 + "options": "Issue" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "type", "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": "Type", - "length": 0, - "no_copy": 0, - "options": "Task Type", - "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 + "options": "Task Type" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, "default": "0", - "fetch_if_empty": 0, "fieldname": "is_group", "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 Group", - "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 + "label": "Is Group" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break0", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "50%", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "length": 0, "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "Open\nWorking\nPending Review\nOverdue\nCompleted\nCancelled", - "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": "Open\nWorking\nPending Review\nOverdue\nCompleted\nCancelled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "priority", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Priority", - "length": 0, - "no_copy": 0, "oldfieldname": "priority", "oldfieldtype": "Select", "options": "Low\nMedium\nHigh\nUrgent", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "color", "fieldtype": "Color", - "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": "Color", - "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 + "label": "Color" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "parent_task", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Parent Task", - "length": 0, - "no_copy": 0, "options": "Task", - "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 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, "collapsible_depends_on": "eval:doc.__islocal", - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "sb_timeline", "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": "Timeline", - "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 + "label": "Timeline" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "exp_start_date", "fieldtype": "Date", - "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": "Expected Start Date", - "length": 0, - "no_copy": 0, "oldfieldname": "exp_start_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "depends_on": "", - "description": "", - "fetch_if_empty": 0, "fieldname": "expected_time", "fieldtype": "Float", - "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": "Expected Time (in hours)", - "length": 0, - "no_copy": 0, "oldfieldname": "exp_total_hrs", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "fetch_from": "type.weight", - "fetch_if_empty": 0, "fieldname": "task_weight", "fieldtype": "Float", - "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": "Weight", - "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 + "label": "Weight" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "exp_end_date", "fieldtype": "Date", - "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": "Expected End Date", - "length": 0, - "no_copy": 0, "oldfieldname": "exp_end_date", "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "progress", "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "% Progress", - "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 + "label": "% Progress" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "is_milestone", "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 Milestone", - "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 + "label": "Is Milestone" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "sb_details", "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": "Details", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Text Editor", - "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": "Task Description", - "length": 0, - "no_copy": 0, "oldfieldname": "description", "oldfieldtype": "Text Editor", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "300px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "300px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "sb_depends_on", "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": "Depends On", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Dependencies", + "oldfieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "depends_on", "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": "depends_on", - "length": 0, - "no_copy": 0, - "options": "Task Depends On", - "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 + "label": "Dependent Tasks", + "options": "Task Depends On" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "depends_on_tasks", "fieldtype": "Data", "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": "Depends on Tasks", - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "", - "description": "", - "fetch_if_empty": 0, "fieldname": "sb_actual", "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": "", - "length": 0, - "no_copy": 0, "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "50%", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "act_start_date", "fieldtype": "Date", - "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": "Actual Start Date (via Time Sheet)", - "length": 0, - "no_copy": 0, "oldfieldname": "act_start_date", "oldfieldtype": "Date", - "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, - "default": "", - "depends_on": "", - "description": "", - "fetch_if_empty": 0, "fieldname": "actual_time", "fieldtype": "Float", - "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": "Actual Time (in hours)", - "length": 0, - "no_copy": 0, - "options": "", - "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, - "fetch_if_empty": 0, "fieldname": "column_break_15", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "act_end_date", "fieldtype": "Date", - "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": "Actual End Date (via Time Sheet)", - "length": 0, - "no_copy": 0, "oldfieldname": "act_end_date", "oldfieldtype": "Date", - "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": 1, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "sb_costing", "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": "Costing", - "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 + "label": "Costing" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "total_costing_amount", "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": "Total Costing Amount (via Time Sheet)", - "length": 0, - "no_copy": 0, "oldfieldname": "actual_budget", "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, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "total_expense_claim", "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": "Total Expense Claim (via Expense Claim)", - "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, - "fetch_if_empty": 0, "fieldname": "column_break_20", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "total_billing_amount", "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": "Total Billing Amount (via Time Sheet)", - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "sb_more_info", "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": "More Info", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "More Info" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.status == \"Closed\" || doc.status == \"Pending Review\"", - "fetch_if_empty": 0, "fieldname": "review_date", "fieldtype": "Date", - "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": "Review Date", - "length": 0, - "no_copy": 0, "oldfieldname": "review_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.status == \"Closed\"", - "fetch_if_empty": 0, "fieldname": "closing_date", "fieldtype": "Date", - "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": "Closing Date", - "length": 0, - "no_copy": 0, "oldfieldname": "closing_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_22", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "department", "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": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "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 + "options": "Department" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "company", "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": "Company", - "length": 0, - "no_copy": 0, "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "remember_last_selected_value": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "lft", "fieldtype": "Int", "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": "lft", - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "rgt", "fieldtype": "Int", "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": "rgt", - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "old_parent", "fieldtype": "Data", "hidden": 1, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Old Parent", - "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 + "read_only": 1 } ], - "has_web_view": 0, - "hide_toolbar": 0, "icon": "fa fa-check", "idx": 1, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, "max_attachments": 5, - "menu_index": 0, - "modified": "2019-04-20 22:45:20.777600", + "modified": "2019-04-29 14:48:10.204819", "modified_by": "Administrator", "module": "Projects", "name": "Task", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Projects User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, "search_fields": "subject", "show_name_in_global_search": 1, "sort_order": "DESC", "timeline_field": "project", "title_field": "subject", - "track_changes": 0, - "track_seen": 1, - "track_views": 0 + "track_seen": 1 } \ No newline at end of file