diff --git a/.flake8 b/.flake8 index 56c9b9a369..5735456ae7 100644 --- a/.flake8 +++ b/.flake8 @@ -28,6 +28,7 @@ ignore = B007, B950, W191, + E124, # closing bracket, irritating while writing QB code max-line-length = 200 exclude=.github/helper/semgrep_rules diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 8f938112a7..4d61f1fb94 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -40,6 +40,7 @@ body: - HR - projects - support + - CRM - assets - integrations - quality @@ -48,6 +49,7 @@ body: - agriculture - education - non-profit + - other validations: required: true diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 85f146d351..903196847d 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -12,17 +12,30 @@ git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench mkdir ~/frappe-bench/sites/test_site -cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/ -mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'" -mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" +if [ "$DB" == "mariadb" ];then + cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_mariadb.json" ~/frappe-bench/sites/test_site/site_config.json +else + cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_postgres.json" ~/frappe-bench/sites/test_site/site_config.json +fi -mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" -mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe" -mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" -mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'" -mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES" +if [ "$DB" == "mariadb" ];then + mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'" + mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" + + mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" + mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe" + mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" + + mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'" + mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES" +fi + +if [ "$DB" == "postgres" ];then + echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe" -U postgres; + echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe'" -U postgres; +fi wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz tar -xf /tmp/wkhtmltox.tar.xz -C /tmp diff --git a/.github/helper/site_config.json b/.github/helper/site_config_mariadb.json similarity index 99% rename from .github/helper/site_config.json rename to .github/helper/site_config_mariadb.json index 60ef80cbad..948ad08bab 100644 --- a/.github/helper/site_config.json +++ b/.github/helper/site_config_mariadb.json @@ -13,4 +13,4 @@ "host_name": "http://test_site:8000", "install_apps": ["erpnext"], "throttle_user_limit": 100 -} \ No newline at end of file +} diff --git a/.github/helper/site_config_postgres.json b/.github/helper/site_config_postgres.json new file mode 100644 index 0000000000..c82905fea0 --- /dev/null +++ b/.github/helper/site_config_postgres.json @@ -0,0 +1,18 @@ +{ + "db_host": "127.0.0.1", + "db_port": 5432, + "db_name": "test_frappe", + "db_password": "test_frappe", + "db_type": "postgres", + "allow_tests": true, + "auto_email_id": "test@example.com", + "mail_server": "smtp.example.com", + "mail_login": "test@example.com", + "mail_password": "test", + "admin_password": "admin", + "root_login": "postgres", + "root_password": "travis", + "host_name": "http://test_site:8000", + "install_apps": ["erpnext"], + "throttle_user_limit": 100 +} diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..3aaba71b12 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,55 @@ +accounts: +- erpnext/accounts/* +- erpnext/controllers/accounts_controller.py +- erpnext/controllers/taxes_and_totals.py + +stock: +- erpnext/stock/* +- erpnext/controllers/stock_controller.py +- erpnext/controllers/item_variant.py + +assets: +- erpnext/assets/* + +regional: +- erpnext/regional/* + +selling: +- erpnext/selling/* +- erpnext/controllers/selling_controller.py + +buying: +- erpnext/buying/* +- erpnext/controllers/buying_controller.py + +support: +- erpnext/support/* + +POS: +- pos* + +ecommerce: +- erpnext/e_commerce/* + +maintenance: +- erpnext/maintenance/* + +manufacturing: +- erpnext/manufacturing/* + +crm: +- erpnext/crm/* + +HR: +- erpnext/hr/* + +payroll: +- erpnext/payroll* + +projects: +- erpnext/projects/* + +# Any python files modifed but no test files modified +needs-tests: +- any: ['erpnext/**/*.py'] + all: ['!erpnext/**/test*.py'] diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml index db46c5621b..b644568d5e 100644 --- a/.github/workflows/docs-checker.yml +++ b/.github/workflows/docs-checker.yml @@ -12,7 +12,7 @@ jobs: - name: 'Setup Environment' uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.8 - name: 'Clone repo' uses: actions/checkout@v2 diff --git a/.github/workflows/labeller.yml b/.github/workflows/labeller.yml new file mode 100644 index 0000000000..a774400611 --- /dev/null +++ b/.github/workflows/labeller.yml @@ -0,0 +1,12 @@ +name: "Pull Request Labeler" +on: + pull_request_target: + types: [opened, reopened] + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v3 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 97bccf5d56..d05bbbec50 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.8 - name: Setup Node uses: actions/setup-node@v2 @@ -80,6 +80,9 @@ jobs: - name: Install run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + env: + DB: mariadb + TYPE: server - name: Run Patch Tests run: | diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests-mariadb.yml similarity index 94% rename from .github/workflows/server-tests.yml rename to .github/workflows/server-tests-mariadb.yml index 77c0aee195..7347a5856a 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -1,10 +1,11 @@ -name: Server +name: Server (Mariadb) on: pull_request: paths-ignore: - '**.js' - '**.md' + - '**.html' workflow_dispatch: push: branches: [ develop ] @@ -13,7 +14,7 @@ on: - '**.md' concurrency: - group: server-develop-${{ github.event.number }} + group: server-mariadb-develop-${{ github.event.number }} cancel-in-progress: true jobs: @@ -45,7 +46,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.8 - name: Setup Node uses: actions/setup-node@v2 @@ -92,6 +93,7 @@ jobs: - name: Install run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh env: + DB: mariadb TYPE: server - name: Run Tests diff --git a/.github/workflows/server-tests-postgres.yml b/.github/workflows/server-tests-postgres.yml new file mode 100644 index 0000000000..77d3c1ae61 --- /dev/null +++ b/.github/workflows/server-tests-postgres.yml @@ -0,0 +1,105 @@ +name: Server (Postgres) + +on: + pull_request: + paths-ignore: + - '**.js' + - '**.md' + - '**.html' + types: [opened, labelled, synchronize, reopened] + +concurrency: + group: server-postgres-develop-${{ github.event.number }} + cancel-in-progress: true + +jobs: + test: + if: ${{ contains(github.event.pull_request.labels.*.name, 'postgres') }} + runs-on: ubuntu-latest + timeout-minutes: 60 + + strategy: + fail-fast: false + matrix: + container: [1, 2, 3] + + name: Python Unit Tests + + services: + postgres: + image: postgres:13.3 + env: + POSTGRES_PASSWORD: travis + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + + - name: Clone + uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: 14 + check-latest: true + + - name: Add to Hosts + run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + + - name: Install + run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + env: + DB: postgres + TYPE: server + + - name: Run Tests + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator + env: + TYPE: server + CI_BUILD_ID: ${{ github.run_id }} + ORCHESTRATOR_URL: http://test-orchestrator.frappe.io diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index d765f0482c..ab6a53b5d9 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -36,7 +36,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.8 - uses: actions/setup-node@v2 with: diff --git a/CODEOWNERS b/CODEOWNERS index a4a14de1b8..bfc2601088 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -23,13 +23,13 @@ erpnext/stock/ @marination @rohitwaghchaure @ankush erpnext/crm/ @ruchamahabal @pateljannat erpnext/education/ @ruchamahabal @pateljannat -erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand erpnext/hr/ @ruchamahabal @pateljannat -erpnext/non_profit/ @ruchamahabal erpnext/payroll @ruchamahabal @pateljannat erpnext/projects/ @ruchamahabal @pateljannat -erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination +erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination @ankush +erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination @ankush +erpnext/public/ @nextchamp-saqib @marination -.github/ @surajshetty3416 @ankush +.github/ @ankush requirements.txt @gavindsouza diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 22c81ddd46..9e2cdfffd9 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -254,11 +254,13 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): enable_check = "enable_deferred_revenue" \ if doc.doctype=="Sales Invoice" else "enable_deferred_expense" + accounts_frozen_upto = frappe.get_cached_value('Accounts Settings', 'None', 'acc_frozen_upto') + def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on): start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date) if not (start_date and end_date): return - account_currency = get_account_currency(item.expense_account) + account_currency = get_account_currency(item.expense_account or item.income_account) if doc.doctype == "Sales Invoice": against, project = doc.customer, doc.project credit_account, debit_account = item.income_account, item.deferred_revenue_account @@ -279,6 +281,10 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): if not amount: return + # check if books nor frozen till endate: + if getdate(end_date) >= getdate(accounts_frozen_upto): + end_date = get_last_day(add_days(accounts_frozen_upto, 1)) + if via_journal_entry: book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry) @@ -406,8 +412,6 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against, 'account': credit_account, 'credit': base_amount, 'credit_in_account_currency': amount, - 'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier', - 'party': against, 'account_currency': account_currency, 'reference_name': doc.name, 'reference_type': doc.doctype, @@ -420,8 +424,6 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against, 'account': debit_account, 'debit': base_amount, 'debit_in_account_currency': amount, - 'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier', - 'party': against, 'account_currency': account_currency, 'reference_name': doc.name, 'reference_type': doc.doctype, diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js index 7a1d735948..320e1cab7c 100644 --- a/erpnext/accounts/doctype/account/account.js +++ b/erpnext/accounts/doctype/account/account.js @@ -43,12 +43,12 @@ frappe.ui.form.on('Account', { frm.trigger('add_toolbar_buttons'); } if (frm.has_perm('write')) { - frm.add_custom_button(__('Update Account Name / Number'), function () { - frm.trigger("update_account_number"); - }); frm.add_custom_button(__('Merge Account'), function () { frm.trigger("merge_account"); - }); + }, __('Actions')); + frm.add_custom_button(__('Update Account Name / Number'), function () { + frm.trigger("update_account_number"); + }, __('Actions')); } } }, @@ -59,11 +59,12 @@ frappe.ui.form.on('Account', { } }, add_toolbar_buttons: function(frm) { - frm.add_custom_button(__('Chart of Accounts'), - function () { frappe.set_route("Tree", "Account"); }); + frm.add_custom_button(__('Chart of Accounts'), () => { + frappe.set_route("Tree", "Account"); + }, __('View')); if (frm.doc.is_group == 1) { - frm.add_custom_button(__('Group to Non-Group'), function () { + frm.add_custom_button(__('Convert to Non-Group'), function () { return frappe.call({ doc: frm.doc, method: 'convert_group_to_ledger', @@ -71,10 +72,11 @@ frappe.ui.form.on('Account', { frm.refresh(); } }); - }); + }, __('Actions')); + } else if (cint(frm.doc.is_group) == 0 && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) { - frm.add_custom_button(__('Ledger'), function () { + frm.add_custom_button(__('General Ledger'), function () { frappe.route_options = { "account": frm.doc.name, "from_date": frappe.sys_defaults.year_start_date, @@ -82,9 +84,9 @@ frappe.ui.form.on('Account', { "company": frm.doc.company }; frappe.set_route("query-report", "General Ledger"); - }); + }, __('View')); - frm.add_custom_button(__('Non-Group to Group'), function () { + frm.add_custom_button(__('Convert to Group'), function () { return frappe.call({ doc: frm.doc, method: 'convert_ledger_to_group', @@ -92,7 +94,7 @@ frappe.ui.form.on('Account', { frm.refresh(); } }); - }); + }, __('Actions')); } }, diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js index 78e7ff6ea2..335f8502c7 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -7,7 +7,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", { frm.set_query("bank_account", function () { return { filters: { - company: ["in", frm.doc.company], + company: frm.doc.company, 'is_company_account': 1 }, }; diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index 0a2e0bc9ba..990d6d9c8d 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -239,7 +239,8 @@ frappe.ui.form.on("Bank Statement Import", { "withdrawal", "description", "reference_number", - "bank_account" + "bank_account", + "currency" ], }, }); diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index e786d13c95..1403303f53 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -16,6 +16,7 @@ from frappe.utils.xlsxutils import ILLEGAL_CHARACTERS_RE, handle_html from openpyxl.styles import Font from openpyxl.utils import get_column_letter +INVALID_VALUES = ("", None) class BankStatementImport(DataImport): def __init__(self, *args, **kwargs): @@ -95,6 +96,18 @@ def download_errored_template(data_import_name): data_import = frappe.get_doc("Bank Statement Import", data_import_name) data_import.export_errored_rows() +def parse_data_from_template(raw_data): + data = [] + + for i, row in enumerate(raw_data): + if all(v in INVALID_VALUES for v in row): + # empty row + continue + + data.append(row) + + return data + def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options): """This method runs in background job""" @@ -104,7 +117,8 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url, file = import_file_path if import_file_path else google_sheets_url import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records") - data = import_file.raw_data + + data = parse_data_from_template(import_file.raw_data) if import_file_path: add_bank_account(data, bank_account) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 44cea31ed3..51e1d6e9a0 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -2,9 +2,10 @@ # For license information, please see license.txt +from functools import reduce + import frappe from frappe.utils import flt -from six.moves import reduce from erpnext.controllers.status_updater import StatusUpdater diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js index ee23b1be5c..632fab0197 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.js +++ b/erpnext/accounts/doctype/cost_center/cost_center.js @@ -15,17 +15,6 @@ frappe.ui.form.on('Cost Center', { } } }); - - frm.set_query("cost_center", "distributed_cost_center", function() { - return { - filters: { - company: frm.doc.company, - is_group: 0, - enable_distributed_cost_center: 0, - name: ['!=', frm.doc.name] - } - }; - }); }, refresh: function(frm) { if (!frm.is_new()) { diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json index e7fa954e01..7cbb290947 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.json +++ b/erpnext/accounts/doctype/cost_center/cost_center.json @@ -16,9 +16,6 @@ "cb0", "is_group", "disabled", - "section_break_9", - "enable_distributed_cost_center", - "distributed_cost_center", "lft", "rgt", "old_parent" @@ -122,31 +119,13 @@ "fieldname": "disabled", "fieldtype": "Check", "label": "Disabled" - }, - { - "default": "0", - "fieldname": "enable_distributed_cost_center", - "fieldtype": "Check", - "label": "Enable Distributed Cost Center" - }, - { - "depends_on": "eval:doc.is_group==0", - "fieldname": "section_break_9", - "fieldtype": "Section Break" - }, - { - "depends_on": "enable_distributed_cost_center", - "fieldname": "distributed_cost_center", - "fieldtype": "Table", - "label": "Distributed Cost Center", - "options": "Distributed Cost Center" } ], "icon": "fa fa-money", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-06-17 16:09:30.025214", + "modified": "2022-01-31 13:22:58.916273", "modified_by": "Administrator", "module": "Accounts", "name": "Cost Center", @@ -189,5 +168,6 @@ "search_fields": "parent_cost_center, is_group", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py index 7ae0a72e3d..07cc0764e3 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -4,7 +4,6 @@ import frappe from frappe import _ -from frappe.utils import cint from frappe.utils.nestedset import NestedSet from erpnext.accounts.utils import validate_field_number @@ -20,24 +19,6 @@ class CostCenter(NestedSet): def validate(self): self.validate_mandatory() self.validate_parent_cost_center() - self.validate_distributed_cost_center() - - def validate_distributed_cost_center(self): - if cint(self.enable_distributed_cost_center): - if not self.distributed_cost_center: - frappe.throw(_("Please enter distributed cost center")) - if sum(x.percentage_allocation for x in self.distributed_cost_center) != 100: - frappe.throw(_("Total percentage allocation for distributed cost center should be equal to 100")) - if not self.get('__islocal'): - if not cint(frappe.get_cached_value("Cost Center", {"name": self.name}, "enable_distributed_cost_center")) \ - and self.check_if_part_of_distributed_cost_center(): - frappe.throw(_("Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center")) - if next((True for x in self.distributed_cost_center if x.cost_center == x.parent), False): - frappe.throw(_("Parent Cost Center cannot be added in Distributed Cost Center")) - if check_if_distributed_cost_center_enabled(list(x.cost_center for x in self.distributed_cost_center)): - frappe.throw(_("A Distributed Cost Center cannot be added in the Distributed Cost Center allocation table.")) - else: - self.distributed_cost_center = [] def validate_mandatory(self): if self.cost_center_name != self.company and not self.parent_cost_center: @@ -64,10 +45,10 @@ class CostCenter(NestedSet): @frappe.whitelist() def convert_ledger_to_group(self): - if cint(self.enable_distributed_cost_center): - frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group")) - if self.check_if_part_of_distributed_cost_center(): - frappe.throw(_("Cost Center Already Allocated in a Distributed Cost Center cannot be converted to group")) + if self.if_allocation_exists_against_cost_center(): + frappe.throw(_("Cost Center with Allocation records can not be converted to a group")) + if self.check_if_part_of_cost_center_allocation(): + frappe.throw(_("Cost Center is a part of Cost Center Allocation, hence cannot be converted to a group")) if self.check_gle_exists(): frappe.throw(_("Cost Center with existing transactions can not be converted to group")) self.is_group = 1 @@ -81,8 +62,17 @@ class CostCenter(NestedSet): return frappe.db.sql("select name from `tabCost Center` where \ parent_cost_center = %s and docstatus != 2", self.name) - def check_if_part_of_distributed_cost_center(self): - return frappe.db.get_value("Distributed Cost Center", {"cost_center": self.name}) + def if_allocation_exists_against_cost_center(self): + return frappe.db.get_value("Cost Center Allocation", filters = { + "main_cost_center": self.name, + "docstatus": 1 + }) + + def check_if_part_of_cost_center_allocation(self): + return frappe.db.get_value("Cost Center Allocation Percentage", filters = { + "cost_center": self.name, + "docstatus": 1 + }) def before_rename(self, olddn, newdn, merge=False): # Add company abbr if not provided @@ -126,8 +116,4 @@ def on_doctype_update(): def get_name_with_number(new_account, account_number): if account_number and not new_account[0].isdigit(): new_account = account_number + " - " + new_account - return new_account - -def check_if_distributed_cost_center_enabled(cost_center_list): - value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1) - return next((True for x in value_list if x[0]), False) + return new_account \ No newline at end of file diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py index f8615ec03a..ff50a21124 100644 --- a/erpnext/accounts/doctype/cost_center/test_cost_center.py +++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py @@ -23,33 +23,6 @@ class TestCostCenter(unittest.TestCase): self.assertRaises(frappe.ValidationError, cost_center.save) - def test_validate_distributed_cost_center(self): - - if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center - _TC'}): - frappe.get_doc(test_records[0]).insert() - - if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}): - frappe.get_doc(test_records[1]).insert() - - invalid_distributed_cost_center = frappe.get_doc({ - "company": "_Test Company", - "cost_center_name": "_Test Distributed Cost Center", - "doctype": "Cost Center", - "is_group": 0, - "parent_cost_center": "_Test Company - _TC", - "enable_distributed_cost_center": 1, - "distributed_cost_center": [{ - "cost_center": "_Test Cost Center - _TC", - "percentage_allocation": 40 - }, { - "cost_center": "_Test Cost Center 2 - _TC", - "percentage_allocation": 50 - } - ] - }) - - self.assertRaises(frappe.ValidationError, invalid_distributed_cost_center.save) - def create_cost_center(**args): args = frappe._dict(args) if args.cost_center_name: diff --git a/erpnext/accounts/doctype/distributed_cost_center/__init__.py b/erpnext/accounts/doctype/cost_center_allocation/__init__.py similarity index 100% rename from erpnext/accounts/doctype/distributed_cost_center/__init__.py rename to erpnext/accounts/doctype/cost_center_allocation/__init__.py diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js new file mode 100644 index 0000000000..ab0baab24a --- /dev/null +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js @@ -0,0 +1,19 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Cost Center Allocation', { + setup: function(frm) { + let filters = {"is_group": 0}; + if (frm.doc.company) { + $.extend(filters, { + "company": frm.doc.company + }); + } + + frm.set_query('main_cost_center', function() { + return { + filters: filters + }; + }); + } +}); diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json new file mode 100644 index 0000000000..45ab886d33 --- /dev/null +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json @@ -0,0 +1,128 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "CC-ALLOC-.#####", + "creation": "2022-01-13 20:07:29.871109", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "main_cost_center", + "company", + "column_break_2", + "valid_from", + "section_break_5", + "allocation_percentages", + "amended_from" + ], + "fields": [ + { + "fieldname": "main_cost_center", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Main Cost Center", + "options": "Cost Center", + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "valid_from", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Valid From", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fetch_from": "main_cost_center.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "allocation_percentages", + "fieldtype": "Table", + "label": "Cost Center Allocation Percentages", + "options": "Cost Center Allocation Percentage", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Cost Center Allocation", + "print_hide": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2022-01-31 11:47:12.086253", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Cost Center Allocation", + "name_case": "UPPER CASE", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py new file mode 100644 index 0000000000..bad3fb4f96 --- /dev/null +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py @@ -0,0 +1,90 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import add_days, format_date, getdate + + +class MainCostCenterCantBeChild(frappe.ValidationError): + pass +class InvalidMainCostCenter(frappe.ValidationError): + pass +class InvalidChildCostCenter(frappe.ValidationError): + pass +class WrongPercentageAllocation(frappe.ValidationError): + pass +class InvalidDateError(frappe.ValidationError): + pass + +class CostCenterAllocation(Document): + def validate(self): + self.validate_total_allocation_percentage() + self.validate_from_date_based_on_existing_gle() + self.validate_backdated_allocation() + self.validate_main_cost_center() + self.validate_child_cost_centers() + + def validate_total_allocation_percentage(self): + total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])]) + + if total_percentage != 100: + frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation) + + def validate_from_date_based_on_existing_gle(self): + # Check if GLE exists against the main cost center + # If exists ensure from date is set after posting date of last GLE + + last_gle_date = frappe.db.get_value("GL Entry", + {"cost_center": self.main_cost_center, "is_cancelled": 0}, + "posting_date", order_by="posting_date desc") + + if last_gle_date: + if getdate(self.valid_from) <= getdate(last_gle_date): + frappe.throw(_("Valid From must be after {0} as last GL Entry against the cost center {1} posted on this date") + .format(last_gle_date, self.main_cost_center), InvalidDateError) + + def validate_backdated_allocation(self): + # Check if there are any future existing allocation records against the main cost center + # If exists, warn the user about it + + future_allocation = frappe.db.get_value("Cost Center Allocation", filters = { + "main_cost_center": self.main_cost_center, + "valid_from": (">=", self.valid_from), + "name": ("!=", self.name), + "docstatus": 1 + }, fieldname=['valid_from', 'name'], order_by='valid_from', as_dict=1) + + if future_allocation: + frappe.msgprint(_("Another Cost Center Allocation record {0} applicable from {1}, hence this allocation will be applicable upto {2}") + .format(frappe.bold(future_allocation.name), frappe.bold(format_date(future_allocation.valid_from)), + frappe.bold(format_date(add_days(future_allocation.valid_from, -1)))), + title=_("Warning!"), indicator="orange", alert=1 + ) + + def validate_main_cost_center(self): + # Main cost center itself cannot be entered in child table + if self.main_cost_center in [d.cost_center for d in self.allocation_percentages]: + frappe.throw(_("Main Cost Center {0} cannot be entered in the child table") + .format(self.main_cost_center), MainCostCenterCantBeChild) + + # If main cost center is used for allocation under any other cost center, + # allocation cannot be done against it + parent = frappe.db.get_value("Cost Center Allocation Percentage", filters = { + "cost_center": self.main_cost_center, + "docstatus": 1 + }, fieldname='parent') + if parent: + frappe.throw(_("{0} cannot be used as a Main Cost Center because it has been used as child in Cost Center Allocation {1}") + .format(self.main_cost_center, parent), InvalidMainCostCenter) + + def validate_child_cost_centers(self): + # Check if child cost center is used as main cost center in any existing allocation + main_cost_centers = [d.main_cost_center for d in + frappe.get_all("Cost Center Allocation", {'docstatus': 1}, 'main_cost_center')] + + for d in self.allocation_percentages: + if d.cost_center in main_cost_centers: + frappe.throw(_("Cost Center {0} cannot be used for allocation as it is used as main cost center in other allocation record.") + .format(d.cost_center), InvalidChildCostCenter) \ No newline at end of file diff --git a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py new file mode 100644 index 0000000000..9cf4c00217 --- /dev/null +++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py @@ -0,0 +1,156 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import unittest + +import frappe +from frappe.utils import add_days, today + +from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center +from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation import ( + InvalidChildCostCenter, + InvalidDateError, + InvalidMainCostCenter, + MainCostCenterCantBeChild, + WrongPercentageAllocation, +) +from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry + + +class TestCostCenterAllocation(unittest.TestCase): + def setUp(self): + cost_centers = ["Main Cost Center 1", "Main Cost Center 2", "Sub Cost Center 1", "Sub Cost Center 2"] + for cc in cost_centers: + create_cost_center(cost_center_name=cc, company="_Test Company") + + def test_gle_based_on_cost_center_allocation(self): + cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC", + { + "Sub Cost Center 1 - _TC": 60, + "Sub Cost Center 2 - _TC": 40 + } + ) + + jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100, + cost_center = "Main Cost Center 1 - _TC", submit=True) + + expected_values = [ + ["Sub Cost Center 1 - _TC", 0.0, 60], + ["Sub Cost Center 2 - _TC", 0.0, 40] + ] + + gle = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gle) + .select(gle.cost_center, gle.debit, gle.credit) + .where(gle.voucher_type == 'Journal Entry') + .where(gle.voucher_no == jv.name) + .where(gle.account == 'Sales - _TC') + .orderby(gle.cost_center) + ).run(as_dict=1) + + self.assertTrue(gl_entries) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_values[i][0], gle.cost_center) + self.assertEqual(expected_values[i][1], gle.debit) + self.assertEqual(expected_values[i][2], gle.credit) + + cca.cancel() + jv.cancel() + + def test_main_cost_center_cant_be_child(self): + # Main cost center itself cannot be entered in child table + cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC", + { + "Sub Cost Center 1 - _TC": 60, + "Main Cost Center 1 - _TC": 40 + }, save=False + ) + + self.assertRaises(MainCostCenterCantBeChild, cca.save) + + def test_invalid_main_cost_center(self): + # If main cost center is used for allocation under any other cost center, + # allocation cannot be done against it + cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC", + { + "Sub Cost Center 1 - _TC": 60, + "Sub Cost Center 2 - _TC": 40 + } + ) + + cca2 = create_cost_center_allocation("_Test Company", "Sub Cost Center 1 - _TC", + { + "Sub Cost Center 2 - _TC": 100 + }, save=False + ) + + self.assertRaises(InvalidMainCostCenter, cca2.save) + + cca1.cancel() + + def test_if_child_cost_center_has_any_allocation_record(self): + # Check if any child cost center is used as main cost center in any other existing allocation + cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC", + { + "Sub Cost Center 1 - _TC": 60, + "Sub Cost Center 2 - _TC": 40 + } + ) + + cca2 = create_cost_center_allocation("_Test Company", "Main Cost Center 2 - _TC", + { + "Main Cost Center 1 - _TC": 60, + "Sub Cost Center 1 - _TC": 40 + }, save=False + ) + + self.assertRaises(InvalidChildCostCenter, cca2.save) + + cca1.cancel() + + def test_total_percentage(self): + cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC", + { + "Sub Cost Center 1 - _TC": 40, + "Sub Cost Center 2 - _TC": 40 + }, save=False + ) + self.assertRaises(WrongPercentageAllocation, cca.save) + + def test_valid_from_based_on_existing_gle(self): + # GLE posted against Sub Cost Center 1 on today + jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100, + cost_center = "Main Cost Center 1 - _TC", posting_date=today(), submit=True) + + # try to set valid from as yesterday + cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC", + { + "Sub Cost Center 1 - _TC": 60, + "Sub Cost Center 2 - _TC": 40 + }, valid_from=add_days(today(), -1), save=False + ) + + self.assertRaises(InvalidDateError, cca.save) + + jv.cancel() + +def create_cost_center_allocation(company, main_cost_center, allocation_percentages, + valid_from=None, valid_upto=None, save=True, submit=True): + doc = frappe.new_doc("Cost Center Allocation") + doc.main_cost_center = main_cost_center + doc.company = company + doc.valid_from = valid_from or today() + doc.valid_upto = valid_upto + for cc, percentage in allocation_percentages.items(): + doc.append("allocation_percentages", { + "cost_center": cc, + "percentage": percentage + }) + if save: + doc.save() + if submit: + doc.submit() + + return doc \ No newline at end of file diff --git a/erpnext/agriculture/__init__.py b/erpnext/accounts/doctype/cost_center_allocation_percentage/__init__.py similarity index 100% rename from erpnext/agriculture/__init__.py rename to erpnext/accounts/doctype/cost_center_allocation_percentage/__init__.py diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json similarity index 63% rename from erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json rename to erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json index 45b0e2df9b..4b871ae88d 100644 --- a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json +++ b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json @@ -1,12 +1,13 @@ { "actions": [], - "creation": "2020-03-19 12:34:01.500390", + "allow_rename": 1, + "creation": "2022-01-03 18:10:11.697198", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "cost_center", - "percentage_allocation" + "percentage" ], "fields": [ { @@ -18,23 +19,23 @@ "reqd": 1 }, { - "fieldname": "percentage_allocation", - "fieldtype": "Float", + "fieldname": "percentage", + "fieldtype": "Int", "in_list_view": 1, - "label": "Percentage Allocation", + "label": "Percentage (%)", "reqd": 1 } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-03-19 12:54:43.674655", + "modified": "2022-01-03 18:10:20.029821", "modified_by": "Administrator", "module": "Accounts", - "name": "Distributed Cost Center", + "name": "Cost Center Allocation Percentage", "owner": "Administrator", "permissions": [], - "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1 + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py new file mode 100644 index 0000000000..7d20efb6d5 --- /dev/null +++ b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CostCenterAllocationPercentage(Document): + pass diff --git a/erpnext/agriculture/doctype/__init__.py b/erpnext/accounts/doctype/currency_exchange_settings/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/__init__.py rename to erpnext/accounts/doctype/currency_exchange_settings/__init__.py diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js new file mode 100644 index 0000000000..6c40f2bec0 --- /dev/null +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js @@ -0,0 +1,45 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Currency Exchange Settings', { + service_provider: function(frm) { + if (frm.doc.service_provider == "exchangerate.host") { + let result = ['result']; + let params = { + date: '{transaction_date}', + from: '{from_currency}', + to: '{to_currency}' + }; + add_param(frm, "https://api.exchangerate.host/convert", params, result); + } else if (frm.doc.service_provider == "frankfurter.app") { + let result = ['rates', '{to_currency}']; + let params = { + base: '{from_currency}', + symbols: '{to_currency}' + }; + add_param(frm, "https://frankfurter.app/{transaction_date}", params, result); + } + } +}); + + +function add_param(frm, api, params, result) { + var row; + frm.clear_table("req_params"); + frm.clear_table("result_key"); + + frm.doc.api_endpoint = api; + + $.each(params, function(key, value) { + row = frm.add_child("req_params"); + row.key = key; + row.value = value; + }); + + $.each(result, function(key, value) { + row = frm.add_child("result_key"); + row.key = value; + }); + + frm.refresh_fields(); +} diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json new file mode 100644 index 0000000000..7921fcc2b9 --- /dev/null +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json @@ -0,0 +1,126 @@ +{ + "actions": [], + "creation": "2022-01-10 13:03:26.237081", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "api_details_section", + "service_provider", + "api_endpoint", + "url", + "column_break_3", + "help", + "section_break_2", + "req_params", + "column_break_4", + "result_key" + ], + "fields": [ + { + "fieldname": "api_details_section", + "fieldtype": "Section Break", + "label": "API Details" + }, + { + "fieldname": "api_endpoint", + "fieldtype": "Data", + "in_list_view": 1, + "label": "API Endpoint", + "read_only_depends_on": "eval: doc.service_provider != \"Custom\"", + "reqd": 1 + }, + { + "fieldname": "url", + "fieldtype": "Data", + "label": "Example URL", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "help", + "fieldtype": "HTML", + "label": "Help", + "options": "

Currency Exchange Settings Help

\n

There are 3 variables that could be used within the endpoint, result key and in values of the parameter.

\n

Exchange rate between {from_currency} and {to_currency} on {transaction_date} is fetched by the API.

\n

Example: If your endpoint is exchange.com/2021-08-01, then, you will have to input exchange.com/{transaction_date}

" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "label": "Request Parameters" + }, + { + "fieldname": "req_params", + "fieldtype": "Table", + "label": "Parameters", + "options": "Currency Exchange Settings Details", + "read_only_depends_on": "eval: doc.service_provider != \"Custom\"", + "reqd": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "result_key", + "fieldtype": "Table", + "label": "Result Key", + "options": "Currency Exchange Settings Result", + "read_only_depends_on": "eval: doc.service_provider != \"Custom\"", + "reqd": 1 + }, + { + "fieldname": "service_provider", + "fieldtype": "Select", + "label": "Service Provider", + "options": "frankfurter.app\nexchangerate.host\nCustom", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2022-01-10 15:51:14.521174", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Currency Exchange Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py new file mode 100644 index 0000000000..e16ff3aa7e --- /dev/null +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py @@ -0,0 +1,82 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +import requests +from frappe import _ +from frappe.model.document import Document +from frappe.utils import nowdate + + +class CurrencyExchangeSettings(Document): + def validate(self): + self.set_parameters_and_result() + response, value = self.validate_parameters() + self.validate_result(response, value) + + def set_parameters_and_result(self): + if self.service_provider == 'exchangerate.host': + self.set('result_key', []) + self.set('req_params', []) + + self.api_endpoint = "https://api.exchangerate.host/convert" + self.append('result_key', {'key': 'result'}) + self.append('req_params', {'key': 'date', 'value': '{transaction_date}'}) + self.append('req_params', {'key': 'from', 'value': '{from_currency}'}) + self.append('req_params', {'key': 'to', 'value': '{to_currency}'}) + elif self.service_provider == 'frankfurter.app': + self.set('result_key', []) + self.set('req_params', []) + + self.api_endpoint = "https://frankfurter.app/{transaction_date}" + self.append('result_key', {'key': 'rates'}) + self.append('result_key', {'key': '{to_currency}'}) + self.append('req_params', {'key': 'base', 'value': '{from_currency}'}) + self.append('req_params', {'key': 'symbols', 'value': '{to_currency}'}) + + def validate_parameters(self): + if frappe.flags.in_test: + return None, None + + params = {} + for row in self.req_params: + params[row.key] = row.value.format( + transaction_date=nowdate(), + to_currency='INR', + from_currency='USD' + ) + + api_url = self.api_endpoint.format( + transaction_date=nowdate(), + to_currency='INR', + from_currency='USD' + ) + + try: + response = requests.get(api_url, params=params) + except requests.exceptions.RequestException as e: + frappe.throw("Error: " + str(e)) + + response.raise_for_status() + value = response.json() + + return response, value + + def validate_result(self, response, value): + if frappe.flags.in_test: + return + + try: + for key in self.result_key: + value = value[str(key.key).format( + transaction_date=nowdate(), + to_currency='INR', + from_currency='USD' + )] + except Exception: + frappe.throw("Invalid result key. Response: " + response.text) + if not isinstance(value, (int, float)): + frappe.throw(_("Returned exchange rate is neither integer not float.")) + + self.url = response.url + frappe.msgprint("Exchange rate of USD to INR is " + str(value)) diff --git a/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py new file mode 100644 index 0000000000..2778729f58 --- /dev/null +++ b/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +import unittest + + +class TestCurrencyExchangeSettings(unittest.TestCase): + pass diff --git a/erpnext/agriculture/doctype/agriculture_analysis_criteria/__init__.py b/erpnext/accounts/doctype/currency_exchange_settings_details/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/agriculture_analysis_criteria/__init__.py rename to erpnext/accounts/doctype/currency_exchange_settings_details/__init__.py diff --git a/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.json b/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.json new file mode 100644 index 0000000000..30935871c6 --- /dev/null +++ b/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.json @@ -0,0 +1,39 @@ +{ + "actions": [], + "creation": "2021-09-02 14:54:49.033512", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "key", + "value" + ], + "fields": [ + { + "fieldname": "key", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Key", + "reqd": 1 + }, + { + "fieldname": "value", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Value", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-11-03 19:14:55.889037", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Currency Exchange Settings Details", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.py b/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.py new file mode 100644 index 0000000000..a6ad7634a5 --- /dev/null +++ b/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CurrencyExchangeSettingsDetails(Document): + pass diff --git a/erpnext/agriculture/doctype/agriculture_task/__init__.py b/erpnext/accounts/doctype/currency_exchange_settings_result/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/agriculture_task/__init__.py rename to erpnext/accounts/doctype/currency_exchange_settings_result/__init__.py diff --git a/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.json b/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.json new file mode 100644 index 0000000000..fff5337616 --- /dev/null +++ b/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2021-09-03 13:17:22.088259", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "key" + ], + "fields": [ + { + "fieldname": "key", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Key", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-11-03 19:14:40.054245", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Currency Exchange Settings Result", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.py b/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.py new file mode 100644 index 0000000000..177412860a --- /dev/null +++ b/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CurrencyExchangeSettingsResult(Document): + pass diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 957a50f013..617b376128 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -31,7 +31,7 @@ frappe.ui.form.on("Journal Entry", { if(frm.doc.docstatus==1) { frm.add_custom_button(__('Reverse Journal Entry'), function() { return erpnext.journal_entry.reverse_journal_entry(frm); - }, __('Make')); + }, __('Actions')); } if (frm.doc.__islocal) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 20678d787b..335fd350de 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -13,6 +13,7 @@ "voucher_type", "naming_series", "finance_book", + "reversal_of", "tax_withholding_category", "column_break1", "from_template", @@ -515,13 +516,21 @@ "fieldname": "apply_tds", "fieldtype": "Check", "label": "Apply Tax Withholding Amount " + }, + { + "depends_on": "eval:doc.docstatus", + "fieldname": "reversal_of", + "fieldtype": "Link", + "label": "Reversal Of", + "options": "Journal Entry", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 176, "is_submittable": 1, "links": [], - "modified": "2021-09-09 15:31:14.484029", + "modified": "2022-01-04 13:39:36.485954", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ca17265078..ac8ab31024 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -407,13 +407,14 @@ class JournalEntry(AccountsController): debit_or_credit = 'Debit' if d.debit else 'Credit' party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no, debit_or_credit) + against_voucher = ['', against_voucher[1]] else: if d.reference_type == "Sales Invoice": party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1] else: party_account = against_voucher[1] - if (against_voucher[0] != d.party or party_account != d.account): + if (against_voucher[0] != cstr(d.party) or party_account != d.account): frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}") .format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1], d.reference_type, d.reference_name)) @@ -478,13 +479,22 @@ class JournalEntry(AccountsController): def set_against_account(self): accounts_debited, accounts_credited = [], [] - for d in self.get("accounts"): - if flt(d.debit > 0): accounts_debited.append(d.party or d.account) - if flt(d.credit) > 0: accounts_credited.append(d.party or d.account) + if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'): + for d in self.get('accounts'): + if d.reference_type == 'Sales Invoice': + field = 'customer' + else: + field = 'supplier' - for d in self.get("accounts"): - if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited))) - if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited))) + d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field) + else: + for d in self.get("accounts"): + if flt(d.debit > 0): accounts_debited.append(d.party or d.account) + if flt(d.credit) > 0: accounts_credited.append(d.party or d.account) + + for d in self.get("accounts"): + if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited))) + if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited))) def validate_debit_credit_amount(self): for d in self.get('accounts'): @@ -1157,9 +1167,8 @@ def make_inter_company_journal_entry(name, voucher_type, company): def make_reverse_journal_entry(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc - def update_accounts(source, target, source_parent): - target.reference_type = "Journal Entry" - target.reference_name = source_parent.name + def post_process(source, target): + target.reversal_of = source.name doclist = get_mapped_doc("Journal Entry", source_name, { "Journal Entry": { @@ -1177,9 +1186,8 @@ def make_reverse_journal_entry(source_name, target_doc=None): "debit": "credit", "credit_in_account_currency": "debit_in_account_currency", "credit": "debit", - }, - "postprocess": update_accounts, + } }, - }, target_doc) + }, target_doc, post_process) return doclist diff --git a/erpnext/agriculture/doctype/crop/__init__.py b/erpnext/accounts/doctype/ledger_merge/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/crop/__init__.py rename to erpnext/accounts/doctype/ledger_merge/__init__.py diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.js b/erpnext/accounts/doctype/ledger_merge/ledger_merge.js new file mode 100644 index 0000000000..b2db98dbd0 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.js @@ -0,0 +1,128 @@ +// Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Ledger Merge', { + setup: function(frm) { + frappe.realtime.on('ledger_merge_refresh', ({ ledger_merge }) => { + if (ledger_merge !== frm.doc.name) return; + frappe.model.clear_doc(frm.doc.doctype, frm.doc.name); + frappe.model.with_doc(frm.doc.doctype, frm.doc.name).then(() => { + frm.refresh(); + }); + }); + + frappe.realtime.on('ledger_merge_progress', data => { + if (data.ledger_merge !== frm.doc.name) return; + let message = __('Merging {0} of {1}', [data.current, data.total]); + let percent = Math.floor((data.current * 100) / data.total); + frm.dashboard.show_progress(__('Merge Progress'), percent, message); + frm.page.set_indicator(__('In Progress'), 'orange'); + }); + + frm.set_query("account", function(doc) { + if (!doc.company) frappe.throw(__('Please set Company')); + if (!doc.root_type) frappe.throw(__('Please set Root Type')); + return { + filters: { + root_type: doc.root_type, + company: doc.company + } + }; + }); + + frm.set_query('account', 'merge_accounts', function(doc) { + if (!doc.company) frappe.throw(__('Please set Company')); + if (!doc.root_type) frappe.throw(__('Please set Root Type')); + if (!doc.account) frappe.throw(__('Please set Account')); + let acc = [doc.account]; + frm.doc.merge_accounts.forEach((row) => { + acc.push(row.account); + }); + return { + filters: { + is_group: doc.is_group, + root_type: doc.root_type, + name: ["not in", acc], + company: doc.company + } + }; + }); + }, + + refresh: function(frm) { + frm.page.hide_icon_group(); + frm.trigger('set_merge_status'); + frm.trigger('update_primary_action'); + }, + + after_save: function(frm) { + setTimeout(() => { + frm.trigger('update_primary_action'); + }, 500); + }, + + update_primary_action: function(frm) { + if (frm.is_dirty()) { + frm.enable_save(); + return; + } + frm.disable_save(); + if (frm.doc.status !== 'Success') { + if (!frm.is_new()) { + let label = frm.doc.status === 'Pending' ? __('Start Merge') : __('Retry'); + frm.page.set_primary_action(label, () => frm.events.start_merge(frm)); + } else { + frm.page.set_primary_action(__('Save'), () => frm.save()); + } + } + }, + + start_merge: function(frm) { + frm.call({ + method: 'form_start_merge', + args: { docname: frm.doc.name }, + btn: frm.page.btn_primary + }).then(r => { + if (r.message === true) { + frm.disable_save(); + } + }); + }, + + set_merge_status: function(frm) { + if (frm.doc.status == "Pending") return; + let successful_records = 0; + frm.doc.merge_accounts.forEach((row) => { + if (row.merged) successful_records += 1; + }); + let message_args = [successful_records, frm.doc.merge_accounts.length]; + frm.dashboard.set_headline(__('Successfully merged {0} out of {1}.', message_args)); + }, + + root_type: function(frm) { + frm.set_value('account', ''); + frm.set_value('merge_accounts', []); + }, + + company: function(frm) { + frm.set_value('account', ''); + frm.set_value('merge_accounts', []); + } +}); + +frappe.ui.form.on('Ledger Merge Accounts', { + merge_accounts_add: function(frm) { + frm.trigger('update_primary_action'); + }, + + merge_accounts_remove: function(frm) { + frm.trigger('update_primary_action'); + }, + + account: function(frm, cdt, cdn) { + let row = frappe.get_doc(cdt, cdn); + row.account_name = row.account; + frm.refresh_field('merge_accounts'); + frm.trigger('update_primary_action'); + } +}); diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.json b/erpnext/accounts/doctype/ledger_merge/ledger_merge.json new file mode 100644 index 0000000000..dd816df627 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.json @@ -0,0 +1,130 @@ +{ + "actions": [], + "autoname": "format:{account_name} merger on {creation}", + "creation": "2021-12-09 15:38:04.556584", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "root_type", + "account", + "account_name", + "column_break_3", + "company", + "status", + "is_group", + "section_break_5", + "merge_accounts" + ], + "fields": [ + { + "depends_on": "root_type", + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "merge_accounts", + "fieldtype": "Table", + "label": "Accounts to Merge", + "options": "Ledger Merge Accounts", + "reqd": 1 + }, + { + "depends_on": "account", + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Pending\nSuccess\nPartial Success\nError", + "read_only": 1 + }, + { + "fieldname": "root_type", + "fieldtype": "Select", + "label": "Root Type", + "options": "\nAsset\nLiability\nIncome\nExpense\nEquity", + "reqd": 1, + "set_only_once": 1 + }, + { + "depends_on": "account", + "fetch_from": "account.account_name", + "fetch_if_empty": 1, + "fieldname": "account_name", + "fieldtype": "Data", + "label": "Account Name", + "read_only": 1, + "reqd": 1 + }, + { + "default": "0", + "depends_on": "account", + "fetch_from": "account.is_group", + "fieldname": "is_group", + "fieldtype": "Check", + "label": "Is Group", + "read_only": 1 + } + ], + "hide_toolbar": 1, + "links": [], + "modified": "2021-12-12 21:34:55.155146", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Ledger Merge", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py new file mode 100644 index 0000000000..830ad370d7 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py @@ -0,0 +1,76 @@ +# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document + +from erpnext.accounts.doctype.account.account import merge_account + + +class LedgerMerge(Document): + def start_merge(self): + from frappe.core.page.background_jobs.background_jobs import get_info + from frappe.utils.background_jobs import enqueue + from frappe.utils.scheduler import is_scheduler_inactive + + if is_scheduler_inactive() and not frappe.flags.in_test: + frappe.throw( + _("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive") + ) + + enqueued_jobs = [d.get("job_name") for d in get_info()] + + if self.name not in enqueued_jobs: + enqueue( + start_merge, + queue="default", + timeout=6000, + event="ledger_merge", + job_name=self.name, + docname=self.name, + now=frappe.conf.developer_mode or frappe.flags.in_test, + ) + return True + + return False + +@frappe.whitelist() +def form_start_merge(docname): + return frappe.get_doc("Ledger Merge", docname).start_merge() + +def start_merge(docname): + ledger_merge = frappe.get_doc("Ledger Merge", docname) + successful_merges = 0 + total = len(ledger_merge.merge_accounts) + for row in ledger_merge.merge_accounts: + if not row.merged: + try: + merge_account( + row.account, + ledger_merge.account, + ledger_merge.is_group, + ledger_merge.root_type, + ledger_merge.company + ) + row.db_set('merged', 1) + frappe.db.commit() + successful_merges += 1 + frappe.publish_realtime("ledger_merge_progress", { + "ledger_merge": ledger_merge.name, + "current": successful_merges, + "total": total + } + ) + except Exception: + frappe.db.rollback() + frappe.log_error(title=ledger_merge.name) + finally: + if successful_merges == total: + ledger_merge.db_set('status', 'Success') + elif successful_merges > 0: + ledger_merge.db_set('status', 'Partial Success') + else: + ledger_merge.db_set('status', 'Error') + + frappe.publish_realtime("ledger_merge_refresh", {"ledger_merge": ledger_merge.name}) diff --git a/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py new file mode 100644 index 0000000000..f7315362b7 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py @@ -0,0 +1,118 @@ +# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and Contributors +# See license.txt + +import unittest + +import frappe + +from erpnext.accounts.doctype.ledger_merge.ledger_merge import start_merge + + +class TestLedgerMerge(unittest.TestCase): + def test_merge_success(self): + if not frappe.db.exists("Account", "Indirect Expenses - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Indirect Expenses" + acc.is_group = 1 + acc.parent_account = "Expenses - _TC" + acc.company = "_Test Company" + acc.insert() + if not frappe.db.exists("Account", "Indirect Test Expenses - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Indirect Test Expenses" + acc.is_group = 1 + acc.parent_account = "Expenses - _TC" + acc.company = "_Test Company" + acc.insert() + if not frappe.db.exists("Account", "Administrative Test Expenses - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Administrative Test Expenses" + acc.parent_account = "Indirect Test Expenses - _TC" + acc.company = "_Test Company" + acc.insert() + + doc = frappe.get_doc({ + "doctype": "Ledger Merge", + "company": "_Test Company", + "root_type": frappe.db.get_value("Account", "Indirect Test Expenses - _TC", "root_type"), + "account": "Indirect Expenses - _TC", + "merge_accounts": [ + { + "account": "Indirect Test Expenses - _TC", + "account_name": "Indirect Expenses" + } + ] + }).insert(ignore_permissions=True) + + parent = frappe.db.get_value("Account", "Administrative Test Expenses - _TC", "parent_account") + self.assertEqual(parent, "Indirect Test Expenses - _TC") + + start_merge(doc.name) + + parent = frappe.db.get_value("Account", "Administrative Test Expenses - _TC", "parent_account") + self.assertEqual(parent, "Indirect Expenses - _TC") + + self.assertFalse(frappe.db.exists("Account", "Indirect Test Expenses - _TC")) + + def test_partial_merge_success(self): + if not frappe.db.exists("Account", "Indirect Income - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Indirect Income" + acc.is_group = 1 + acc.parent_account = "Income - _TC" + acc.company = "_Test Company" + acc.insert() + if not frappe.db.exists("Account", "Indirect Test Income - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Indirect Test Income" + acc.is_group = 1 + acc.parent_account = "Income - _TC" + acc.company = "_Test Company" + acc.insert() + if not frappe.db.exists("Account", "Administrative Test Income - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Administrative Test Income" + acc.parent_account = "Indirect Test Income - _TC" + acc.company = "_Test Company" + acc.insert() + + doc = frappe.get_doc({ + "doctype": "Ledger Merge", + "company": "_Test Company", + "root_type": frappe.db.get_value("Account", "Indirect Income - _TC", "root_type"), + "account": "Indirect Income - _TC", + "merge_accounts": [ + { + "account": "Indirect Test Income - _TC", + "account_name": "Indirect Test Income" + }, + { + "account": "Administrative Test Income - _TC", + "account_name": "Administrative Test Income" + } + ] + }).insert(ignore_permissions=True) + + parent = frappe.db.get_value("Account", "Administrative Test Income - _TC", "parent_account") + self.assertEqual(parent, "Indirect Test Income - _TC") + + start_merge(doc.name) + + parent = frappe.db.get_value("Account", "Administrative Test Income - _TC", "parent_account") + self.assertEqual(parent, "Indirect Income - _TC") + + self.assertFalse(frappe.db.exists("Account", "Indirect Test Income - _TC")) + self.assertTrue(frappe.db.exists("Account", "Administrative Test Income - _TC")) + + def tearDown(self): + for entry in frappe.db.get_all("Ledger Merge"): + frappe.delete_doc("Ledger Merge", entry.name) + + test_accounts = [ + "Indirect Test Expenses - _TC", + "Administrative Test Expenses - _TC", + "Indirect Test Income - _TC", + "Administrative Test Income - _TC" + ] + for account in test_accounts: + frappe.delete_doc_if_exists("Account", account) diff --git a/erpnext/agriculture/doctype/crop_cycle/__init__.py b/erpnext/accounts/doctype/ledger_merge_accounts/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/crop_cycle/__init__.py rename to erpnext/accounts/doctype/ledger_merge_accounts/__init__.py diff --git a/erpnext/accounts/doctype/ledger_merge_accounts/ledger_merge_accounts.json b/erpnext/accounts/doctype/ledger_merge_accounts/ledger_merge_accounts.json new file mode 100644 index 0000000000..4ce55ada7f --- /dev/null +++ b/erpnext/accounts/doctype/ledger_merge_accounts/ledger_merge_accounts.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-12-09 15:44:58.033398", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account", + "account_name", + "merged" + ], + "fields": [ + { + "columns": 4, + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "columns": 2, + "default": "0", + "fieldname": "merged", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Merged", + "read_only": 1 + }, + { + "columns": 4, + "fieldname": "account_name", + "fieldtype": "Data", + "label": "Account Name", + "read_only": 1, + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-12-10 15:27:24.477139", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Ledger Merge Accounts", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/ledger_merge_accounts/ledger_merge_accounts.py b/erpnext/accounts/doctype/ledger_merge_accounts/ledger_merge_accounts.py new file mode 100644 index 0000000000..30dfd65782 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_merge_accounts/ledger_merge_accounts.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LedgerMergeAccounts(Document): + pass diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json index bc9241802d..daee8f8c1a 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json @@ -75,7 +75,7 @@ ], "hide_toolbar": 1, "issingle": 1, - "modified": "2019-07-25 14:57:33.187689", + "modified": "2022-01-04 15:25:06.053187", "modified_by": "Administrator", "module": "Accounts", "name": "Opening Invoice Creation Tool", diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index ddb833ff3b..19d8d49dfe 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -135,7 +135,7 @@ class OpeningInvoiceCreationTool(Document): default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos") rate = flt(row.outstanding_amount) / flt(row.qty) - return frappe._dict({ + item_dict = frappe._dict({ "uom": default_uom, "rate": rate or 0.0, "qty": row.qty, @@ -146,6 +146,13 @@ class OpeningInvoiceCreationTool(Document): "cost_center": cost_center }) + for dimension in get_accounting_dimensions(): + item_dict.update({ + dimension: row.get(dimension) + }) + + return item_dict + item = get_item_dict() invoice = frappe._dict({ @@ -166,7 +173,7 @@ class OpeningInvoiceCreationTool(Document): accounting_dimension = get_accounting_dimensions() for dimension in accounting_dimension: invoice.update({ - dimension: item.get(dimension) + dimension: self.get(dimension) or item.get(dimension) }) return invoice diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index b5aae9845b..6700e9b975 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -7,21 +7,26 @@ import frappe from frappe.cache_manager import clear_doctype_cache from frappe.custom.doctype.property_setter.property_setter import make_property_setter +from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( + create_dimension, + disable_dimension, +) from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import ( get_temporary_opening_account, ) -test_dependencies = ["Customer", "Supplier"] +test_dependencies = ["Customer", "Supplier", "Accounting Dimension"] class TestOpeningInvoiceCreationTool(unittest.TestCase): def setUp(self): if not frappe.db.exists("Company", "_Test Opening Invoice Company"): make_company() + create_dimension() - def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None): + def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None, department=None): doc = frappe.get_single("Opening Invoice Creation Tool") args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company, - party_1=party_1, party_2=party_2, invoice_number=invoice_number) + party_1=party_1, party_2=party_2, invoice_number=invoice_number, department=department) doc.update(args) return doc.make_invoices() @@ -106,6 +111,19 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): doc = frappe.get_doc('Sales Invoice', inv) doc.cancel() + def test_opening_invoice_with_accounting_dimension(self): + invoices = self.make_invoices(invoice_type="Sales", company="_Test Opening Invoice Company", department='Sales - _TOIC') + + expected_value = { + "keys": ["customer", "outstanding_amount", "status", "department"], + 0: ["_Test Customer", 300, "Overdue", "Sales - _TOIC"], + 1: ["_Test Customer 1", 250, "Overdue", "Sales - _TOIC"], + } + self.check_expected_values(invoices, expected_value, invoice_type="Sales") + + def tearDown(self): + disable_dimension() + def get_opening_invoice_creation_dict(**args): party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" company = args.get("company", "_Test Company") diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py index e9f813c17c..031a9fa4db 100644 --- a/erpnext/accounts/doctype/party_link/party_link.py +++ b/erpnext/accounts/doctype/party_link/party_link.py @@ -2,7 +2,7 @@ # For license information, please see license.txt import frappe -from frappe import _ +from frappe import _, bold from frappe.model.document import Document @@ -12,6 +12,17 @@ class PartyLink(Document): frappe.throw(_("Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."), title=_("Invalid Primary Role")) + existing_party_link = frappe.get_all('Party Link', { + 'primary_party': self.primary_party, + 'secondary_party': self.secondary_party + }, pluck="primary_role") + if existing_party_link: + frappe.throw(_('{} {} is already linked with {} {}') + .format( + self.primary_role, bold(self.primary_party), + self.secondary_role, bold(self.secondary_party) + )) + existing_party_link = frappe.get_all('Party Link', { 'primary_party': self.secondary_party }, pluck="primary_role") diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c1b056b9c7..02a144d3e7 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -3,6 +3,7 @@ import json +from functools import reduce import frappe from frappe import ValidationError, _, scrub, throw @@ -1523,6 +1524,10 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.received_amount = received_amount pe.letter_head = doc.get("letter_head") + if dt in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice']: + pe.project = (doc.get('project') or + reduce(lambda prev,cur: prev or cur, [x.get('project') for x in doc.get('items')], None)) # get first non-empty project from items + if pe.party_type in ["Customer", "Supplier"]: bank_account = get_party_bank_account(pe.party_type, pe.party) pe.set("bank_account", bank_account) @@ -1708,7 +1713,10 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta def apply_early_payment_discount(paid_amount, received_amount, doc): total_discount = 0 - if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule: + eligible_for_payments = ['Sales Order', 'Sales Invoice', 'Purchase Order', 'Purchase Invoice'] + has_payment_schedule = hasattr(doc, 'payment_schedule') and doc.payment_schedule + + if doc.doctype in eligible_for_payments and has_payment_schedule: for term in doc.payment_schedule: if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date: if term.discount_type == 'Percentage': diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index fd801516e4..97d34e0a71 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -15,6 +15,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( update_multi_mode_option, ) from erpnext.accounts.party import get_due_date, get_party_account +from erpnext.stock.doctype.batch.batch import get_batch_qty, get_pos_reserved_batch_qty from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos @@ -123,9 +124,26 @@ class POSInvoice(SalesInvoice): frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.") .format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable")) elif invalid_serial_nos: - frappe.throw(_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.") + frappe.throw(_("Row #{}: Serial Nos. {} have already been transacted into another POS Invoice. Please select valid serial no.") .format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable")) + def validate_pos_reserved_batch_qty(self, item): + filters = {"item_code": item.item_code, "warehouse": item.warehouse, "batch_no":item.batch_no} + + available_batch_qty = get_batch_qty(item.batch_no, item.warehouse, item.item_code) + reserved_batch_qty = get_pos_reserved_batch_qty(filters) + + bold_item_name = frappe.bold(item.item_name) + bold_extra_batch_qty_needed = frappe.bold(abs(available_batch_qty - reserved_batch_qty - item.qty)) + bold_invalid_batch_no = frappe.bold(item.batch_no) + + if (available_batch_qty - reserved_batch_qty) == 0: + frappe.throw(_("Row #{}: Batch No. {} of item {} has no stock available. Please select valid batch no.") + .format(item.idx, bold_invalid_batch_no, bold_item_name), title=_("Item Unavailable")) + elif (available_batch_qty - reserved_batch_qty - item.qty) < 0: + frappe.throw(_("Row #{}: Batch No. {} of item {} has less than required stock available, {} more required") + .format(item.idx, bold_invalid_batch_no, bold_item_name, bold_extra_batch_qty_needed), title=_("Item Unavailable")) + def validate_delivered_serial_nos(self, item): serial_nos = get_serial_nos(item.serial_no) delivered_serial_nos = frappe.db.get_list('Serial No', { @@ -139,6 +157,20 @@ class POSInvoice(SalesInvoice): frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.") .format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable")) + def validate_invalid_serial_nos(self, item): + serial_nos = get_serial_nos(item.serial_no) + error_msg = [] + invalid_serials, msg = "", "" + for serial_no in serial_nos: + if not frappe.db.exists('Serial No', serial_no): + invalid_serials = invalid_serials + (", " if invalid_serials else "") + serial_no + msg = (_("Row #{}: Following Serial numbers for item {} are Invalid: {}").format(item.idx, frappe.bold(item.get("item_code")), frappe.bold(invalid_serials))) + if invalid_serials: + error_msg.append(msg) + + if error_msg: + frappe.throw(error_msg, title=_("Invalid Item"), as_list=True) + def validate_stock_availablility(self): if self.is_return or self.docstatus != 1: return @@ -150,6 +182,9 @@ class POSInvoice(SalesInvoice): if d.serial_no: self.validate_pos_reserved_serial_nos(d) self.validate_delivered_serial_nos(d) + self.validate_invalid_serial_nos(d) + elif d.batch_no: + self.validate_pos_reserved_batch_qty(d) else: if allow_negative_stock: return @@ -326,7 +361,6 @@ class POSInvoice(SalesInvoice): if not for_validate and not self.customer: self.customer = profile.customer - self.ignore_pricing_rule = profile.ignore_pricing_rule self.account_for_change_amount = profile.get('account_for_change_amount') or self.account_for_change_amount self.set_warehouse = profile.get('warehouse') or self.set_warehouse diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 6696333537..ba751c081b 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -354,6 +354,24 @@ class TestPOSInvoice(unittest.TestCase): pos2.insert() self.assertRaises(frappe.ValidationError, pos2.submit) + def test_invalid_serial_no_validation(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item + + se = make_serialized_item(company='_Test Company', + target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC') + serial_nos = se.get("items")[0].serial_no + 'wrong' + + pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', + account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', + expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', + item=se.get("items")[0].item_code, rate=1000, qty=2, do_not_save=1) + + pos.get('items')[0].has_serial_no = 1 + pos.get('items')[0].serial_no = serial_nos + pos.insert() + + self.assertRaises(frappe.ValidationError, pos.submit) + def test_loyalty_points(self): from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( get_loyalty_program_details_with_points, @@ -521,6 +539,72 @@ class TestPOSInvoice(unittest.TestCase): rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total") self.assertEqual(rounded_total, 400) + def test_pos_batch_item_qty_validation(self): + from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_batch_item_with_batch, + ) + create_batch_item_with_batch('_BATCH ITEM', 'TestBatch 01') + item = frappe.get_doc('Item', '_BATCH ITEM') + batch = frappe.get_doc('Batch', 'TestBatch 01') + batch.submit() + item.batch_no = 'TestBatch 01' + item.save() + + se = make_stock_entry(target="_Test Warehouse - _TC", item_code="_BATCH ITEM", qty=2, basic_rate=100, batch_no='TestBatch 01') + + pos_inv1 = create_pos_invoice(item=item.name, rate=300, qty=1, do_not_submit=1) + pos_inv1.items[0].batch_no = 'TestBatch 01' + pos_inv1.save() + pos_inv1.submit() + + pos_inv2 = create_pos_invoice(item=item.name, rate=300, qty=2, do_not_submit=1) + pos_inv2.items[0].batch_no = 'TestBatch 01' + pos_inv2.save() + + self.assertRaises(frappe.ValidationError, pos_inv2.submit) + + #teardown + pos_inv1.reload() + pos_inv1.cancel() + pos_inv1.delete() + pos_inv2.reload() + pos_inv2.delete() + se.cancel() + batch.reload() + batch.cancel() + batch.delete() + + def test_ignore_pricing_rule(self): + from erpnext.accounts.doctype.pricing_rule.test_pricing_rule import make_pricing_rule + + item_price = frappe.get_doc({ + 'doctype': 'Item Price', + 'item_code': '_Test Item', + 'price_list': '_Test Price List', + 'price_list_rate': '450', + }) + item_price.insert() + pr = make_pricing_rule(selling=1, priority=5, discount_percentage=10) + pr.save() + pos_inv = create_pos_invoice(qty=1, do_not_submit=1) + pos_inv.items[0].rate = 300 + pos_inv.save() + self.assertEquals(pos_inv.items[0].discount_percentage, 10) + # rate shouldn't change + self.assertEquals(pos_inv.items[0].rate, 405) + + pos_inv.ignore_pricing_rule = 1 + pos_inv.items[0].rate = 300 + pos_inv.save() + self.assertEquals(pos_inv.ignore_pricing_rule, 1) + # rate should change since pricing rules are ignored + self.assertEquals(pos_inv.items[0].rate, 300) + + item_price.delete() + pos_inv.delete() + pr.delete() + + def create_pos_invoice(**args): args = frappe._dict(args) pos_profile = None @@ -557,7 +641,8 @@ def create_pos_invoice(**args): "income_account": args.income_account or "Sales - _TC", "expense_account": args.expense_account or "Cost of Goods Sold - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC", - "serial_no": args.serial_no + "serial_no": args.serial_no, + "batch_no": args.batch_no }) if not args.do_not_save: @@ -570,3 +655,8 @@ def create_pos_invoice(**args): pos_inv.payment_schedule = [] return pos_inv + +def make_batch_item(item_name): + from erpnext.stock.doctype.item.test_item import make_item + if not frappe.db.exists(item_name): + return make_item(item_name, dict(has_batch_no = 1, create_new_batch = 1, is_stock_item=1)) \ No newline at end of file diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index d8b860671f..968137e7f8 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -166,7 +166,7 @@ class TestPricingRule(unittest.TestCase): "item_group": "Products", }, { - "item_group": "Seed", + "item_group": "_Test Item Group", }, ], "selling": 1, @@ -628,6 +628,26 @@ class TestPricingRule(unittest.TestCase): for doc in [si, si1]: doc.delete() + def test_multiple_pricing_rules_with_min_qty(self): + make_pricing_rule(discount_percentage=20, selling=1, priority=1, min_qty=4, + apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 1") + make_pricing_rule(discount_percentage=10, selling=1, priority=2, min_qty=4, + apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 2") + + si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD") + item = si.items[0] + item.stock_qty = 1 + si.save() + self.assertFalse(item.discount_percentage) + item.qty = 5 + item.stock_qty = 5 + si.save() + self.assertEqual(item.discount_percentage, 30) + si.delete() + + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1") + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2") + test_dependencies = ["Campaign"] def make_pricing_rule(**args): @@ -650,7 +670,7 @@ def make_pricing_rule(**args): "rate": args.rate or 0.0, "margin_rate_or_amount": args.margin_rate_or_amount or 0.0, "condition": args.condition or '', - "priority": 1, + "priority": args.priority or 1, "discount_amount": args.discount_amount or 0.0, "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0 }) @@ -676,6 +696,8 @@ def make_pricing_rule(**args): if args.get(applicable_for): doc.db_set(applicable_for, args.get(applicable_for)) + return doc + def setup_pricing_rule_data(): if not frappe.db.exists('Campaign', '_Test Campaign'): frappe.get_doc({ diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 02bfc9defd..7792590c9c 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -73,7 +73,7 @@ def sorted_by_priority(pricing_rules, args, doc=None): for key in sorted(pricing_rule_dict): pricing_rules_list.extend(pricing_rule_dict.get(key)) - return pricing_rules_list or pricing_rules + return pricing_rules_list def filter_pricing_rule_based_on_condition(pricing_rules, doc=None): filtered_pricing_rules = [] diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index df957d261b..b3642181ac 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -505,11 +505,11 @@ class PurchaseInvoice(BuyingController): # Checked both rounding_adjustment and rounded_total # because rounded_total had value even before introcution of posting GLE based on rounded total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total + base_grand_total = flt(self.base_rounded_total if (self.base_rounding_adjustment and self.base_rounded_total) + else self.base_grand_total, self.precision("base_grand_total")) if grand_total and not self.is_internal_transfer(): # Did not use base_grand_total to book rounding loss gle - grand_total_in_company_currency = flt(grand_total * self.conversion_rate, - self.precision("grand_total")) gl_entries.append( self.get_gl_dict({ "account": self.credit_to, @@ -517,8 +517,8 @@ class PurchaseInvoice(BuyingController): "party": self.supplier, "due_date": self.due_date, "against": self.against_expense_account, - "credit": grand_total_in_company_currency, - "credit_in_account_currency": grand_total_in_company_currency \ + "credit": base_grand_total, + "credit_in_account_currency": base_grand_total \ if self.party_account_currency==self.company_currency else grand_total, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index cb18dd3b17..21846bb76c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1236,7 +1236,7 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): def update_tax_witholding_category(company, account): from erpnext.accounts.utils import get_fiscal_year - fiscal_year = get_fiscal_year(fiscal_year='2021') + fiscal_year = get_fiscal_year(date=nowdate()) if not frappe.db.get_value('Tax Withholding Rate', {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]), diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js index 6336db16eb..f54bce8aac 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js @@ -1,6 +1,8 @@ {% include "erpnext/regional/india/taxes.js" %} +{% include "erpnext/regional/india/e_invoice/einvoice.js" %} erpnext.setup_auto_gst_taxation('Sales Invoice'); +erpnext.setup_einvoice_actions('Sales Invoice') frappe.ui.form.on("Sales Invoice", { setup: function(frm) { diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js index d9d6634c39..f01325d80b 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js @@ -36,4 +36,139 @@ frappe.listview_settings['Sales Invoice'].onload = function (list_view) { }; list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false); + + const generate_irns = () => { + const docnames = list_view.get_checked_items(true); + if (docnames && docnames.length) { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices', + args: { docnames }, + freeze: true, + freeze_message: __('Generating E-Invoices...') + }); + } else { + frappe.msgprint({ + message: __('Please select at least one sales invoice to generate IRN'), + title: __('No Invoice Selected'), + indicator: 'red' + }); + } + }; + + const cancel_irns = () => { + const docnames = list_view.get_checked_items(true); + + const fields = [ + { + "label": "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + + const d = new frappe.ui.Dialog({ + title: __("Cancel IRN"), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_irns', + args: { + doctype: list_view.doctype, + docnames, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + freeze_message: __('Cancelling E-Invoices...'), + }); + d.hide(); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + + let einvoicing_enabled = false; + frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => { + einvoicing_enabled = enabled; + }); + + list_view.$result.on("change", "input[type=checkbox]", () => { + if (einvoicing_enabled) { + const docnames = list_view.get_checked_items(true); + // show/hide e-invoicing actions when no sales invoices are checked + if (docnames && docnames.length) { + // prevent adding actions twice if e-invoicing action group already exists + if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) { + list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing')); + list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing')); + } + } else { + list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing')); + list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing')); + } + } + }); + + frappe.realtime.on("bulk_einvoice_generation_complete", (data) => { + const { failures, user, invoices } = data; + + if (invoices.length != failures.length) { + frappe.msgprint({ + message: __('{0} e-invoices generated successfully', [invoices.length]), + title: __('Bulk E-Invoice Generation Complete'), + indicator: 'orange' + }); + } + + if (failures && failures.length && user == frappe.session.user) { + let message = ` + Failed to generate IRNs for following ${failures.length} sales invoices: + + `; + frappe.msgprint({ + message: message, + title: __('Bulk E-Invoice Generation Complete'), + indicator: 'orange' + }); + } + }); + + frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => { + const { failures, user, invoices } = data; + + if (invoices.length != failures.length) { + frappe.msgprint({ + message: __('{0} e-invoices cancelled successfully', [invoices.length]), + title: __('Bulk E-Invoice Cancellation Complete'), + indicator: 'orange' + }); + } + + if (failures && failures.length && user == frappe.session.user) { + let message = ` + Failed to cancel IRNs for following ${failures.length} sales invoices: + + `; + frappe.msgprint({ + message: message, + title: __('Bulk E-Invoice Cancellation Complete'), + indicator: 'orange' + }); + } + }); }; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 39dfd8d76d..af6a52a642 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -469,7 +469,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e let row = frappe.get_doc(d.doctype, d.name) set_timesheet_detail_rate(row.doctype, row.name, me.frm.doc.currency, row.timesheet_detail) }); - frm.trigger("calculate_timesheet_totals"); + this.frm.trigger("calculate_timesheet_totals"); } } }; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 545abf77e6..5062c1c807 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -651,7 +651,7 @@ "hide_seconds": 1, "label": "Ignore Pricing Rule", "no_copy": 1, - "permlevel": 1, + "permlevel": 0, "print_hide": 1 }, { @@ -2038,7 +2038,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-10-21 20:19:38.667508", + "modified": "2021-12-23 20:19:38.667508", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 321b45323f..bc443581e4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -43,6 +43,7 @@ from erpnext.setup.doctype.company.company import update_company_current_month_s from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos +from erpnext.stock.utils import calculate_mapped_packed_items_return form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -293,6 +294,8 @@ class SalesInvoice(SellingController): def before_cancel(self): self.check_if_consolidated_invoice() + + super(SalesInvoice, self).before_cancel() self.update_time_sheet(None) def on_cancel(self): @@ -728,8 +731,11 @@ class SalesInvoice(SellingController): def update_packing_list(self): if cint(self.update_stock) == 1: - from erpnext.stock.doctype.packed_item.packed_item import make_packing_list - make_packing_list(self) + if cint(self.is_return) and self.return_against: + calculate_mapped_packed_items_return(self) + else: + from erpnext.stock.doctype.packed_item.packed_item import make_packing_list + make_packing_list(self) else: self.set('packed_items', []) @@ -862,11 +868,11 @@ class SalesInvoice(SellingController): # Checked both rounding_adjustment and rounded_total # because rounded_total had value even before introcution of posting GLE based on rounded total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total + base_grand_total = flt(self.base_rounded_total if (self.base_rounding_adjustment and self.base_rounded_total) + else self.base_grand_total, self.precision("base_grand_total")) + if grand_total and not self.is_internal_transfer(): # Didnot use base_grand_total to book rounding loss gle - grand_total_in_company_currency = flt(grand_total * self.conversion_rate, - self.precision("grand_total")) - gl_entries.append( self.get_gl_dict({ "account": self.debit_to, @@ -874,8 +880,8 @@ class SalesInvoice(SellingController): "party": self.customer, "due_date": self.due_date, "against": self.against_income_account, - "debit": grand_total_in_company_currency, - "debit_in_account_currency": grand_total_in_company_currency \ + "debit": base_grand_total, + "debit_in_account_currency": base_grand_total \ if self.party_account_currency==self.company_currency else grand_total, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6a488ea96e..941061f2a2 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -20,6 +20,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_comp from erpnext.accounts.utils import PaymentEntryUnlinkError from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data +from erpnext.controllers.accounts_controller import update_invoice_status from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.regional.india.utils import get_ewb_data @@ -1780,47 +1781,6 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, "2019-01-30") - def test_deferred_revenue_post_account_freeze_upto_by_admin(self): - frappe.set_user("Administrator") - - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) - frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) - - deferred_account = create_account(account_name="Deferred Revenue", - parent_account="Current Liabilities - _TC", company="_Test Company") - - item = create_item("_Test Item for Deferred Accounting") - item.enable_deferred_revenue = 1 - item.deferred_revenue_account = deferred_account - item.no_of_months = 12 - item.save() - - si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True) - si.items[0].enable_deferred_revenue = 1 - si.items[0].service_start_date = "2019-01-10" - si.items[0].service_end_date = "2019-03-15" - si.items[0].deferred_revenue_account = deferred_account - si.save() - si.submit() - - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31')) - frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager') - - pda1 = frappe.get_doc(dict( - doctype='Process Deferred Accounting', - posting_date=nowdate(), - start_date="2019-01-01", - end_date="2019-03-31", - type="Income", - company="_Test Company" - )) - - pda1.insert() - self.assertRaises(frappe.ValidationError, pda1.submit) - - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) - frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) - def test_fixed_deferred_revenue(self): deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") @@ -2140,6 +2100,54 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(data['billLists'][0]['actualFromStateCode'],7) self.assertEqual(data['billLists'][0]['fromStateCode'],27) + def test_einvoice_submission_without_irn(self): + # init + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 1 + einvoice_settings.applicable_from = nowdate() + einvoice_settings.append('credentials', { + 'company': '_Test Company', + 'gstin': '27AAECE4835E1ZR', + 'username': 'test', + 'password': 'test' + }) + einvoice_settings.save() + + country = frappe.flags.country + frappe.flags.country = 'India' + + si = make_sales_invoice_for_ewaybill() + self.assertRaises(frappe.ValidationError, si.submit) + + si.irn = 'test_irn' + si.submit() + + # reset + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 0 + frappe.flags.country = country + + def test_einvoice_json(self): + from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals + + si = get_sales_invoice_for_e_invoice() + si.discount_amount = 100 + si.save() + + einvoice = make_einvoice(si) + self.assertTrue(einvoice['EwbDtls']) + validate_totals(einvoice) + + si.apply_discount_on = 'Net Total' + si.save() + einvoice = make_einvoice(si) + validate_totals(einvoice) + + [d.set('included_in_print_rate', 1) for d in si.taxes] + si.save() + einvoice = make_einvoice(si) + validate_totals(einvoice) + def test_item_tax_net_range(self): item = create_item("T Shirt") @@ -2232,9 +2240,9 @@ class TestSalesInvoice(unittest.TestCase): asset.load_from_db() expected_values = [ - ["2020-06-30", 1311.48, 1311.48], - ["2021-06-30", 20000.0, 21311.48], - ["2021-09-30", 5041.1, 26352.58] + ["2020-06-30", 1366.12, 1366.12], + ["2021-06-30", 20000.0, 21366.12], + ["2021-09-30", 5041.1, 26407.22] ] for i, schedule in enumerate(asset.schedules): @@ -2282,12 +2290,12 @@ class TestSalesInvoice(unittest.TestCase): asset.load_from_db() expected_values = [ - ["2020-06-30", 1311.48, 1311.48, True], - ["2021-06-30", 20000.0, 21311.48, True], - ["2022-06-30", 20000.0, 41311.48, False], - ["2023-06-30", 20000.0, 61311.48, False], - ["2024-06-30", 20000.0, 81311.48, False], - ["2025-06-06", 18688.52, 100000.0, False] + ["2020-06-30", 1366.12, 1366.12, True], + ["2021-06-30", 20000.0, 21366.12, True], + ["2022-06-30", 20000.0, 41366.12, False], + ["2023-06-30", 20000.0, 61366.12, False], + ["2024-06-30", 20000.0, 81366.12, False], + ["2025-06-06", 18633.88, 100000.0, False] ] for i, schedule in enumerate(asset.schedules): @@ -2385,6 +2393,41 @@ class TestSalesInvoice(unittest.TestCase): si.reload() self.assertEqual(si.status, "Paid") + def test_update_invoice_status(self): + today = nowdate() + + # Sales Invoice without Payment Schedule + si = create_sales_invoice(posting_date=add_days(today, -5)) + + # Sales Invoice with Payment Schedule + si_with_payment_schedule = create_sales_invoice(do_not_submit=True) + si_with_payment_schedule.extend("payment_schedule", [ + { + "due_date": add_days(today, -5), + "invoice_portion": 50, + "payment_amount": si_with_payment_schedule.grand_total / 2 + }, + { + "due_date": add_days(today, 5), + "invoice_portion": 50, + "payment_amount": si_with_payment_schedule.grand_total / 2 + } + ]) + si_with_payment_schedule.submit() + + + for invoice in (si, si_with_payment_schedule): + invoice.db_set("status", "Unpaid") + update_invoice_status() + invoice.reload() + self.assertEqual(invoice.status, "Overdue") + + invoice.db_set("status", "Unpaid and Discounted") + update_invoice_status() + invoice.reload() + self.assertEqual(invoice.status, "Overdue and Discounted") + + def test_sales_commission(self): si = frappe.copy_doc(test_records[0]) item = copy.deepcopy(si.get('items')[0]) @@ -2446,6 +2489,74 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance) + def test_multi_currency_deferred_revenue_via_journal_entry(self): + deferred_account = create_account(account_name="Deferred Revenue", + parent_account="Current Liabilities - _TC", company="_Test Company") + + acc_settings = frappe.get_single('Accounts Settings') + acc_settings.book_deferred_entries_via_journal_entry = 1 + acc_settings.submit_journal_entries = 1 + acc_settings.save() + + item = create_item("_Test Item for Deferred Accounting") + item.enable_deferred_expense = 1 + item.deferred_revenue_account = deferred_account + item.save() + + si = create_sales_invoice(customer='_Test Customer USD', currency='USD', + item=item.name, qty=1, rate=100, conversion_rate=60, do_not_save=True) + + si.set_posting_time = 1 + si.posting_date = '2019-01-01' + si.debit_to = '_Test Receivable USD - _TC' + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-01" + si.items[0].service_end_date = "2019-03-30" + si.items[0].deferred_expense_account = deferred_account + si.save() + si.submit() + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31')) + + pda1 = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=nowdate(), + start_date="2019-01-01", + end_date="2019-03-31", + type="Income", + company="_Test Company" + )) + + pda1.insert() + pda1.submit() + + expected_gle = [ + ["Sales - _TC", 0.0, 2089.89, "2019-01-28"], + [deferred_account, 2089.89, 0.0, "2019-01-28"], + ["Sales - _TC", 0.0, 1887.64, "2019-02-28"], + [deferred_account, 1887.64, 0.0, "2019-02-28"], + ["Sales - _TC", 0.0, 2022.47, "2019-03-15"], + [deferred_account, 2022.47, 0.0, "2019-03-15"] + ] + + gl_entries = gl_entries = frappe.db.sql("""select account, debit, credit, posting_date + from `tabGL Entry` + where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s + order by posting_date asc, account asc""", (si.items[0].name, si.posting_date), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.credit) + self.assertEqual(expected_gle[i][2], gle.debit) + self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) + + acc_settings = frappe.get_single('Accounts Settings') + acc_settings.book_deferred_entries_via_journal_entry = 0 + acc_settings.submit_journal_entriessubmit_journal_entries = 0 + acc_settings.save() + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index 7e5129911e..792e7d21a7 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -71,7 +71,8 @@ class ShippingRule(Document): if doc.currency != doc.company_currency: shipping_amount = flt(shipping_amount / doc.conversion_rate, 2) - self.add_shipping_rule_to_tax_table(doc, shipping_amount) + if shipping_amount: + self.add_shipping_rule_to_tax_table(doc, shipping_amount) def get_shipping_amount_from_rules(self, value): for condition in self.get("conditions"): diff --git a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json index d2c505c630..e032bb307b 100644 --- a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json +++ b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json @@ -28,14 +28,14 @@ { "columns": 2, "fieldname": "single_threshold", - "fieldtype": "Currency", + "fieldtype": "Float", "in_list_view": 1, "label": "Single Transaction Threshold" }, { "columns": 3, "fieldname": "cumulative_threshold", - "fieldtype": "Currency", + "fieldtype": "Float", "in_list_view": 1, "label": "Cumulative Transaction Threshold" }, @@ -59,7 +59,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-08-31 11:42:12.213977", + "modified": "2022-01-13 12:04:42.904263", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withholding Rate", @@ -68,5 +68,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json index 7de9ae1539..02e30c3835 100644 --- a/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json +++ b/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json @@ -2,15 +2,17 @@ "creation": "2021-08-24 12:28:18.044902", "docstatus": 0, "doctype": "Form Tour", + "first_document": 0, "idx": 0, + "include_name_field": 0, "is_standard": 1, - "modified": "2021-08-24 12:28:18.044902", + "modified": "2022-01-18 18:32:17.102330", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges Template", "owner": "Administrator", "reference_doctype": "Sales Taxes and Charges Template", - "save_on_complete": 0, + "save_on_complete": 1, "steps": [ { "description": "A name by which you will identify this template. You can change this later.", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 1836db6477..55bc9673c1 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -2,6 +2,8 @@ # License: GNU General Public License v3. See license.txt +import copy + import frappe from frappe import _ from frappe.model.meta import get_field_precision @@ -51,49 +53,57 @@ def validate_accounting_period(gl_map): .format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod) def process_gl_map(gl_map, merge_entries=True, precision=None): + if not gl_map: + return [] + + gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision) + if merge_entries: gl_map = merge_similar_entries(gl_map, precision) - for entry in gl_map: - # toggle debit, credit if negative entry - if flt(entry.debit) < 0: - entry.credit = flt(entry.credit) - flt(entry.debit) - entry.debit = 0.0 - if flt(entry.debit_in_account_currency) < 0: - entry.credit_in_account_currency = \ - flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency) - entry.debit_in_account_currency = 0.0 - - if flt(entry.credit) < 0: - entry.debit = flt(entry.debit) - flt(entry.credit) - entry.credit = 0.0 - - if flt(entry.credit_in_account_currency) < 0: - entry.debit_in_account_currency = \ - flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency) - entry.credit_in_account_currency = 0.0 - - update_net_values(entry) + gl_map = toggle_debit_credit_if_negative(gl_map) return gl_map -def update_net_values(entry): - # In some scenarios net value needs to be shown in the ledger - # This method updates net values as debit or credit - if entry.post_net_value and entry.debit and entry.credit: - if entry.debit > entry.credit: - entry.debit = entry.debit - entry.credit - entry.debit_in_account_currency = entry.debit_in_account_currency \ - - entry.credit_in_account_currency - entry.credit = 0 - entry.credit_in_account_currency = 0 - else: - entry.credit = entry.credit - entry.debit - entry.credit_in_account_currency = entry.credit_in_account_currency \ - - entry.debit_in_account_currency +def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None): + cost_center_allocation = get_cost_center_allocation_data(gl_map[0]["company"], gl_map[0]["posting_date"]) + if not cost_center_allocation: + return gl_map - entry.debit = 0 - entry.debit_in_account_currency = 0 + new_gl_map = [] + for d in gl_map: + cost_center = d.get("cost_center") + if cost_center and cost_center_allocation.get(cost_center): + for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items(): + gle = copy.deepcopy(d) + gle.cost_center = sub_cost_center + for field in ("debit", "credit", "debit_in_account_currency", "credit_in_company_currency"): + gle[field] = flt(flt(d.get(field)) * percentage / 100, precision) + new_gl_map.append(gle) + else: + new_gl_map.append(d) + + return new_gl_map + +def get_cost_center_allocation_data(company, posting_date): + par = frappe.qb.DocType("Cost Center Allocation") + child = frappe.qb.DocType("Cost Center Allocation Percentage") + + records = ( + frappe.qb.from_(par).inner_join(child).on(par.name == child.parent) + .select(par.main_cost_center, child.cost_center, child.percentage) + .where(par.docstatus == 1) + .where(par.company == company) + .where(par.valid_from <= posting_date) + .orderby(par.valid_from, order=frappe.qb.desc) + ).run(as_dict=True) + + cc_allocation = frappe._dict() + for d in records: + cc_allocation.setdefault(d.main_cost_center, frappe._dict())\ + .setdefault(d.cost_center, d.percentage) + + return cc_allocation def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] @@ -145,6 +155,49 @@ def check_if_in_list(gle, gl_map, dimensions=None): if same_head: return e +def toggle_debit_credit_if_negative(gl_map): + for entry in gl_map: + # toggle debit, credit if negative entry + if flt(entry.debit) < 0: + entry.credit = flt(entry.credit) - flt(entry.debit) + entry.debit = 0.0 + + if flt(entry.debit_in_account_currency) < 0: + entry.credit_in_account_currency = \ + flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency) + entry.debit_in_account_currency = 0.0 + + if flt(entry.credit) < 0: + entry.debit = flt(entry.debit) - flt(entry.credit) + entry.credit = 0.0 + + if flt(entry.credit_in_account_currency) < 0: + entry.debit_in_account_currency = \ + flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency) + entry.credit_in_account_currency = 0.0 + + update_net_values(entry) + + return gl_map + +def update_net_values(entry): + # In some scenarios net value needs to be shown in the ledger + # This method updates net values as debit or credit + if entry.post_net_value and entry.debit and entry.credit: + if entry.debit > entry.credit: + entry.debit = entry.debit - entry.credit + entry.debit_in_account_currency = entry.debit_in_account_currency \ + - entry.credit_in_account_currency + entry.credit = 0 + entry.credit_in_account_currency = 0 + else: + entry.credit = entry.credit - entry.debit + entry.credit_in_account_currency = entry.credit_in_account_currency \ + - entry.debit_in_account_currency + + entry.debit = 0 + entry.debit_in_account_currency = 0 + def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): if not from_repost: validate_cwip_accounts(gl_map) diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json index 2e0ab4305d..aa7cdf788b 100644 --- a/erpnext/accounts/module_onboarding/accounts/accounts.json +++ b/erpnext/accounts/module_onboarding/accounts/accounts.json @@ -13,15 +13,12 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts", "idx": 0, "is_complete": 0, - "modified": "2021-08-13 11:59:35.690443", + "modified": "2022-01-18 18:35:52.326688", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts", "owner": "Administrator", "steps": [ - { - "step": "Company" - }, { "step": "Chart of Accounts" }, diff --git a/erpnext/accounts/onboarding_step/company/company.json b/erpnext/accounts/onboarding_step/company/company.json deleted file mode 100644 index 4992e4d018..0000000000 --- a/erpnext/accounts/onboarding_step/company/company.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "action": "Go to Page", - "action_label": "Let's Review your Company", - "creation": "2021-06-29 14:47:42.497318", - "description": "# Company\n\nIn ERPNext, you can also create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company. \n", - "docstatus": 0, - "doctype": "Onboarding Step", - "idx": 0, - "is_complete": 0, - "is_single": 0, - "is_skipped": 0, - "modified": "2021-08-13 11:43:35.767341", - "modified_by": "Administrator", - "name": "Company", - "owner": "Administrator", - "path": "app/company", - "reference_document": "Company", - "show_form_tour": 0, - "show_full_form": 0, - "title": "Review Company", - "validate_action": 1 -} \ No newline at end of file diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 6b4b43d30b..c13bc23c15 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -58,7 +58,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError) party = frappe.get_doc(party_type, party) - currency = party.default_currency if party.get("default_currency") else get_company_currency(company) + currency = party.get("default_currency") or currency or get_company_currency(company) party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address) set_contact_details(party_details, party, party_type) diff --git a/erpnext/agriculture/doctype/detected_disease/__init__.py b/erpnext/accounts/print_format/gst_e_invoice/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/detected_disease/__init__.py rename to erpnext/accounts/print_format/gst_e_invoice/__init__.py diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html new file mode 100644 index 0000000000..e658049309 --- /dev/null +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -0,0 +1,173 @@ +{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%} +{%- set einvoice = json.loads(doc.signed_einvoice) -%} + +
+
+ {% if letter_head and not no_letterhead %} +
{{ letter_head }}
+ {% endif %} + +
+ {% if print_settings.repeat_header_footer %} + + {% endif %} +
1. Transaction Details
+
+
+
+
+
{{ einvoice.Irn }}
+
+
+
+
{{ einvoice.AckNo }}
+
+
+
+
{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}
+
+
+
+
{{ einvoice.TranDtls.SupTyp }}
+
+
+
+
{{ einvoice.DocDtls.Typ }}
+
+
+
+
{{ einvoice.DocDtls.No }}
+
+
+
+ +
+
+
2. Party Details
+
+ {%- set seller = einvoice.SellerDtls -%} +
+
Seller
+

{{ seller.Gstin }}

+

{{ seller.LglNm }}

+

{{ seller.Addr1 }}

+ {%- if seller.Addr2 -%}

{{ seller.Addr2 }}

{% endif %} +

{{ seller.Loc }}

+

{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}

+ + {%- if einvoice.ShipDtls -%} + {%- set shipping = einvoice.ShipDtls -%} +
Shipped From
+

{{ shipping.Gstin }}

+

{{ shipping.LglNm }}

+

{{ shipping.Addr1 }}

+ {%- if shipping.Addr2 -%}

{{ shipping.Addr2 }}

{% endif %} +

{{ shipping.Loc }}

+

{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}

+ {% endif %} +
+ {%- set buyer = einvoice.BuyerDtls -%} +
+
Buyer
+

{{ buyer.Gstin }}

+

{{ buyer.LglNm }}

+

{{ buyer.Addr1 }}

+ {%- if buyer.Addr2 -%}

{{ buyer.Addr2 }}

{% endif %} +

{{ buyer.Loc }}

+

{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}

+ + {%- if einvoice.DispDtls -%} + {%- set dispatch = einvoice.DispDtls -%} +
Dispatched From
+ {%- if dispatch.Gstin -%}

{{ dispatch.Gstin }}

{% endif %} +

{{ dispatch.LglNm }}

+

{{ dispatch.Addr1 }}

+ {%- if dispatch.Addr2 -%}

{{ dispatch.Addr2 }}

{% endif %} +

{{ dispatch.Loc }}

+

{{ frappe.db.get_value("Address", doc.dispatch_address_name, "gst_state") }} - {{ dispatch.Pin }}

+ {% endif %} +
+
+
+
3. Item Details
+ + + + + + + + + + + + + + + + + + {% for item in einvoice.ItemList %} + + + + + + + + + + + + + + {% endfor %} + +
Sr. No.ItemHSN CodeQtyUOMRateDiscountTaxable AmountTax RateOther ChargesTotal
{{ item.SlNo }}{{ item.PrdDesc }}{{ item.HsnCd }}{{ item.Qty }}{{ item.Unit }}{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}{{ item.GstRt + item.CesRt }} %{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}
+
+
+
4. Value Details
+ + + + + + + + + + + + + + + + + {%- set value_details = einvoice.ValDtls -%} + + + + + + + + + + + + + +
Taxable AmountCGSTSGSTIGSTCESSState CESSDiscountOther ChargesRound OffTotal Value
{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}
+
+
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json new file mode 100644 index 0000000000..1001199a09 --- /dev/null +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json @@ -0,0 +1,24 @@ +{ + "align_labels_right": 1, + "creation": "2020-10-10 18:01:21.032914", + "custom_format": 0, + "default_print_language": "en-US", + "disabled": 1, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "", + "idx": 0, + "line_breaks": 1, + "modified": "2020-10-23 19:54:40.634936", + "modified_by": "Administrator", + "module": "Accounts", + "name": "GST E-Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 1, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index 305cddb102..715cd6476e 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -117,6 +117,11 @@ frappe.query_reports["Accounts Receivable Summary"] = { "label": __("Show Future Payments"), "fieldtype": "Check", }, + { + "fieldname":"show_gl_balance", + "label": __("Show GL Balance"), + "fieldtype": "Check", + }, ], onload: function(report) { diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 3c94629203..8e3bd8be1b 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -4,7 +4,7 @@ import frappe from frappe import _, scrub -from frappe.utils import cint +from frappe.utils import cint, flt from erpnext.accounts.party import get_partywise_advanced_payment_amount from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport @@ -36,6 +36,9 @@ class AccountsReceivableSummary(ReceivablePayableReport): party_advance_amount = get_partywise_advanced_payment_amount(self.party_type, self.filters.report_date, self.filters.show_future_payments, self.filters.company) or {} + if self.filters.show_gl_balance: + gl_balance_map = get_gl_balance(self.filters.report_date) + for party, party_dict in self.party_total.items(): if party_dict.outstanding == 0: continue @@ -55,6 +58,10 @@ class AccountsReceivableSummary(ReceivablePayableReport): # but in summary report advance shown in separate column row.paid -= row.advance + if self.filters.show_gl_balance: + row.gl_balance = gl_balance_map.get(party) + row.diff = flt(row.outstanding) - flt(row.gl_balance) + self.data.append(row) def get_party_total(self, args): @@ -114,6 +121,10 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.add_column(_(credit_debit_label), fieldname='credit_note') self.add_column(_('Outstanding Amount'), fieldname='outstanding') + if self.filters.show_gl_balance: + self.add_column(_('GL Balance'), fieldname='gl_balance') + self.add_column(_('Difference'), fieldname='diff') + self.setup_ageing_columns() if self.party_type == "Customer": @@ -140,3 +151,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): # Add column for total due amount self.add_column(label="Total Amount Due", fieldname='total_due') + +def get_gl_balance(report_date): + return frappe._dict(frappe.db.get_all("GL Entry", fields=['party', 'sum(debit - credit)'], + filters={'posting_date': ("<=", report_date), 'is_cancelled': 0}, group_by='party', as_list=1)) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 3bb590a564..56ee5008cf 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -29,18 +29,6 @@ def execute(filters=None): dimension_items = cam_map.get(dimension) if dimension_items: data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, 0) - else: - DCC_allocation = frappe.db.sql('''SELECT parent, sum(percentage_allocation) as percentage_allocation - FROM `tabDistributed Cost Center` - WHERE cost_center IN %(dimension)s - AND parent NOT IN %(dimension)s - GROUP BY parent''',{'dimension':[dimension]}) - if DCC_allocation: - filters['budget_against_filter'] = [DCC_allocation[0][0]] - ddc_cam_map = get_dimension_account_month_map(filters) - dimension_items = ddc_cam_map.get(DCC_allocation[0][0]) - if dimension_items: - data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1]) chart = get_chart_data(filters, columns, data) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index a4842c1844..3a51db8a97 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -121,20 +121,21 @@ class Deferred_Item(object): """ simulate future posting by creating dummy gl entries. starts from the last posting date. """ - if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date: - self.estimate_for_period_list = get_period_list( - self.filters.from_fiscal_year, - self.filters.to_fiscal_year, - add_days(self.last_entry_date, 1), - self.period_list[-1].to_date, - "Date Range", - "Monthly", - company=self.filters.company, - ) - for period in self.estimate_for_period_list: - amount = self.calculate_amount(period.from_date, period.to_date) - gle = self.make_dummy_gle(period.key, period.to_date, amount) - self.gle_entries.append(gle) + if self.service_start_date != self.service_end_date: + if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date: + self.estimate_for_period_list = get_period_list( + self.filters.from_fiscal_year, + self.filters.to_fiscal_year, + add_days(self.last_entry_date, 1), + self.period_list[-1].to_date, + "Date Range", + "Monthly", + company=self.filters.company, + ) + for period in self.estimate_for_period_list: + amount = self.calculate_amount(period.from_date, period.to_date) + gle = self.make_dummy_gle(period.key, period.to_date, amount) + self.gle_entries.append(gle) def calculate_item_revenue_expense_for_period(self): """ diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py index 1de6fb6824..86eb2134fe 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py @@ -17,10 +17,42 @@ from erpnext.stock.doctype.item.test_item import create_item class TestDeferredRevenueAndExpense(unittest.TestCase): @classmethod def setUpClass(self): - clear_old_entries() + clear_accounts_and_items() create_company() + self.maxDiff = None + + def clear_old_entries(self): + sinv = qb.DocType("Sales Invoice") + sinv_item = qb.DocType("Sales Invoice Item") + pinv = qb.DocType("Purchase Invoice") + pinv_item = qb.DocType("Purchase Invoice Item") + + # delete existing invoices with deferred items + deferred_invoices = ( + qb.from_(sinv) + .join(sinv_item) + .on(sinv.name == sinv_item.parent) + .select(sinv.name) + .where(sinv_item.enable_deferred_revenue == 1) + .run() + ) + if deferred_invoices: + qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run() + + deferred_invoices = ( + qb.from_(pinv) + .join(pinv_item) + .on(pinv.name == pinv_item.parent) + .select(pinv.name) + .where(pinv_item.enable_deferred_expense == 1) + .run() + ) + if deferred_invoices: + qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run() def test_deferred_revenue(self): + self.clear_old_entries() + # created deferred expense accounts, if not found deferred_revenue_account = create_account( account_name="Deferred Revenue", @@ -108,6 +140,8 @@ class TestDeferredRevenueAndExpense(unittest.TestCase): self.assertEqual(report.period_total, expected) def test_deferred_expense(self): + self.clear_old_entries() + # created deferred expense accounts, if not found deferred_expense_account = create_account( account_name="Deferred Expense", @@ -198,6 +232,91 @@ class TestDeferredRevenueAndExpense(unittest.TestCase): ] self.assertEqual(report.period_total, expected) + def test_zero_months(self): + self.clear_old_entries() + # created deferred expense accounts, if not found + deferred_revenue_account = create_account( + account_name="Deferred Revenue", + parent_account="Current Liabilities - _CD", + company="_Test Company DR", + ) + + acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") + acc_settings.book_deferred_entries_based_on = "Months" + acc_settings.save() + + customer = frappe.new_doc("Customer") + customer.customer_name = "_Test Customer DR" + customer.type = "Individual" + customer.insert() + + item = create_item( + "_Test Internet Subscription", + is_stock_item=0, + warehouse="All Warehouses - _CD", + company="_Test Company DR", + ) + item.enable_deferred_revenue = 1 + item.deferred_revenue_account = deferred_revenue_account + item.no_of_months = 0 + item.save() + + si = create_sales_invoice( + item=item.name, + company="_Test Company DR", + customer="_Test Customer DR", + debit_to="Debtors - _CD", + posting_date="2021-05-01", + parent_cost_center="Main - _CD", + cost_center="Main - _CD", + do_not_submit=True, + rate=300, + price_list_rate=300, + ) + si.items[0].enable_deferred_revenue = 1 + si.items[0].deferred_revenue_account = deferred_revenue_account + si.items[0].income_account = "Sales - _CD" + si.save() + si.submit() + + pda = frappe.get_doc( + dict( + doctype="Process Deferred Accounting", + posting_date=nowdate(), + start_date="2021-05-01", + end_date="2021-08-01", + type="Income", + company="_Test Company DR", + ) + ) + pda.insert() + pda.submit() + + # execute report + fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year")) + self.filters = frappe._dict( + { + "company": frappe.defaults.get_user_default("Company"), + "filter_based_on": "Date Range", + "period_start_date": "2021-05-01", + "period_end_date": "2021-08-01", + "from_fiscal_year": fiscal_year.year, + "to_fiscal_year": fiscal_year.year, + "periodicity": "Monthly", + "type": "Revenue", + "with_upcoming_postings": False, + } + ) + + report = Deferred_Revenue_and_Expense_Report(filters=self.filters) + report.run() + expected = [ + {"key": "may_2021", "total": 300.0, "actual": 300.0}, + {"key": "jun_2021", "total": 0, "actual": 0}, + {"key": "jul_2021", "total": 0, "actual": 0}, + {"key": "aug_2021", "total": 0, "actual": 0}, + ] + self.assertEqual(report.period_total, expected) def create_company(): company = frappe.db.exists("Company", "_Test Company DR") @@ -209,15 +328,11 @@ def create_company(): company.insert() -def clear_old_entries(): +def clear_accounts_and_items(): item = qb.DocType("Item") account = qb.DocType("Account") customer = qb.DocType("Customer") supplier = qb.DocType("Supplier") - sinv = qb.DocType("Sales Invoice") - sinv_item = qb.DocType("Sales Invoice Item") - pinv = qb.DocType("Purchase Invoice") - pinv_item = qb.DocType("Purchase Invoice Item") qb.from_(account).delete().where( (account.account_name == "Deferred Revenue") @@ -228,26 +343,3 @@ def clear_old_entries(): ).run() qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run() qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run() - - # delete existing invoices with deferred items - deferred_invoices = ( - qb.from_(sinv) - .join(sinv_item) - .on(sinv.name == sinv_item.parent) - .select(sinv.name) - .where(sinv_item.enable_deferred_revenue == 1) - .run() - ) - if deferred_invoices: - qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run() - - deferred_invoices = ( - qb.from_(pinv) - .join(pinv_item) - .on(pinv.name == pinv_item.parent) - .select(pinv.name) - .where(pinv_item.enable_deferred_expense == 1) - .run() - ) - if deferred_invoices: - qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run() diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 1e89b650c7..03ae0aea13 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -387,42 +387,15 @@ def set_gl_entries_by_account( key: value }) - distributed_cost_center_query = "" - if filters and filters.get('cost_center'): - distributed_cost_center_query = """ - UNION ALL - SELECT posting_date, - account, - debit*(DCC_allocation.percentage_allocation/100) as debit, - credit*(DCC_allocation.percentage_allocation/100) as credit, - is_opening, - fiscal_year, - debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency, - credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency, - account_currency - FROM `tabGL Entry`, - ( - SELECT parent, sum(percentage_allocation) as percentage_allocation - FROM `tabDistributed Cost Center` - WHERE cost_center IN %(cost_center)s - AND parent NOT IN %(cost_center)s - GROUP BY parent - ) as DCC_allocation - WHERE company=%(company)s - {additional_conditions} - AND posting_date <= %(to_date)s - AND is_cancelled = 0 - AND cost_center = DCC_allocation.parent - """.format(additional_conditions=additional_conditions.replace("and cost_center in %(cost_center)s ", '')) - - gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry` + gl_entries = frappe.db.sql(""" + select posting_date, account, debit, credit, is_opening, fiscal_year, + debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry` where company=%(company)s {additional_conditions} and posting_date <= %(to_date)s - and is_cancelled = 0 - {distributed_cost_center_query}""".format( - additional_conditions=additional_conditions, - distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec + and is_cancelled = 0""".format( + additional_conditions=additional_conditions), gl_filters, as_dict=True + ) if filters and filters.get('presentation_currency'): convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company')) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index b2968761c6..010284c2ea 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -167,7 +167,7 @@ frappe.query_reports["General Ledger"] = { "fieldname": "include_dimensions", "label": __("Consider Accounting Dimensions"), "fieldtype": "Check", - "default": 0 + "default": 1 }, { "fieldname": "show_opening_entries", diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 385c8b2b6e..4ff0297dba 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -176,44 +176,7 @@ def get_gl_entries(filters, accounting_dimensions): if accounting_dimensions: dimension_fields = ', '.join(accounting_dimensions) + ',' - distributed_cost_center_query = "" - if filters and filters.get('cost_center'): - select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, - credit*(DCC_allocation.percentage_allocation/100) as credit, - debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency, - credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """ - - distributed_cost_center_query = """ - UNION ALL - SELECT name as gl_entry, - posting_date, - account, - party_type, - party, - voucher_type, - voucher_no, {dimension_fields} - cost_center, project, - against_voucher_type, - against_voucher, - account_currency, - remarks, against, - is_opening, `tabGL Entry`.creation {select_fields_with_percentage} - FROM `tabGL Entry`, - ( - SELECT parent, sum(percentage_allocation) as percentage_allocation - FROM `tabDistributed Cost Center` - WHERE cost_center IN %(cost_center)s - AND parent NOT IN %(cost_center)s - GROUP BY parent - ) as DCC_allocation - WHERE company=%(company)s - {conditions} - AND posting_date <= %(to_date)s - AND cost_center = DCC_allocation.parent - """.format(dimension_fields=dimension_fields,select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", '')) - - gl_entries = frappe.db.sql( - """ + gl_entries = frappe.db.sql(""" select name as gl_entry, posting_date, account, party_type, party, voucher_type, voucher_no, {dimension_fields} @@ -222,13 +185,11 @@ def get_gl_entries(filters, accounting_dimensions): remarks, against, is_opening, creation {select_fields} from `tabGL Entry` where company=%(company)s {conditions} - {distributed_cost_center_query} {order_by_statement} - """.format( - dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query, - order_by_statement=order_by_statement - ), - filters, as_dict=1) + """.format( + dimension_fields=dimension_fields, select_fields=select_fields, + conditions=get_conditions(filters), order_by_statement=order_by_statement + ), filters, as_dict=1) if filters.get('presentation_currency'): return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company')) @@ -448,9 +409,11 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): elif group_by_voucher_consolidated: keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")] - for dim in accounting_dimensions: - keylist.append(gle.get(dim)) - keylist.append(gle.get("cost_center")) + if filters.get("include_dimensions"): + for dim in accounting_dimensions: + keylist.append(gle.get(dim)) + keylist.append(gle.get("cost_center")) + key = tuple(keylist) if key not in consolidated_gle: consolidated_gle.setdefault(key, gle) @@ -547,10 +510,7 @@ def get_columns(filters): "fieldname": "balance", "fieldtype": "Float", "width": 130 - } - ] - - columns.extend([ + }, { "label": _("Voucher Type"), "fieldname": "voucher_type", @@ -584,7 +544,7 @@ def get_columns(filters): "fieldname": "project", "width": 100 } - ]) + ] if filters.get("include_dimensions"): for dim in get_accounting_dimensions(as_list = False): @@ -594,14 +554,14 @@ def get_columns(filters): "fieldname": dim.fieldname, "width": 100 }) - - columns.extend([ - { + columns.append({ "label": _("Cost Center"), "options": "Cost Center", "fieldname": "cost_center", "width": 100 - }, + }) + + columns.extend([ { "label": _("Against Voucher Type"), "fieldname": "against_voucher_type", diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index 3dcb86267c..f4b8731ba8 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -109,7 +109,6 @@ def accumulate_values_into_parents(accounts, accounts_by_name): def prepare_data(accounts, filters, total_row, parent_children_map, based_on): data = [] - new_accounts = accounts company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency") for d in accounts: @@ -123,19 +122,6 @@ def prepare_data(accounts, filters, total_row, parent_children_map, based_on): "currency": company_currency, "based_on": based_on } - if based_on == 'cost_center': - cost_center_doc = frappe.get_doc("Cost Center",d.name) - if not cost_center_doc.enable_distributed_cost_center: - DCC_allocation = frappe.db.sql("""SELECT parent, sum(percentage_allocation) as percentage_allocation - FROM `tabDistributed Cost Center` - WHERE cost_center IN %(cost_center)s - AND parent NOT IN %(cost_center)s - GROUP BY parent""",{'cost_center': [d.name]}) - if DCC_allocation: - for account in new_accounts: - if account['name'] == DCC_allocation[0][0]: - for value in value_fields: - d[value] += account[value]*(DCC_allocation[0][1]/100) for key in value_fields: row[key] = flt(d.get(key, 0.0), 3) diff --git a/erpnext/accounts/test/test_reports.py b/erpnext/accounts/test/test_reports.py new file mode 100644 index 0000000000..78c109ab94 --- /dev/null +++ b/erpnext/accounts/test/test_reports.py @@ -0,0 +1,48 @@ +import unittest +from typing import List, Tuple + +from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report + +DEFAULT_FILTERS = { + "company": "_Test Company", + "from_date": "2010-01-01", + "to_date": "2030-01-01", + "period_start_date": "2010-01-01", + "period_end_date": "2030-01-01" +} + + +REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ + ("General Ledger", {"group_by": "Group by Voucher (Consolidated)"} ), + ("General Ledger", {"group_by": "Group by Voucher (Consolidated)", "include_dimensions": 1} ), + ("Accounts Payable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}), + ("Accounts Receivable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}), + ("Consolidated Financial Statement", {"report": "Balance Sheet"} ), + ("Consolidated Financial Statement", {"report": "Profit and Loss Statement"} ), + ("Consolidated Financial Statement", {"report": "Cash Flow"} ), + ("Gross Profit", {"group_by": "Invoice"}), + ("Gross Profit", {"group_by": "Item Code"}), + ("Gross Profit", {"group_by": "Item Group"}), + ("Gross Profit", {"group_by": "Customer"}), + ("Gross Profit", {"group_by": "Customer Group"}), + ("Item-wise Sales Register", {}), + ("Item-wise Purchase Register", {}), + ("Sales Register", {}), + ("Purchase Register", {}), + ("Tax Detail", {"mode": "run", "report_name": "Tax Detail"},), +] + +OPTIONAL_FILTERS = {} + + +class TestReports(unittest.TestCase): + def test_execute_all_accounts_reports(self): + """Test that all script report in stock modules are executable with supported filters""" + for report, filter in REPORT_FILTER_TEST_CASES: + execute_script_report( + report_name=report, + module="Accounts", + filters=filter, + default_filters=DEFAULT_FILTERS, + optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, + ) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 33d1748825..a456c7fb57 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -5,7 +5,7 @@ "label": "Profit and Loss" } ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Accounts\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Profit and Loss\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chart of Accounts\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Journal Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payment Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Trial Balance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounting Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Payable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Financial Statements\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Multi Currency\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bank Statement\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Subscription Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goods and Services Tax (GST India)\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Share Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Cost Center and Budgeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening and Closing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Profitability\", \"col\": 4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Goods and Services Tax (GST India)\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}}]", "creation": "2020-03-02 15:41:59.515192", "docstatus": 0, "doctype": "Workspace", @@ -230,6 +230,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payment Reconciliation", + "link_count": 0, "link_to": "Payment Reconciliation", "link_type": "DocType", "onboard": 0, @@ -346,6 +347,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payment Reconciliation", + "link_count": 0, "link_to": "Payment Reconciliation", "link_type": "DocType", "onboard": 0, @@ -527,16 +529,17 @@ "type": "Link" }, { - "dependencies": "GL Entry", - "hidden": 0, - "is_query_report": 1, - "label": "KSA VAT Report", - "link_to": "KSA VAT", - "link_type": "Report", - "onboard": 0, - "only_for": "Saudi Arabia", - "type": "Link" - }, + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "KSA VAT Report", + "link_count": 0, + "link_to": "KSA VAT", + "link_type": "Report", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1020,6 +1023,17 @@ "onboard": 0, "type": "Link" }, + { + "dependencies": "Cost Center", + "hidden": 0, + "is_query_report": 0, + "label": "Cost Center Allocation", + "link_count": 0, + "link_to": "Cost Center Allocation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "Cost Center", "hidden": 0, @@ -1158,15 +1172,16 @@ "type": "Link" }, { - "hidden": 0, - "is_query_report": 0, - "label": "KSA VAT Setting", - "link_to": "KSA VAT Setting", - "link_type": "DocType", - "onboard": 0, - "only_for": "Saudi Arabia", - "type": "Link" - }, + "hidden": 0, + "is_query_report": 0, + "label": "KSA VAT Setting", + "link_count": 0, + "link_to": "KSA VAT Setting", + "link_type": "DocType", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1220,7 +1235,7 @@ "type": "Link" } ], - "modified": "2021-08-27 12:15:52.872471", + "modified": "2022-01-13 17:25:09.835345", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", @@ -1229,7 +1244,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 2, + "sequence_id": 2.0, "shortcuts": [ { "label": "Chart of Accounts", @@ -1278,4 +1293,4 @@ } ], "title": "Accounting" -} +} \ No newline at end of file diff --git a/erpnext/agriculture/doctype/agriculture_analysis_criteria/agriculture_analysis_criteria.js b/erpnext/agriculture/doctype/agriculture_analysis_criteria/agriculture_analysis_criteria.js deleted file mode 100644 index e236cc6675..0000000000 --- a/erpnext/agriculture/doctype/agriculture_analysis_criteria/agriculture_analysis_criteria.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Agriculture Analysis Criteria', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/agriculture/doctype/agriculture_analysis_criteria/agriculture_analysis_criteria.json b/erpnext/agriculture/doctype/agriculture_analysis_criteria/agriculture_analysis_criteria.json deleted file mode 100644 index bb5e4d9108..0000000000 --- a/erpnext/agriculture/doctype/agriculture_analysis_criteria/agriculture_analysis_criteria.json +++ /dev/null @@ -1,182 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:title", - "beta": 0, - "creation": "2017-12-05 16:37:46.599982", - "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": "title", - "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": "Title", - "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": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "standard", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Standard", - "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": "linked_doctype", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Linked Doctype", - "length": 0, - "no_copy": 0, - "options": "\nWater Analysis\nSoil Analysis\nPlant Analysis\nFertilizer\nSoil Texture\nWeather", - "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": "2018-11-04 03:27:36.678832", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Agriculture Analysis Criteria", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/agriculture/doctype/agriculture_analysis_criteria/agriculture_analysis_criteria.py b/erpnext/agriculture/doctype/agriculture_analysis_criteria/agriculture_analysis_criteria.py deleted file mode 100644 index 19459927a5..0000000000 --- a/erpnext/agriculture/doctype/agriculture_analysis_criteria/agriculture_analysis_criteria.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class AgricultureAnalysisCriteria(Document): - pass diff --git a/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.py b/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.py deleted file mode 100644 index 91e6f3fa0c..0000000000 --- a/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestAgricultureAnalysisCriteria(unittest.TestCase): - pass diff --git a/erpnext/agriculture/doctype/agriculture_task/agriculture_task.js b/erpnext/agriculture/doctype/agriculture_task/agriculture_task.js deleted file mode 100644 index 4d6b9597a2..0000000000 --- a/erpnext/agriculture/doctype/agriculture_task/agriculture_task.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Agriculture Task', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/agriculture/doctype/agriculture_task/agriculture_task.json b/erpnext/agriculture/doctype/agriculture_task/agriculture_task.json deleted file mode 100644 index d943d77167..0000000000 --- a/erpnext/agriculture/doctype/agriculture_task/agriculture_task.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "AG-TASK-.#####", - "beta": 0, - "creation": "2017-10-26 15:51:19.602452", - "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": "task_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Task Name", - "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, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "start_day", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Start Day", - "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, - "default": "", - "fieldname": "end_day", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "End Day", - "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, - "default": "Ignore holidays", - "fieldname": "holiday_management", - "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": 0, - "label": "Holiday Management", - "length": 0, - "no_copy": 0, - "options": "Ignore holidays\nPrevious Business Day\nNext Business Day", - "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, - "default": "Low", - "fieldname": "priority", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Priority", - "length": 0, - "no_copy": 0, - "options": "Low\nMedium\nHigh\nUrgent", - "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": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:28:08.679157", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Agriculture Task", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/agriculture_task/agriculture_task.py b/erpnext/agriculture/doctype/agriculture_task/agriculture_task.py deleted file mode 100644 index dab2998935..0000000000 --- a/erpnext/agriculture/doctype/agriculture_task/agriculture_task.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class AgricultureTask(Document): - pass diff --git a/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.py b/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.py deleted file mode 100644 index 94d7915d62..0000000000 --- a/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestAgricultureTask(unittest.TestCase): - pass diff --git a/erpnext/agriculture/doctype/crop/crop.js b/erpnext/agriculture/doctype/crop/crop.js deleted file mode 100644 index 550824636b..0000000000 --- a/erpnext/agriculture/doctype/crop/crop.js +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.provide("erpnext.crop"); - -frappe.ui.form.on('Crop', { - refresh: (frm) => { - frm.fields_dict.materials_required.grid.set_column_disp('bom_no', false); - } -}); - -frappe.ui.form.on("BOM Item", { - item_code: (frm, cdt, cdn) => { - erpnext.crop.update_item_rate_uom(frm, cdt, cdn); - }, - qty: (frm, cdt, cdn) => { - erpnext.crop.update_item_qty_amount(frm, cdt, cdn); - }, - rate: (frm, cdt, cdn) => { - erpnext.crop.update_item_qty_amount(frm, cdt, cdn); - } -}); - -erpnext.crop.update_item_rate_uom = function(frm, cdt, cdn) { - let material_list = ['materials_required', 'produce', 'byproducts']; - material_list.forEach((material) => { - frm.doc[material].forEach((item, index) => { - if (item.name == cdn && item.item_code){ - frappe.call({ - method:'erpnext.agriculture.doctype.crop.crop.get_item_details', - args: { - item_code: item.item_code - }, - callback: (r) => { - frappe.model.set_value('BOM Item', item.name, 'uom', r.message.uom); - frappe.model.set_value('BOM Item', item.name, 'rate', r.message.rate); - } - }); - } - }); - }); -}; - -erpnext.crop.update_item_qty_amount = function(frm, cdt, cdn) { - let material_list = ['materials_required', 'produce', 'byproducts']; - material_list.forEach((material) => { - frm.doc[material].forEach((item, index) => { - if (item.name == cdn){ - if (!frappe.model.get_value('BOM Item', item.name, 'qty')) - frappe.model.set_value('BOM Item', item.name, 'qty', 1); - frappe.model.set_value('BOM Item', item.name, 'amount', item.qty * item.rate); - } - }); - }); -}; diff --git a/erpnext/agriculture/doctype/crop/crop.json b/erpnext/agriculture/doctype/crop/crop.json deleted file mode 100644 index e357abb98b..0000000000 --- a/erpnext/agriculture/doctype/crop/crop.json +++ /dev/null @@ -1,1110 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:title", - "beta": 0, - "creation": "2017-10-20 01:16:17.606174", - "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": "title", - "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": "Title", - "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": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "crop_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Crop Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "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_4", - "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": "scientific_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Scientific Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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, - "description": "You can define all the tasks which need to carried out for this crop here. The day field is used to mention the day on which the task needs to be carried out, 1 being the 1st day, etc.. ", - "fieldname": "section_break_20", - "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": "Tasks", - "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": "agriculture_task", - "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": "Agriculture Task", - "length": 0, - "no_copy": 0, - "options": "Agriculture 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": 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, - "default": "0", - "fieldname": "period", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Period", - "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": "section_break_9", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "crop_spacing", - "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": "Crop Spacing", - "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": "crop_spacing_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Crop Spacing UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "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_12", - "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": "row_spacing", - "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": "Row Spacing", - "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": "row_spacing_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Row Spacing UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "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": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Type", - "length": 0, - "no_copy": 0, - "options": "Annual\nPerennial\nBiennial", - "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_6", - "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": "category", - "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": "Category", - "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": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "target_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Target Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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": "section_break_12", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "planting_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Planting UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "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": "planting_area", - "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": "Planting Area", - "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_14", - "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": "yield_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Yield UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "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": "section_break_16", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_17", - "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": "Materials Required", - "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": "materials_required", - "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": "Materials Required", - "length": 0, - "no_copy": 0, - "options": "BOM Item", - "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": "section_break_19", - "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": "Produced 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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "produce", - "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": "Produce", - "length": 0, - "no_copy": 0, - "options": "BOM Item", - "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": "section_break_18", - "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": "Byproducts", - "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": "byproducts", - "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": "Byproducts", - "length": 0, - "no_copy": 0, - "options": "BOM Item", - "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": "2018-11-04 03:27:10.651075", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Crop", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/crop/crop.py b/erpnext/agriculture/doctype/crop/crop.py deleted file mode 100644 index ed2073cebf..0000000000 --- a/erpnext/agriculture/doctype/crop/crop.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe import _ -from frappe.model.document import Document - - -class Crop(Document): - def validate(self): - self.validate_crop_tasks() - - def validate_crop_tasks(self): - for task in self.agriculture_task: - if task.start_day > task.end_day: - frappe.throw(_("Start day is greater than end day in task '{0}'").format(task.task_name)) - - # Verify that the crop period is correct - max_crop_period = max([task.end_day for task in self.agriculture_task]) - self.period = max(self.period, max_crop_period) - - # Sort the crop tasks based on start days, - # maintaining the order for same-day tasks - self.agriculture_task.sort(key=lambda task: task.start_day) - - -@frappe.whitelist() -def get_item_details(item_code): - item = frappe.get_doc('Item', item_code) - return {"uom": item.stock_uom, "rate": item.valuation_rate} diff --git a/erpnext/agriculture/doctype/crop/crop_dashboard.py b/erpnext/agriculture/doctype/crop/crop_dashboard.py deleted file mode 100644 index 37cdbb2df5..0000000000 --- a/erpnext/agriculture/doctype/crop/crop_dashboard.py +++ /dev/null @@ -1,12 +0,0 @@ -from frappe import _ - - -def get_data(): - return { - 'transactions': [ - { - 'label': _('Crop Cycle'), - 'items': ['Crop Cycle'] - } - ] - } diff --git a/erpnext/agriculture/doctype/crop/test_crop.py b/erpnext/agriculture/doctype/crop/test_crop.py deleted file mode 100644 index c79a367219..0000000000 --- a/erpnext/agriculture/doctype/crop/test_crop.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe - -test_dependencies = ["Fertilizer"] - -class TestCrop(unittest.TestCase): - def test_crop_period(self): - basil = frappe.get_doc('Crop', 'Basil from seed') - self.assertEqual(basil.period, 15) diff --git a/erpnext/agriculture/doctype/crop/test_records.json b/erpnext/agriculture/doctype/crop/test_records.json deleted file mode 100644 index 41ddb9af22..0000000000 --- a/erpnext/agriculture/doctype/crop/test_records.json +++ /dev/null @@ -1,80 +0,0 @@ -[ - { - "doctype": "Item", - "item_code": "Basil Seeds", - "item_name": "Basil Seeds", - "item_group": "Seed" - }, - { - "doctype": "Item", - "item_code": "Twigs", - "item_name": "Twigs", - "item_group": "By-product" - }, - { - "doctype": "Item", - "item_code": "Basil Leaves", - "item_name": "Basil Leaves", - "item_group": "Produce" - }, - { - "doctype": "Crop", - "title": "Basil from seed", - "crop_name": "Basil", - "scientific_name": "Ocimum basilicum", - "materials_required": [{ - "item_code": "Basil Seeds", - "qty": "25", - "uom": "Nos", - "rate": "1" - }, { - "item_code": "Urea", - "qty": "5", - "uom": "Kg", - "rate": "10" - }], - "byproducts": [{ - "item_code": "Twigs", - "qty": "25", - "uom": "Nos", - "rate": "1" - }], - "produce": [{ - "item_code": "Basil Leaves", - "qty": "100", - "uom": "Nos", - "rate": "1" - }], - "agriculture_task": [{ - "task_name": "Plough the field", - "start_day": 1, - "end_day": 1, - "holiday_management": "Ignore holidays" - }, { - "task_name": "Plant the seeds", - "start_day": 2, - "end_day": 3, - "holiday_management": "Ignore holidays" - }, { - "task_name": "Water the field", - "start_day": 4, - "end_day": 4, - "holiday_management": "Ignore holidays" - }, { - "task_name": "First harvest", - "start_day": 8, - "end_day": 8, - "holiday_management": "Ignore holidays" - }, { - "task_name": "Add the fertilizer", - "start_day": 10, - "end_day": 12, - "holiday_management": "Ignore holidays" - }, { - "task_name": "Final cut", - "start_day": 15, - "end_day": 15, - "holiday_management": "Ignore holidays" - }] - } -] \ No newline at end of file diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.js b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.js deleted file mode 100644 index 94392e7261..0000000000 --- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.js +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Crop Cycle', { - refresh: (frm) => { - if (!frm.doc.__islocal) - frm.add_custom_button(__('Reload Linked Analysis'), () => frm.call("reload_linked_analysis")); - - frappe.realtime.on("List of Linked Docs", (output) => { - let analysis_doctypes = ['Soil Texture', 'Plant Analysis', 'Soil Analysis']; - let analysis_doctypes_docs = ['soil_texture', 'plant_analysis', 'soil_analysis']; - let obj_to_append = {soil_analysis: [], soil_texture: [], plant_analysis: []}; - output['Location'].forEach( (land_doc) => { - analysis_doctypes.forEach( (doctype) => { - output[doctype].forEach( (analysis_doc) => { - let point_to_be_tested = JSON.parse(analysis_doc.location).features[0].geometry.coordinates; - let poly_of_land = JSON.parse(land_doc.location).features[0].geometry.coordinates[0]; - if (is_in_land_unit(point_to_be_tested, poly_of_land)){ - obj_to_append[analysis_doctypes_docs[analysis_doctypes.indexOf(doctype)]].push(analysis_doc.name); - } - }); - }); - }); - frm.call('append_to_child', { - obj_to_append: obj_to_append - }); - }); - } -}); - -function is_in_land_unit(point, vs) { - // ray-casting algorithm based on - // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html - - var x = point[0], y = point[1]; - - var inside = false; - for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) { - var xi = vs[i][0], yi = vs[i][1]; - var xj = vs[j][0], yj = vs[j][1]; - - var intersect = ((yi > y) != (yj > y)) - && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - if (intersect) inside = !inside; - } - - return inside; -}; diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.json b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.json deleted file mode 100644 index a076718919..0000000000 --- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.json +++ /dev/null @@ -1,904 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:title", - "beta": 0, - "creation": "2017-11-02 03:09:35.449880", - "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": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Title", - "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": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "crop", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Crop", - "length": 0, - "no_copy": 0, - "options": "Crop", - "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, - "description": "", - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Linked Location", - "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, - "description": "A link to all the Locations in which the Crop is growing", - "fieldname": "linked_location", - "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": "Linked Location", - "length": 0, - "no_copy": 0, - "options": "Linked Location", - "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": "section_break_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!doc.__islocal", - "fieldname": "project", - "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": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "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_12", - "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, - "description": "This will be day 1 of the crop cycle", - "fieldname": "start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Start Date", - "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, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "project.expected_end_date", - "fieldname": "end_date", - "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": "End Date", - "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": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "iso_8601_standard", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "ISO 8601 standard", - "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_5", - "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": "cycle_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Cycle Type", - "length": 0, - "no_copy": 0, - "options": "Yearly\nLess than a year", - "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": "section_break_12", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "The minimum length between each plant in the field for optimum growth", - "fetch_from": "crop.crop_spacing", - "fieldname": "crop_spacing", - "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": "Crop Spacing", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "crop_spacing_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Crop Spacing UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "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_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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "The minimum distance between rows of plants for optimum growth", - "fetch_from": "crop.row_spacing", - "fieldname": "row_spacing", - "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": "Row Spacing", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "row_spacing_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Row Spacing UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "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, - "depends_on": "eval:!doc.__islocal", - "description": "List of diseases detected on the field. When selected it'll automatically add a list of tasks to deal with the disease ", - "fieldname": "section_break_14", - "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": "Detected Diseases", - "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": "detected_disease", - "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": "Detected Disease", - "length": 0, - "no_copy": 0, - "options": "Detected Disease", - "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": 1, - "collapsible_depends_on": "eval:false", - "columns": 0, - "fieldname": "section_break_22", - "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": "LInked Analysis", - "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": "soil_texture", - "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": "Soil Texture", - "length": 0, - "no_copy": 0, - "options": "Linked Soil Texture", - "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": "soil_analysis", - "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": "Soil Analysis", - "length": 0, - "no_copy": 0, - "options": "Linked Soil Analysis", - "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": "plant_analysis", - "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": "Plant Analysis", - "length": 0, - "no_copy": 0, - "options": "Linked Plant Analysis", - "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": "2018-11-04 03:31:47.602312", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Crop Cycle", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/crop_cycle/crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py deleted file mode 100644 index 43c5bbde82..0000000000 --- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import ast - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.utils import add_days - - -class CropCycle(Document): - def validate(self): - self.set_missing_values() - - def after_insert(self): - self.create_crop_cycle_project() - self.create_tasks_for_diseases() - - def on_update(self): - self.create_tasks_for_diseases() - - def set_missing_values(self): - crop = frappe.get_doc('Crop', self.crop) - - if not self.crop_spacing_uom: - self.crop_spacing_uom = crop.crop_spacing_uom - - if not self.row_spacing_uom: - self.row_spacing_uom = crop.row_spacing_uom - - def create_crop_cycle_project(self): - crop = frappe.get_doc('Crop', self.crop) - - self.project = self.create_project(crop.period, crop.agriculture_task) - self.create_task(crop.agriculture_task, self.project, self.start_date) - - def create_tasks_for_diseases(self): - for disease in self.detected_disease: - if not disease.tasks_created: - self.import_disease_tasks(disease.disease, disease.start_date) - disease.tasks_created = True - - frappe.msgprint(_("Tasks have been created for managing the {0} disease (on row {1})").format(disease.disease, disease.idx)) - - def import_disease_tasks(self, disease, start_date): - disease_doc = frappe.get_doc('Disease', disease) - self.create_task(disease_doc.treatment_task, self.project, start_date) - - def create_project(self, period, crop_tasks): - project = frappe.get_doc({ - "doctype": "Project", - "project_name": self.title, - "expected_start_date": self.start_date, - "expected_end_date": add_days(self.start_date, period - 1) - }).insert() - - return project.name - - def create_task(self, crop_tasks, project_name, start_date): - for crop_task in crop_tasks: - frappe.get_doc({ - "doctype": "Task", - "subject": crop_task.get("task_name"), - "priority": crop_task.get("priority"), - "project": project_name, - "exp_start_date": add_days(start_date, crop_task.get("start_day") - 1), - "exp_end_date": add_days(start_date, crop_task.get("end_day") - 1) - }).insert() - - @frappe.whitelist() - def reload_linked_analysis(self): - linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis'] - required_fields = ['location', 'name', 'collection_datetime'] - output = {} - - for doctype in linked_doctypes: - output[doctype] = frappe.get_all(doctype, fields=required_fields) - - output['Location'] = [] - - for location in self.linked_location: - output['Location'].append(frappe.get_doc('Location', location.location)) - - frappe.publish_realtime("List of Linked Docs", - output, user=frappe.session.user) - - @frappe.whitelist() - def append_to_child(self, obj_to_append): - for doctype in obj_to_append: - for doc_name in set(obj_to_append[doctype]): - self.append(doctype, {doctype: doc_name}) - - self.save() - - -def get_coordinates(doc): - return ast.literal_eval(doc.location).get('features')[0].get('geometry').get('coordinates') - - -def get_geometry_type(doc): - return ast.literal_eval(doc.location).get('features')[0].get('geometry').get('type') - - -def is_in_location(point, vs): - x, y = point - inside = False - - j = len(vs) - 1 - i = 0 - - while i < len(vs): - xi, yi = vs[i] - xj, yj = vs[j] - - intersect = ((yi > y) != (yj > y)) and ( - x < (xj - xi) * (y - yi) / (yj - yi) + xi) - - if intersect: - inside = not inside - - i = j - j += 1 - - return inside diff --git a/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.py deleted file mode 100644 index e4765a57c0..0000000000 --- a/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe -from frappe.utils import datetime - -test_dependencies = ["Crop", "Fertilizer", "Location", "Disease"] - - -class TestCropCycle(unittest.TestCase): - def test_crop_cycle_creation(self): - cycle = frappe.get_doc('Crop Cycle', 'Basil from seed 2017') - self.assertTrue(frappe.db.exists('Crop Cycle', 'Basil from seed 2017')) - - # check if the tasks were created - self.assertEqual(check_task_creation(), True) - self.assertEqual(check_project_creation(), True) - - -def check_task_creation(): - all_task_dict = { - "Survey and find the aphid locations": { - "exp_start_date": datetime.date(2017, 11, 21), - "exp_end_date": datetime.date(2017, 11, 22) - }, - "Apply Pesticides": { - "exp_start_date": datetime.date(2017, 11, 23), - "exp_end_date": datetime.date(2017, 11, 23) - }, - "Plough the field": { - "exp_start_date": datetime.date(2017, 11, 11), - "exp_end_date": datetime.date(2017, 11, 11) - }, - "Plant the seeds": { - "exp_start_date": datetime.date(2017, 11, 12), - "exp_end_date": datetime.date(2017, 11, 13) - }, - "Water the field": { - "exp_start_date": datetime.date(2017, 11, 14), - "exp_end_date": datetime.date(2017, 11, 14) - }, - "First harvest": { - "exp_start_date": datetime.date(2017, 11, 18), - "exp_end_date": datetime.date(2017, 11, 18) - }, - "Add the fertilizer": { - "exp_start_date": datetime.date(2017, 11, 20), - "exp_end_date": datetime.date(2017, 11, 22) - }, - "Final cut": { - "exp_start_date": datetime.date(2017, 11, 25), - "exp_end_date": datetime.date(2017, 11, 25) - } - } - - all_tasks = frappe.get_all('Task') - - for task in all_tasks: - sample_task = frappe.get_doc('Task', task.name) - - if sample_task.subject in list(all_task_dict): - if sample_task.exp_start_date != all_task_dict[sample_task.subject]['exp_start_date'] or sample_task.exp_end_date != all_task_dict[sample_task.subject]['exp_end_date']: - return False - all_task_dict.pop(sample_task.subject) - - return True if not all_task_dict else False - - -def check_project_creation(): - return True if frappe.db.exists('Project', {'project_name': 'Basil from seed 2017'}) else False diff --git a/erpnext/agriculture/doctype/crop_cycle/test_records.json b/erpnext/agriculture/doctype/crop_cycle/test_records.json deleted file mode 100644 index 5c79f1030a..0000000000 --- a/erpnext/agriculture/doctype/crop_cycle/test_records.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "doctype": "Crop Cycle", - "title": "Basil from seed 2017", - "linked_location": [{ - "location": "Basil Farm" - }], - "crop": "Basil from seed", - "start_date": "2017-11-11", - "detected_disease": [{ - "disease": "Aphids", - "start_date": "2017-11-21" - }] - } -] \ No newline at end of file diff --git a/erpnext/agriculture/doctype/detected_disease/detected_disease.json b/erpnext/agriculture/doctype/detected_disease/detected_disease.json deleted file mode 100644 index f670cd31b8..0000000000 --- a/erpnext/agriculture/doctype/detected_disease/detected_disease.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-11-20 17:31:30.772779", - "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": "disease", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Disease", - "length": 0, - "no_copy": 0, - "options": "Disease", - "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": "start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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": "tasks_created", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tasks Created", - "length": 0, - "no_copy": 1, - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:27:47.463994", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Detected Disease", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/detected_disease/detected_disease.py b/erpnext/agriculture/doctype/detected_disease/detected_disease.py deleted file mode 100644 index e507add7f9..0000000000 --- a/erpnext/agriculture/doctype/detected_disease/detected_disease.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class DetectedDisease(Document): - pass diff --git a/erpnext/agriculture/doctype/disease/disease.js b/erpnext/agriculture/doctype/disease/disease.js deleted file mode 100644 index f6b678c1a9..0000000000 --- a/erpnext/agriculture/doctype/disease/disease.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Disease', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/agriculture/doctype/disease/disease.json b/erpnext/agriculture/doctype/disease/disease.json deleted file mode 100644 index 16b735a660..0000000000 --- a/erpnext/agriculture/doctype/disease/disease.json +++ /dev/null @@ -1,308 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:common_name", - "beta": 0, - "creation": "2017-11-20 17:16:54.496355", - "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": "common_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Common Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "scientific_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Scientific Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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": "section_break_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "treatment_task", - "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": "Treatment Task", - "length": 0, - "no_copy": 0, - "options": "Agriculture 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": 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": "treatment_period", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Treatment Period", - "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": "section_break_2", - "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": "Treatment Task", - "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": "description", - "fieldtype": "Long Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "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": "2018-11-04 03:27:25.076490", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Disease", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/disease/disease.py b/erpnext/agriculture/doctype/disease/disease.py deleted file mode 100644 index 30ab298376..0000000000 --- a/erpnext/agriculture/doctype/disease/disease.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe import _ -from frappe.model.document import Document - - -class Disease(Document): - def validate(self): - max_period = 0 - for task in self.treatment_task: - # validate start_day is not > end_day - if task.start_day > task.end_day: - frappe.throw(_("Start day is greater than end day in task '{0}'").format(task.task_name)) - # to calculate the period of the Crop Cycle - if task.end_day > max_period: max_period = task.end_day - self.treatment_period = max_period diff --git a/erpnext/agriculture/doctype/disease/test_disease.py b/erpnext/agriculture/doctype/disease/test_disease.py deleted file mode 100644 index 6a6f1e70a9..0000000000 --- a/erpnext/agriculture/doctype/disease/test_disease.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe - - -class TestDisease(unittest.TestCase): - def test_treatment_period(self): - disease = frappe.get_doc('Disease', 'Aphids') - self.assertEqual(disease.treatment_period, 3) diff --git a/erpnext/agriculture/doctype/disease/test_records.json b/erpnext/agriculture/doctype/disease/test_records.json deleted file mode 100644 index e91a61190d..0000000000 --- a/erpnext/agriculture/doctype/disease/test_records.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "doctype": "Disease", - "common_name": "Aphids", - "scientific_name": "Aphidoidea", - "treatment_task": [{ - "task_name": "Survey and find the aphid locations", - "start_day": 1, - "end_day": 2, - "holiday_management": "Ignore holidays" - }, { - "task_name": "Apply Pesticides", - "start_day": 3, - "end_day": 3, - "holiday_management": "Ignore holidays" - }] - } -] \ No newline at end of file diff --git a/erpnext/agriculture/doctype/fertilizer/fertilizer.js b/erpnext/agriculture/doctype/fertilizer/fertilizer.js deleted file mode 100644 index 357e089af2..0000000000 --- a/erpnext/agriculture/doctype/fertilizer/fertilizer.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Fertilizer', { - onload: (frm) => { - if (frm.doc.fertilizer_contents == undefined) frm.call('load_contents'); - } -}); diff --git a/erpnext/agriculture/doctype/fertilizer/fertilizer.json b/erpnext/agriculture/doctype/fertilizer/fertilizer.json deleted file mode 100644 index 6a1877344b..0000000000 --- a/erpnext/agriculture/doctype/fertilizer/fertilizer.json +++ /dev/null @@ -1,307 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:fertilizer_name", - "beta": 0, - "creation": "2017-10-17 18:17:06.175062", - "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": "fertilizer_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Fertilizer Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "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": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "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": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "density", - "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": "Density (if liquid)", - "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": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_28", - "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": "Fertilizer Contents", - "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": "fertilizer_contents", - "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, - "length": 0, - "no_copy": 0, - "options": "Fertilizer Content", - "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": "2018-11-04 03:26:29.211792", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Fertilizer", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/fertilizer/fertilizer.py b/erpnext/agriculture/doctype/fertilizer/fertilizer.py deleted file mode 100644 index 2408302bd1..0000000000 --- a/erpnext/agriculture/doctype/fertilizer/fertilizer.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe.model.document import Document - - -class Fertilizer(Document): - @frappe.whitelist() - def load_contents(self): - docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Fertilizer'}) - for doc in docs: - self.append('fertilizer_contents', {'title': str(doc.name)}) diff --git a/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py b/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py deleted file mode 100644 index c8630ef1f8..0000000000 --- a/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe - - -class TestFertilizer(unittest.TestCase): - def test_fertilizer_creation(self): - self.assertEqual(frappe.db.exists('Fertilizer', 'Urea'), 'Urea') diff --git a/erpnext/agriculture/doctype/fertilizer/test_records.json b/erpnext/agriculture/doctype/fertilizer/test_records.json deleted file mode 100644 index ba735cd985..0000000000 --- a/erpnext/agriculture/doctype/fertilizer/test_records.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "doctype": "Item", - "item_code": "Urea", - "item_name": "Urea", - "item_group": "Fertilizer" - }, - { - "doctype": "Fertilizer", - "fertilizer_name": "Urea", - "item": "Urea" - } -] \ No newline at end of file diff --git a/erpnext/agriculture/doctype/fertilizer_content/fertilizer_content.json b/erpnext/agriculture/doctype/fertilizer_content/fertilizer_content.json deleted file mode 100644 index bf222abaca..0000000000 --- a/erpnext/agriculture/doctype/fertilizer_content/fertilizer_content.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-05 16:54:17.071914", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "options": "Agriculture Analysis Criteria", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Value", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-12-05 19:20:38.892231", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Fertilizer Content", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/agriculture/doctype/fertilizer_content/fertilizer_content.py b/erpnext/agriculture/doctype/fertilizer_content/fertilizer_content.py deleted file mode 100644 index 967c3e02de..0000000000 --- a/erpnext/agriculture/doctype/fertilizer_content/fertilizer_content.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class FertilizerContent(Document): - pass diff --git a/erpnext/agriculture/doctype/linked_location/linked_location.json b/erpnext/agriculture/doctype/linked_location/linked_location.json deleted file mode 100644 index a14ae3d5c4..0000000000 --- a/erpnext/agriculture/doctype/linked_location/linked_location.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-11-22 14:34:59.461273", - "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": "location", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Location", - "length": 0, - "no_copy": 0, - "options": "Location", - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:27:58.120962", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Linked Location", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/linked_location/linked_location.py b/erpnext/agriculture/doctype/linked_location/linked_location.py deleted file mode 100644 index e1257f3db1..0000000000 --- a/erpnext/agriculture/doctype/linked_location/linked_location.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class LinkedLocation(Document): - pass diff --git a/erpnext/agriculture/doctype/linked_plant_analysis/linked_plant_analysis.json b/erpnext/agriculture/doctype/linked_plant_analysis/linked_plant_analysis.json deleted file mode 100644 index 57d2aab7b2..0000000000 --- a/erpnext/agriculture/doctype/linked_plant_analysis/linked_plant_analysis.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-11-22 15:04:25.180446", - "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": "plant_analysis", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Plant Analysis", - "length": 0, - "no_copy": 0, - "options": "Plant Analysis", - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:25:15.359130", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Linked Plant Analysis", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/linked_plant_analysis/linked_plant_analysis.py b/erpnext/agriculture/doctype/linked_plant_analysis/linked_plant_analysis.py deleted file mode 100644 index 0bc04af640..0000000000 --- a/erpnext/agriculture/doctype/linked_plant_analysis/linked_plant_analysis.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class LinkedPlantAnalysis(Document): - pass diff --git a/erpnext/agriculture/doctype/linked_soil_analysis/__init__.py b/erpnext/agriculture/doctype/linked_soil_analysis/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/linked_soil_analysis/linked_soil_analysis.json b/erpnext/agriculture/doctype/linked_soil_analysis/linked_soil_analysis.json deleted file mode 100644 index 38e5030d85..0000000000 --- a/erpnext/agriculture/doctype/linked_soil_analysis/linked_soil_analysis.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-11-22 15:00:37.259063", - "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": "soil_analysis", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Soil Analysis", - "length": 0, - "no_copy": 0, - "options": "Soil Analysis", - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:25:27.670079", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Linked Soil Analysis", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/linked_soil_analysis/linked_soil_analysis.py b/erpnext/agriculture/doctype/linked_soil_analysis/linked_soil_analysis.py deleted file mode 100644 index 0d290556bf..0000000000 --- a/erpnext/agriculture/doctype/linked_soil_analysis/linked_soil_analysis.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class LinkedSoilAnalysis(Document): - pass diff --git a/erpnext/agriculture/doctype/linked_soil_texture/__init__.py b/erpnext/agriculture/doctype/linked_soil_texture/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/linked_soil_texture/linked_soil_texture.json b/erpnext/agriculture/doctype/linked_soil_texture/linked_soil_texture.json deleted file mode 100644 index 80682b07a5..0000000000 --- a/erpnext/agriculture/doctype/linked_soil_texture/linked_soil_texture.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-11-22 14:58:52.818040", - "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": "soil_texture", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Soil Texture", - "length": 0, - "no_copy": 0, - "options": "Soil Texture", - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:26:17.877616", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Linked Soil Texture", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/linked_soil_texture/linked_soil_texture.py b/erpnext/agriculture/doctype/linked_soil_texture/linked_soil_texture.py deleted file mode 100644 index 1438853690..0000000000 --- a/erpnext/agriculture/doctype/linked_soil_texture/linked_soil_texture.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class LinkedSoilTexture(Document): - pass diff --git a/erpnext/agriculture/doctype/plant_analysis/__init__.py b/erpnext/agriculture/doctype/plant_analysis/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.js b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.js deleted file mode 100644 index 3914f832a5..0000000000 --- a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.js +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Plant Analysis', { - onload: (frm) => { - if (frm.doc.plant_analysis_criteria == undefined) frm.call('load_contents'); - }, - refresh: (frm) => { - let map_tools = ["a.leaflet-draw-draw-polyline", - "a.leaflet-draw-draw-polygon", - "a.leaflet-draw-draw-rectangle", - "a.leaflet-draw-draw-circle", - "a.leaflet-draw-draw-circlemarker"]; - - map_tools.forEach((element) => $(element).hide()); - } -}); diff --git a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.json b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.json deleted file mode 100644 index ceb1a5ba5f..0000000000 --- a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.json +++ /dev/null @@ -1,372 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "AG-PLA-.YYYY.-.#####", - "beta": 0, - "creation": "2017-10-18 12:45:13.575986", - "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": "crop", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Crop", - "length": 0, - "no_copy": 0, - "options": "Crop", - "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": "section_break_1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "location", - "fieldtype": "Geolocation", - "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": "Location", - "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": "collection_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Collection Datetime", - "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": "laboratory_testing_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Laboratory Testing Datetime", - "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": "result_datetime", - "fieldtype": "Datetime", - "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": "Result Datetime", - "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": "section_break_2", - "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": "Plant Analysis Criterias", - "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": "plant_analysis_criteria", - "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": "", - "length": 0, - "no_copy": 0, - "options": "Plant Analysis Criteria", - "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": "2018-11-04 03:28:48.087828", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Plant Analysis", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/plant_analysis/plant_analysis.py b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py deleted file mode 100644 index 9a939cde0b..0000000000 --- a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe.model.document import Document - - -class PlantAnalysis(Document): - @frappe.whitelist() - def load_contents(self): - docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Plant Analysis'}) - for doc in docs: - self.append('plant_analysis_criteria', {'title': str(doc.name)}) diff --git a/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.py b/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.py deleted file mode 100644 index cee241f53a..0000000000 --- a/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestPlantAnalysis(unittest.TestCase): - pass diff --git a/erpnext/agriculture/doctype/plant_analysis_criteria/__init__.py b/erpnext/agriculture/doctype/plant_analysis_criteria/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/plant_analysis_criteria/plant_analysis_criteria.json b/erpnext/agriculture/doctype/plant_analysis_criteria/plant_analysis_criteria.json deleted file mode 100644 index eefc5ee1ab..0000000000 --- a/erpnext/agriculture/doctype/plant_analysis_criteria/plant_analysis_criteria.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-05 19:23:52.481348", - "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": "title", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "options": "Agriculture Analysis Criteria", - "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": "value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Value", - "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": "minimum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Minimum Permissible Value", - "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": "maximum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Maximum Permissible Value", - "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": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:25:43.714882", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Plant Analysis Criteria", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/plant_analysis_criteria/plant_analysis_criteria.py b/erpnext/agriculture/doctype/plant_analysis_criteria/plant_analysis_criteria.py deleted file mode 100644 index 7e6571c4a3..0000000000 --- a/erpnext/agriculture/doctype/plant_analysis_criteria/plant_analysis_criteria.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class PlantAnalysisCriteria(Document): - pass diff --git a/erpnext/agriculture/doctype/soil_analysis/__init__.py b/erpnext/agriculture/doctype/soil_analysis/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.js b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.js deleted file mode 100644 index 12829beefb..0000000000 --- a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.js +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Soil Analysis', { - onload: (frm) => { - if (frm.doc.soil_analysis_criteria == undefined) frm.call('load_contents'); - }, - refresh: (frm) => { - let map_tools = ["a.leaflet-draw-draw-polyline", - "a.leaflet-draw-draw-polygon", - "a.leaflet-draw-draw-rectangle", - "a.leaflet-draw-draw-circle", - "a.leaflet-draw-draw-circlemarker"]; - - map_tools.forEach((element) => $(element).hide()); - } -}); diff --git a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.json b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.json deleted file mode 100644 index 59680fab99..0000000000 --- a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.json +++ /dev/null @@ -1,593 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "AG-ANA-.YY.-.MM.-.#####", - "beta": 0, - "creation": "2017-10-17 19:12:16.728395", - "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": "location", - "fieldtype": "Geolocation", - "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": "Location", - "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 - }, - { - "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": "collection_datetime", - "fieldtype": "Datetime", - "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": "Collection Datetime", - "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": "laboratory_testing_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Laboratory Testing Datetime", - "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": "result_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Result Datetime", - "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": "section_break_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ca_per_k", - "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": "Ca/K", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ca_per_mg", - "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": "Ca/Mg", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mg_per_k", - "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": "Mg/K", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_31", - "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": "ca_mg_per_k", - "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": "(Ca+Mg)/K", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ca_per_k_ca_mg", - "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": "Ca/(K+Ca+Mg)", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_28", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "invoice_number", - "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": "Invoice Number", - "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": "soil_analysis_criterias", - "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": "Soil Analysis Criterias", - "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": "soil_analysis_criteria", - "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, - "length": 0, - "no_copy": 0, - "options": "Soil Analysis Criteria", - "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": "2018-11-04 03:28:58.403760", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Soil Analysis", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/soil_analysis/soil_analysis.py b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py deleted file mode 100644 index 03667fbcae..0000000000 --- a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe.model.document import Document - - -class SoilAnalysis(Document): - @frappe.whitelist() - def load_contents(self): - docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Analysis'}) - for doc in docs: - self.append('soil_analysis_criteria', {'title': str(doc.name)}) diff --git a/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.py b/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.py deleted file mode 100644 index bb99363ffd..0000000000 --- a/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestSoilAnalysis(unittest.TestCase): - pass diff --git a/erpnext/agriculture/doctype/soil_analysis_criteria/__init__.py b/erpnext/agriculture/doctype/soil_analysis_criteria/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/soil_analysis_criteria/soil_analysis_criteria.json b/erpnext/agriculture/doctype/soil_analysis_criteria/soil_analysis_criteria.json deleted file mode 100644 index 860e48aa50..0000000000 --- a/erpnext/agriculture/doctype/soil_analysis_criteria/soil_analysis_criteria.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-05 19:36:05.300770", - "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": "title", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "options": "Agriculture Analysis Criteria", - "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": "value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Value", - "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": "minimum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Minimum Permissible Value", - "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": "maximum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Maximum Permissible Value", - "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": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:25:54.359008", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Soil Analysis Criteria", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/soil_analysis_criteria/soil_analysis_criteria.py b/erpnext/agriculture/doctype/soil_analysis_criteria/soil_analysis_criteria.py deleted file mode 100644 index f501820328..0000000000 --- a/erpnext/agriculture/doctype/soil_analysis_criteria/soil_analysis_criteria.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class SoilAnalysisCriteria(Document): - pass diff --git a/erpnext/agriculture/doctype/soil_texture/__init__.py b/erpnext/agriculture/doctype/soil_texture/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/soil_texture/soil_texture.js b/erpnext/agriculture/doctype/soil_texture/soil_texture.js deleted file mode 100644 index 673284b246..0000000000 --- a/erpnext/agriculture/doctype/soil_texture/soil_texture.js +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.provide('agriculture'); - -frappe.ui.form.on('Soil Texture', { - refresh: (frm) => { - let map_tools = ["a.leaflet-draw-draw-polyline", - "a.leaflet-draw-draw-polygon", - "a.leaflet-draw-draw-rectangle", - "a.leaflet-draw-draw-circle", - "a.leaflet-draw-draw-circlemarker"]; - - map_tools.forEach((element) => $(element).hide()); - }, - onload: function(frm) { - if (frm.doc.soil_texture_criteria == undefined) frm.call('load_contents'); - if (frm.doc.ternary_plot) return; - frm.doc.ternary_plot = new agriculture.TernaryPlot({ - parent: frm.get_field("ternary_plot").$wrapper, - clay: frm.doc.clay_composition, - sand: frm.doc.sand_composition, - silt: frm.doc.silt_composition, - }); - }, - soil_type: (frm) => { - let composition_types = ['clay_composition', 'sand_composition', 'silt_composition']; - composition_types.forEach((composition_type) => { - frm.doc[composition_type] = 0; - frm.refresh_field(composition_type); - }); - }, - clay_composition: function(frm) { - frm.call("update_soil_edit", { - soil_type: 'clay_composition' - }, () => { - refresh_ternary_plot(frm, this); - }); - }, - sand_composition: function(frm) { - frm.call("update_soil_edit", { - soil_type: 'sand_composition' - }, () => { - refresh_ternary_plot(frm, this); - }); - }, - silt_composition: function(frm) { - frm.call("update_soil_edit", { - soil_type: 'silt_composition' - }, () => { - refresh_ternary_plot(frm, this); - }); - } -}); - -let refresh_ternary_plot = (frm, me) => { - me.ternary_plot.remove_blip(); - me.ternary_plot.mark_blip({clay: frm.doc.clay_composition, sand: frm.doc.sand_composition, silt: frm.doc.silt_composition}); -}; diff --git a/erpnext/agriculture/doctype/soil_texture/soil_texture.json b/erpnext/agriculture/doctype/soil_texture/soil_texture.json deleted file mode 100644 index f78c262be4..0000000000 --- a/erpnext/agriculture/doctype/soil_texture/soil_texture.json +++ /dev/null @@ -1,533 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "AG-TEX-.YYYY.-.#####", - "beta": 0, - "creation": "2017-10-18 13:06:47.506762", - "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": "location", - "fieldtype": "Geolocation", - "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": "Location", - "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": "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": "collection_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Collection Datetime", - "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": "laboratory_testing_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Laboratory Testing Datetime", - "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": "result_datetime", - "fieldtype": "Datetime", - "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": "Result Datetime", - "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": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "soil_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Soil Type", - "length": 0, - "no_copy": 0, - "options": "Select\nSand\nLoamy Sand\nSandy Loam\nLoam\nSilt Loam\nSilt\nSandy Clay Loam\nClay Loam\nSilty Clay Loam\nSandy Clay\nSilty Clay\nClay", - "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": "0", - "fieldname": "clay_composition", - "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": "Clay Composition (%)", - "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": "0", - "fieldname": "sand_composition", - "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": "Sand Composition (%)", - "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": "0", - "fieldname": "silt_composition", - "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": "Silt Composition (%)", - "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_6", - "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": "ternary_plot", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ternary Plot", - "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": "section_break_15", - "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": "Soil Texture Criteria", - "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": "soil_texture_criteria", - "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, - "length": 0, - "no_copy": 0, - "options": "Soil Texture Criteria", - "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": "2018-11-04 03:29:18.221173", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Soil Texture", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/soil_texture/soil_texture.py b/erpnext/agriculture/doctype/soil_texture/soil_texture.py deleted file mode 100644 index b1fc9a063d..0000000000 --- a/erpnext/agriculture/doctype/soil_texture/soil_texture.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.utils import cint, flt - - -class SoilTexture(Document): - soil_edit_order = [2, 1, 0] - soil_types = ['clay_composition', 'sand_composition', 'silt_composition'] - - @frappe.whitelist() - def load_contents(self): - docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Texture'}) - for doc in docs: - self.append('soil_texture_criteria', {'title': str(doc.name)}) - - def validate(self): - self.update_soil_edit('sand_composition') - for soil_type in self.soil_types: - if self.get(soil_type) > 100 or self.get(soil_type) < 0: - frappe.throw(_("{0} should be a value between 0 and 100").format(soil_type)) - if sum(self.get(soil_type) for soil_type in self.soil_types) != 100: - frappe.throw(_('Soil compositions do not add up to 100')) - - @frappe.whitelist() - def update_soil_edit(self, soil_type): - self.soil_edit_order[self.soil_types.index(soil_type)] = max(self.soil_edit_order)+1 - self.soil_type = self.get_soil_type() - - def get_soil_type(self): - # update the last edited soil type - if sum(self.soil_edit_order) < 5: return - last_edit_index = self.soil_edit_order.index(min(self.soil_edit_order)) - - # set composition of the last edited soil - self.set(self.soil_types[last_edit_index], - 100 - sum(cint(self.get(soil_type)) for soil_type in self.soil_types) + cint(self.get(self.soil_types[last_edit_index]))) - - # calculate soil type - c, sa, si = flt(self.clay_composition), flt(self.sand_composition), flt(self.silt_composition) - - if si + (1.5 * c) < 15: - return 'Sand' - elif si + 1.5 * c >= 15 and si + 2 * c < 30: - return 'Loamy Sand' - elif ((c >= 7 and c < 20) or (sa > 52) and ((si + 2*c) >= 30) or (c < 7 and si < 50 and (si+2*c) >= 30)): - return 'Sandy Loam' - elif ((c >= 7 and c < 27) and (si >= 28 and si < 50) and (sa <= 52)): - return 'Loam' - elif ((si >= 50 and (c >= 12 and c < 27)) or ((si >= 50 and si < 80) and c < 12)): - return 'Silt Loam' - elif (si >= 80 and c < 12): - return 'Silt' - elif ((c >= 20 and c < 35) and (si < 28) and (sa > 45)): - return 'Sandy Clay Loam' - elif ((c >= 27 and c < 40) and (sa > 20 and sa <= 45)): - return 'Clay Loam' - elif ((c >= 27 and c < 40) and (sa <= 20)): - return 'Silty Clay Loam' - elif (c >= 35 and sa > 45): - return 'Sandy Clay' - elif (c >= 40 and si >= 40): - return 'Silty Clay' - elif (c >= 40 and sa <= 45 and si < 40): - return 'Clay' - else: - return 'Select' diff --git a/erpnext/agriculture/doctype/soil_texture/test_records.json b/erpnext/agriculture/doctype/soil_texture/test_records.json deleted file mode 100644 index dcac7ad8df..0000000000 --- a/erpnext/agriculture/doctype/soil_texture/test_records.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "doctype": "Soil Texture", - "location": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Point\",\"coordinates\":[72.861242,19.079153]}}]}", - "collection_datetime": "2017-11-08", - "clay_composition": 20, - "sand_composition": 30 - } -] \ No newline at end of file diff --git a/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py b/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py deleted file mode 100644 index 45497675ce..0000000000 --- a/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe - - -class TestSoilTexture(unittest.TestCase): - def test_texture_selection(self): - soil_tex = frappe.get_all('Soil Texture', fields=['name'], filters={'collection_datetime': '2017-11-08'}) - doc = frappe.get_doc('Soil Texture', soil_tex[0].name) - self.assertEqual(doc.silt_composition, 50) - self.assertEqual(doc.soil_type, 'Silt Loam') diff --git a/erpnext/agriculture/doctype/soil_texture_criteria/__init__.py b/erpnext/agriculture/doctype/soil_texture_criteria/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/soil_texture_criteria/soil_texture_criteria.json b/erpnext/agriculture/doctype/soil_texture_criteria/soil_texture_criteria.json deleted file mode 100644 index 0cd72b0f6e..0000000000 --- a/erpnext/agriculture/doctype/soil_texture_criteria/soil_texture_criteria.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-05 23:45:17.419610", - "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": "title", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "options": "Agriculture Analysis Criteria", - "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": "value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Value", - "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": "minimum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Minimum Permissible Value", - "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": "maximum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Maximum Permissible Value", - "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": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:26:46.178377", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Soil Texture Criteria", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/soil_texture_criteria/soil_texture_criteria.py b/erpnext/agriculture/doctype/soil_texture_criteria/soil_texture_criteria.py deleted file mode 100644 index 92a0cf9aec..0000000000 --- a/erpnext/agriculture/doctype/soil_texture_criteria/soil_texture_criteria.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class SoilTextureCriteria(Document): - pass diff --git a/erpnext/agriculture/doctype/water_analysis/__init__.py b/erpnext/agriculture/doctype/water_analysis/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/water_analysis/test_water_analysis.py b/erpnext/agriculture/doctype/water_analysis/test_water_analysis.py deleted file mode 100644 index ae144ccb21..0000000000 --- a/erpnext/agriculture/doctype/water_analysis/test_water_analysis.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestWaterAnalysis(unittest.TestCase): - pass diff --git a/erpnext/agriculture/doctype/water_analysis/water_analysis.js b/erpnext/agriculture/doctype/water_analysis/water_analysis.js deleted file mode 100644 index 13fe3adde6..0000000000 --- a/erpnext/agriculture/doctype/water_analysis/water_analysis.js +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Water Analysis', { - onload: (frm) => { - if (frm.doc.water_analysis_criteria == undefined) frm.call('load_contents'); - }, - refresh: (frm) => { - let map_tools = ["a.leaflet-draw-draw-polyline", - "a.leaflet-draw-draw-polygon", - "a.leaflet-draw-draw-rectangle", - "a.leaflet-draw-draw-circle", - "a.leaflet-draw-draw-circlemarker"]; - - map_tools.forEach((element) => $(element).hide()); - }, - laboratory_testing_datetime: (frm) => frm.call("update_lab_result_date") -}); diff --git a/erpnext/agriculture/doctype/water_analysis/water_analysis.json b/erpnext/agriculture/doctype/water_analysis/water_analysis.json deleted file mode 100644 index f990fef997..0000000000 --- a/erpnext/agriculture/doctype/water_analysis/water_analysis.json +++ /dev/null @@ -1,594 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "HR-WAT-.YYYY.-.#####", - "beta": 0, - "creation": "2017-10-17 18:51:19.946950", - "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": "location", - "fieldtype": "Geolocation", - "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": "Location", - "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": "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, - "label": "", - "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": "collection_datetime", - "fieldtype": "Datetime", - "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": "Collection Datetime", - "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": "laboratory_testing_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Laboratory Testing Datetime", - "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": "result_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Result Datetime", - "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": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "type_of_sample", - "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": "Type of Sample", - "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": "container", - "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": "Container", - "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": "origin", - "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": "Origin", - "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_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": "collection_temperature", - "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": "Collection Temperature ", - "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": "storage_temperature", - "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": "Storage Temperature", - "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": "appearance", - "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": "Appearance", - "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": "person_responsible", - "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": "Person Responsible", - "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_29", - "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": "Water Analysis Criteria", - "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": "water_analysis_criteria", - "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, - "length": 0, - "no_copy": 0, - "options": "Water Analysis Criteria", - "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": "2018-11-04 03:29:08.325644", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Water Analysis", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/water_analysis/water_analysis.py b/erpnext/agriculture/doctype/water_analysis/water_analysis.py deleted file mode 100644 index 434acecc6e..0000000000 --- a/erpnext/agriculture/doctype/water_analysis/water_analysis.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe import _ -from frappe.model.document import Document - - -class WaterAnalysis(Document): - @frappe.whitelist() - def load_contents(self): - docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Water Analysis'}) - for doc in docs: - self.append('water_analysis_criteria', {'title': str(doc.name)}) - - @frappe.whitelist() - def update_lab_result_date(self): - if not self.result_datetime: - self.result_datetime = self.laboratory_testing_datetime - - def validate(self): - if self.collection_datetime > self.laboratory_testing_datetime: - frappe.throw(_('Lab testing datetime cannot be before collection datetime')) - if self.laboratory_testing_datetime > self.result_datetime: - frappe.throw(_('Lab result datetime cannot be before testing datetime')) diff --git a/erpnext/agriculture/doctype/water_analysis_criteria/__init__.py b/erpnext/agriculture/doctype/water_analysis_criteria/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/water_analysis_criteria/water_analysis_criteria.json b/erpnext/agriculture/doctype/water_analysis_criteria/water_analysis_criteria.json deleted file mode 100644 index be9f1beffe..0000000000 --- a/erpnext/agriculture/doctype/water_analysis_criteria/water_analysis_criteria.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-05 23:36:22.723558", - "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": "title", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "options": "Agriculture Analysis Criteria", - "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": "value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Value", - "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": "minimum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Minimum Permissible Value", - "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": "maximum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Maximum Permissible Value", - "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": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:26:07.026834", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Water Analysis Criteria", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/water_analysis_criteria/water_analysis_criteria.py b/erpnext/agriculture/doctype/water_analysis_criteria/water_analysis_criteria.py deleted file mode 100644 index 225c4f6529..0000000000 --- a/erpnext/agriculture/doctype/water_analysis_criteria/water_analysis_criteria.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class WaterAnalysisCriteria(Document): - pass diff --git a/erpnext/agriculture/doctype/weather/__init__.py b/erpnext/agriculture/doctype/weather/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/weather/test_weather.py b/erpnext/agriculture/doctype/weather/test_weather.py deleted file mode 100644 index 345baa9e99..0000000000 --- a/erpnext/agriculture/doctype/weather/test_weather.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestWeather(unittest.TestCase): - pass diff --git a/erpnext/agriculture/doctype/weather/weather.js b/erpnext/agriculture/doctype/weather/weather.js deleted file mode 100644 index dadb1d8b13..0000000000 --- a/erpnext/agriculture/doctype/weather/weather.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Weather', { - onload: (frm) => { - if (frm.doc.weather_parameter == undefined) frm.call('load_contents'); - } -}); diff --git a/erpnext/agriculture/doctype/weather/weather.json b/erpnext/agriculture/doctype/weather/weather.json deleted file mode 100644 index ebab78ad72..0000000000 --- a/erpnext/agriculture/doctype/weather/weather.json +++ /dev/null @@ -1,307 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "format:WEA-{date}-{location}", - "beta": 0, - "creation": "2017-10-17 19:01:05.095598", - "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": "location", - "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": "Location", - "length": 0, - "no_copy": 0, - "options": "Location", - "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": "date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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": "source", - "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": "Source", - "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": "section_break_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_9", - "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": "Weather Parameter", - "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": "weather_parameter", - "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, - "length": 0, - "no_copy": 0, - "options": "Weather Parameter", - "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": "2018-11-04 03:31:36.839743", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Weather", - "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": "Agriculture Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Agriculture User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/weather/weather.py b/erpnext/agriculture/doctype/weather/weather.py deleted file mode 100644 index 8750709c56..0000000000 --- a/erpnext/agriculture/doctype/weather/weather.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe.model.document import Document - - -class Weather(Document): - @frappe.whitelist() - def load_contents(self): - docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Weather'}) - for doc in docs: - self.append('weather_parameter', {'title': str(doc.name)}) diff --git a/erpnext/agriculture/doctype/weather_parameter/__init__.py b/erpnext/agriculture/doctype/weather_parameter/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/agriculture/doctype/weather_parameter/weather_parameter.json b/erpnext/agriculture/doctype/weather_parameter/weather_parameter.json deleted file mode 100644 index 45c4cfc4f5..0000000000 --- a/erpnext/agriculture/doctype/weather_parameter/weather_parameter.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-06 00:19:15.967334", - "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": "title", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "options": "Agriculture Analysis Criteria", - "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": "value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Value", - "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": "minimum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Minimum Permissible Value", - "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": "maximum_permissible_value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Maximum Permissible Value", - "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": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:26:58.794373", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Weather Parameter", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Agriculture", - "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/agriculture/doctype/weather_parameter/weather_parameter.py b/erpnext/agriculture/doctype/weather_parameter/weather_parameter.py deleted file mode 100644 index 7f02ab39ae..0000000000 --- a/erpnext/agriculture/doctype/weather_parameter/weather_parameter.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class WeatherParameter(Document): - pass diff --git a/erpnext/agriculture/setup.py b/erpnext/agriculture/setup.py deleted file mode 100644 index 70931b9ad5..0000000000 --- a/erpnext/agriculture/setup.py +++ /dev/null @@ -1,429 +0,0 @@ -import frappe -from frappe import _ -from erpnext.setup.utils import insert_record - -def setup_agriculture(): - if frappe.get_all('Agriculture Analysis Criteria'): - # already setup - return - create_agriculture_data() - -def create_agriculture_data(): - records = [ - dict( - doctype='Item Group', - item_group_name='Fertilizer', - is_group=0, - parent_item_group=_('All Item Groups')), - dict( - doctype='Item Group', - item_group_name='Seed', - is_group=0, - parent_item_group=_('All Item Groups')), - dict( - doctype='Item Group', - item_group_name='By-product', - is_group=0, - parent_item_group=_('All Item Groups')), - dict( - doctype='Item Group', - item_group_name='Produce', - is_group=0, - parent_item_group=_('All Item Groups')), - dict( - doctype='Agriculture Analysis Criteria', - title='Nitrogen Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Phosphorous Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Potassium Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Calcium Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Sulphur Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Magnesium Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Iron Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Copper Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Zinc Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Boron Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Manganese Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Chlorine Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Molybdenum Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Sodium Content', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Humic Acid', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Fulvic Acid', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Inert', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Others', - standard=1, - linked_doctype='Fertilizer'), - dict( - doctype='Agriculture Analysis Criteria', - title='Nitrogen', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Phosphorous', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Potassium', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Calcium', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Magnesium', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Sulphur', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Boron', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Copper', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Iron', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Manganese', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Zinc', - standard=1, - linked_doctype='Plant Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Depth (in cm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Soil pH', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Salt Concentration (%)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Organic Matter (%)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='CEC (Cation Exchange Capacity) (MAQ/100mL)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Potassium Saturation (%)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Calcium Saturation (%)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Manganese Saturation (%)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Nirtogen (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Phosphorous (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Potassium (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Calcium (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Magnesium (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Sulphur (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Copper (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Iron (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Manganese (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Zinc (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Aluminium (ppm)', - standard=1, - linked_doctype='Soil Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Water pH', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Conductivity (mS/cm)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Hardness (mg/CaCO3)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Turbidity (NTU)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Odor', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Color', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Nitrate (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Nirtite (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Calcium (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Magnesium (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Sulphate (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Boron (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Copper (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Iron (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Manganese (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Zinc (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Chlorine (mg/L)', - standard=1, - linked_doctype='Water Analysis'), - dict( - doctype='Agriculture Analysis Criteria', - title='Bulk Density', - standard=1, - linked_doctype='Soil Texture'), - dict( - doctype='Agriculture Analysis Criteria', - title='Field Capacity', - standard=1, - linked_doctype='Soil Texture'), - dict( - doctype='Agriculture Analysis Criteria', - title='Wilting Point', - standard=1, - linked_doctype='Soil Texture'), - dict( - doctype='Agriculture Analysis Criteria', - title='Hydraulic Conductivity', - standard=1, - linked_doctype='Soil Texture'), - dict( - doctype='Agriculture Analysis Criteria', - title='Organic Matter', - standard=1, - linked_doctype='Soil Texture'), - dict( - doctype='Agriculture Analysis Criteria', - title='Temperature High', - standard=1, - linked_doctype='Weather'), - dict( - doctype='Agriculture Analysis Criteria', - title='Temperature Low', - standard=1, - linked_doctype='Weather'), - dict( - doctype='Agriculture Analysis Criteria', - title='Temperature Average', - standard=1, - linked_doctype='Weather'), - dict( - doctype='Agriculture Analysis Criteria', - title='Dew Point', - standard=1, - linked_doctype='Weather'), - dict( - doctype='Agriculture Analysis Criteria', - title='Precipitation Received', - standard=1, - linked_doctype='Weather'), - dict( - doctype='Agriculture Analysis Criteria', - title='Humidity', - standard=1, - linked_doctype='Weather'), - dict( - doctype='Agriculture Analysis Criteria', - title='Pressure', - standard=1, - linked_doctype='Weather'), - dict( - doctype='Agriculture Analysis Criteria', - title='Insolation/ PAR (Photosynthetically Active Radiation)', - standard=1, - linked_doctype='Weather'), - dict( - doctype='Agriculture Analysis Criteria', - title='Degree Days', - standard=1, - linked_doctype='Weather') - ] - insert_record(records) diff --git a/erpnext/agriculture/workspace/agriculture/agriculture.json b/erpnext/agriculture/workspace/agriculture/agriculture.json deleted file mode 100644 index 6714de6d38..0000000000 --- a/erpnext/agriculture/workspace/agriculture/agriculture.json +++ /dev/null @@ -1,171 +0,0 @@ -{ - "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Crops & Lands\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Analytics\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Diseases & Fertilizers\", \"col\": 4}}]", - "creation": "2020-03-02 17:23:34.339274", - "docstatus": 0, - "doctype": "Workspace", - "for_user": "", - "hide_custom": 0, - "icon": "agriculture", - "idx": 0, - "label": "Agriculture", - "links": [ - { - "hidden": 0, - "is_query_report": 0, - "label": "Crops & Lands", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Crop", - "link_count": 0, - "link_to": "Crop", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Crop Cycle", - "link_count": 0, - "link_to": "Crop Cycle", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Location", - "link_count": 0, - "link_to": "Location", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Analytics", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Plant Analysis", - "link_count": 0, - "link_to": "Plant Analysis", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Soil Analysis", - "link_count": 0, - "link_to": "Soil Analysis", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Water Analysis", - "link_count": 0, - "link_to": "Water Analysis", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Soil Texture", - "link_count": 0, - "link_to": "Soil Texture", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Weather", - "link_count": 0, - "link_to": "Weather", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Agriculture Analysis Criteria", - "link_count": 0, - "link_to": "Agriculture Analysis Criteria", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Diseases & Fertilizers", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Disease", - "link_count": 0, - "link_to": "Disease", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Fertilizer", - "link_count": 0, - "link_to": "Fertilizer", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - } - ], - "modified": "2021-08-05 12:15:54.595198", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Agriculture", - "owner": "Administrator", - "parent_page": "", - "public": 1, - "restrict_to_domain": "Agriculture", - "roles": [], - "sequence_id": 3, - "shortcuts": [], - "title": "Agriculture" -} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 153f5c537a..f414930d72 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -108,6 +108,10 @@ frappe.ui.form.on('Asset', { frm.trigger("create_asset_repair"); }, __("Manage")); + frm.add_custom_button(__("Split Asset"), function() { + frm.trigger("split_asset"); + }, __("Manage")); + if (frm.doc.status != 'Fully Depreciated') { frm.add_custom_button(__("Adjust Asset Value"), function() { frm.trigger("create_asset_value_adjustment"); @@ -322,6 +326,43 @@ frappe.ui.form.on('Asset', { }); }, + split_asset: function(frm) { + const title = __('Split Asset'); + + const fields = [ + { + fieldname: 'split_qty', + fieldtype: 'Int', + label: __('Split Qty'), + reqd: 1 + } + ]; + + let dialog = new frappe.ui.Dialog({ + title: title, + fields: fields + }); + + dialog.set_primary_action(__('Split'), function() { + const dialog_data = dialog.get_values(); + frappe.call({ + args: { + "asset_name": frm.doc.name, + "split_qty": cint(dialog_data.split_qty) + }, + method: "erpnext.assets.doctype.asset.asset.split_asset", + callback: function(r) { + let doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + + dialog.hide(); + }); + + dialog.show(); + }, + create_asset_value_adjustment: function(frm) { frappe.call({ args: { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index de060757e2..6e6bbf1cd2 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -3,7 +3,7 @@ "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", - "creation": "2016-03-01 17:01:27.920130", + "creation": "2022-01-18 02:26:55.975005", "doctype": "DocType", "document_type": "Document", "engine": "InnoDB", @@ -23,6 +23,7 @@ "asset_name", "asset_category", "location", + "split_from", "custodian", "department", "disposal_date", @@ -35,6 +36,7 @@ "available_for_use_date", "column_break_23", "gross_purchase_amount", + "asset_quantity", "purchase_date", "section_break_23", "calculate_depreciation", @@ -141,6 +143,7 @@ }, { "allow_on_submit": 1, + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach Image", "hidden": 1, @@ -480,6 +483,19 @@ "fieldname": "section_break_36", "fieldtype": "Section Break", "label": "Finance Books" + }, + { + "fieldname": "split_from", + "fieldtype": "Link", + "label": "Split From", + "options": "Asset", + "read_only": 1 + }, + { + "fieldname": "asset_quantity", + "fieldtype": "Int", + "label": "Asset Quantity", + "read_only_depends_on": "eval:!doc.is_existing_asset" } ], "idx": 72, @@ -502,10 +518,11 @@ "link_fieldname": "asset" } ], - "modified": "2021-06-24 14:58:51.097908", + "modified": "2022-01-30 20:19:24.680027", "modified_by": "Administrator", "module": "Assets", "name": "Asset", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -542,6 +559,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "asset_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index a18b03a888..6e87426ccb 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -36,8 +36,10 @@ class Asset(AccountsController): self.validate_asset_values() self.validate_asset_and_reference() self.validate_item() + self.validate_cost_center() self.set_missing_values() - self.prepare_depreciation_data() + if not self.split_from: + self.prepare_depreciation_data() self.validate_gross_and_purchase_amount() if self.get("schedules"): self.validate_expected_value_after_useful_life() @@ -95,6 +97,19 @@ class Asset(AccountsController): elif item.is_stock_item: frappe.throw(_("Item {0} must be a non-stock item").format(self.item_code)) + def validate_cost_center(self): + if not self.cost_center: return + + cost_center_company = frappe.db.get_value('Cost Center', self.cost_center, 'company') + if cost_center_company != self.company: + frappe.throw( + _("Selected Cost Center {} doesn't belongs to {}").format( + frappe.bold(self.cost_center), + frappe.bold(self.company) + ), + title=_("Invalid Cost Center") + ) + def validate_in_use_date(self): if not self.available_for_use_date: frappe.throw(_("Available for use date is required")) @@ -188,142 +203,143 @@ class Asset(AccountsController): start = self.clear_depreciation_schedule() for finance_book in self.get('finance_books'): - self.validate_asset_finance_books(finance_book) + self._make_depreciation_schedule(finance_book, start, date_of_sale) - # value_after_depreciation - current Asset value - if self.docstatus == 1 and finance_book.value_after_depreciation: - value_after_depreciation = flt(finance_book.value_after_depreciation) - else: - value_after_depreciation = (flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation)) + def _make_depreciation_schedule(self, finance_book, start, date_of_sale): + self.validate_asset_finance_books(finance_book) - finance_book.value_after_depreciation = value_after_depreciation + value_after_depreciation = self._get_value_after_depreciation(finance_book) + finance_book.value_after_depreciation = value_after_depreciation - number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \ - cint(self.number_of_depreciations_booked) + number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \ + cint(self.number_of_depreciations_booked) - has_pro_rata = self.check_is_pro_rata(finance_book) + has_pro_rata = self.check_is_pro_rata(finance_book) + if has_pro_rata: + number_of_pending_depreciations += 1 - if has_pro_rata: - number_of_pending_depreciations += 1 + skip_row = False - skip_row = False + for n in range(start[finance_book.idx-1], number_of_pending_depreciations): + # If depreciation is already completed (for double declining balance) + if skip_row: continue - for n in range(start[finance_book.idx-1], number_of_pending_depreciations): - # If depreciation is already completed (for double declining balance) - if skip_row: continue + depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book) - depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book) + if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: + schedule_date = add_months(finance_book.depreciation_start_date, + n * cint(finance_book.frequency_of_depreciation)) - if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: - schedule_date = add_months(finance_book.depreciation_start_date, - n * cint(finance_book.frequency_of_depreciation)) + # schedule date will be a year later from start date + # so monthly schedule date is calculated by removing 11 months from it + monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1) - # schedule date will be a year later from start date - # so monthly schedule date is calculated by removing 11 months from it - monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1) - - # if asset is being sold - if date_of_sale: - from_date = self.get_from_date(finance_book.finance_book) - depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, - from_date, date_of_sale) - - if depreciation_amount > 0: - self.append("schedules", { - "schedule_date": date_of_sale, - "depreciation_amount": depreciation_amount, - "depreciation_method": finance_book.depreciation_method, - "finance_book": finance_book.finance_book, - "finance_book_id": finance_book.idx - }) - - break - - # For first row - if has_pro_rata and not self.opening_accumulated_depreciation and n==0: - depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, - self.available_for_use_date, finance_book.depreciation_start_date) - - # For first depr schedule date will be the start date - # so monthly schedule date is calculated by removing month difference between use date and start date - monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1) - - # For last row - elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: - if not self.flags.increase_in_asset_life: - # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission - self.to_date = add_months(self.available_for_use_date, - (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation)) - - depreciation_amount_without_pro_rata = depreciation_amount - - depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, - depreciation_amount, schedule_date, self.to_date) - - depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata, - depreciation_amount, finance_book.finance_book) - - monthly_schedule_date = add_months(schedule_date, 1) - schedule_date = add_days(schedule_date, days) - last_schedule_date = schedule_date - - if not depreciation_amount: continue - value_after_depreciation -= flt(depreciation_amount, - self.precision("gross_purchase_amount")) - - # Adjust depreciation amount in the last period based on the expected value after useful life - if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1 - and value_after_depreciation != finance_book.expected_value_after_useful_life) - or value_after_depreciation < finance_book.expected_value_after_useful_life): - depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life) - skip_row = True + # if asset is being sold + if date_of_sale: + from_date = self.get_from_date(finance_book.finance_book) + depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, + from_date, date_of_sale) if depreciation_amount > 0: - # With monthly depreciation, each depreciation is divided by months remaining until next date - if self.allow_monthly_depreciation: - # month range is 1 to 12 - # In pro rata case, for first and last depreciation, month range would be different - month_range = months \ - if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \ - else finance_book.frequency_of_depreciation + self._add_depreciation_row(date_of_sale, depreciation_amount, finance_book.depreciation_method, + finance_book.finance_book, finance_book.idx) - for r in range(month_range): - if (has_pro_rata and n == 0): - # For first entry of monthly depr - if r == 0: - days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date) - per_day_amt = depreciation_amount / days - depreciation_amount_for_current_month = per_day_amt * days_until_first_depr - depreciation_amount -= depreciation_amount_for_current_month - date = monthly_schedule_date - amount = depreciation_amount_for_current_month - else: - date = add_months(monthly_schedule_date, r) - amount = depreciation_amount / (month_range - 1) - elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1: - # For last entry of monthly depr - date = last_schedule_date - amount = depreciation_amount / month_range + break + + # For first row + if has_pro_rata and not self.opening_accumulated_depreciation and n==0: + from_date = add_days(self.available_for_use_date, -1) # needed to calc depr amount for available_for_use_date too + depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, + from_date, finance_book.depreciation_start_date) + + # For first depr schedule date will be the start date + # so monthly schedule date is calculated by removing month difference between use date and start date + monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1) + + # For last row + elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: + if not self.flags.increase_in_asset_life: + # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission + self.to_date = add_months(self.available_for_use_date, + (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation)) + + depreciation_amount_without_pro_rata = depreciation_amount + + depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, + depreciation_amount, schedule_date, self.to_date) + + depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata, + depreciation_amount, finance_book.finance_book) + + monthly_schedule_date = add_months(schedule_date, 1) + schedule_date = add_days(schedule_date, days) + last_schedule_date = schedule_date + + if not depreciation_amount: continue + value_after_depreciation -= flt(depreciation_amount, + self.precision("gross_purchase_amount")) + + # Adjust depreciation amount in the last period based on the expected value after useful life + if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1 + and value_after_depreciation != finance_book.expected_value_after_useful_life) + or value_after_depreciation < finance_book.expected_value_after_useful_life): + depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life) + skip_row = True + + if depreciation_amount > 0: + # With monthly depreciation, each depreciation is divided by months remaining until next date + if self.allow_monthly_depreciation: + # month range is 1 to 12 + # In pro rata case, for first and last depreciation, month range would be different + month_range = months \ + if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \ + else finance_book.frequency_of_depreciation + + for r in range(month_range): + if (has_pro_rata and n == 0): + # For first entry of monthly depr + if r == 0: + days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date) + per_day_amt = depreciation_amount / days + depreciation_amount_for_current_month = per_day_amt * days_until_first_depr + depreciation_amount -= depreciation_amount_for_current_month + date = monthly_schedule_date + amount = depreciation_amount_for_current_month else: date = add_months(monthly_schedule_date, r) - amount = depreciation_amount / month_range + amount = depreciation_amount / (month_range - 1) + elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1: + # For last entry of monthly depr + date = last_schedule_date + amount = depreciation_amount / month_range + else: + date = add_months(monthly_schedule_date, r) + amount = depreciation_amount / month_range - self.append("schedules", { - "schedule_date": date, - "depreciation_amount": amount, - "depreciation_method": finance_book.depreciation_method, - "finance_book": finance_book.finance_book, - "finance_book_id": finance_book.idx - }) - else: - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": finance_book.depreciation_method, - "finance_book": finance_book.finance_book, - "finance_book_id": finance_book.idx - }) + self._add_depreciation_row(date, amount, finance_book.depreciation_method, + finance_book.finance_book, finance_book.idx) + else: + self._add_depreciation_row(schedule_date, depreciation_amount, finance_book.depreciation_method, + finance_book.finance_book, finance_book.idx) + + def _add_depreciation_row(self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id): + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": depreciation_method, + "finance_book": finance_book, + "finance_book_id": finance_book_id + }) + + def _get_value_after_depreciation(self, finance_book): + # value_after_depreciation - current Asset value + if self.docstatus == 1 and finance_book.value_after_depreciation: + value_after_depreciation = flt(finance_book.value_after_depreciation) + else: + value_after_depreciation = (flt(self.gross_purchase_amount) - + flt(self.opening_accumulated_depreciation)) + + return value_after_depreciation # depreciation schedules need to be cleared before modification due to increase in asset life/asset sales # JE: Journal Entry, FB: Finance Book @@ -333,7 +349,6 @@ class Asset(AccountsController): depr_schedule = [] for schedule in self.get('schedules'): - # to update start when there are JEs linked with all the schedule rows corresponding to an FB if len(start) == (int(schedule.finance_book_id) - 2): start.append(num_of_depreciations_completed) @@ -374,7 +389,9 @@ class Asset(AccountsController): if from_date: return from_date - return self.available_for_use_date + + # since depr for available_for_use_date is not yet booked + return add_days(self.available_for_use_date, -1) # if it returns True, depreciation_amount will not be equal for the first and last rows def check_is_pro_rata(self, row): @@ -608,7 +625,17 @@ class Asset(AccountsController): return purchase_document def get_fixed_asset_account(self): - return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company) + fixed_asset_account = get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company) + if not fixed_asset_account: + frappe.throw( + _("Set {0} in asset category {1} for company {2}").format( + frappe.bold("Fixed Asset Account"), + frappe.bold(self.asset_category), + frappe.bold(self.company), + ), + title=_("Account not Found"), + ) + return fixed_asset_account def get_cwip_account(self, cwip_enabled=False): cwip_account = None @@ -897,3 +924,113 @@ def get_depreciation_amount(asset, depreciable_value, row): depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) return depreciation_amount + +@frappe.whitelist() +def split_asset(asset_name, split_qty): + asset = frappe.get_doc("Asset", asset_name) + split_qty = cint(split_qty) + + if split_qty >= asset.asset_quantity: + frappe.throw(_("Split qty cannot be grater than or equal to asset qty")) + + remaining_qty = asset.asset_quantity - split_qty + + new_asset = create_new_asset_after_split(asset, split_qty) + update_existing_asset(asset, remaining_qty) + + return new_asset + +def update_existing_asset(asset, remaining_qty): + remaining_gross_purchase_amount = flt((asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity) + opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity) + + frappe.db.set_value("Asset", asset.name, { + 'opening_accumulated_depreciation': opening_accumulated_depreciation, + 'gross_purchase_amount': remaining_gross_purchase_amount, + 'asset_quantity': remaining_qty + }) + + for finance_book in asset.get('finance_books'): + value_after_depreciation = flt((finance_book.value_after_depreciation * remaining_qty)/asset.asset_quantity) + expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * remaining_qty)/asset.asset_quantity) + frappe.db.set_value('Asset Finance Book', finance_book.name, 'value_after_depreciation', value_after_depreciation) + frappe.db.set_value('Asset Finance Book', finance_book.name, 'expected_value_after_useful_life', expected_value_after_useful_life) + + accumulated_depreciation = 0 + + for term in asset.get('schedules'): + depreciation_amount = flt((term.depreciation_amount * remaining_qty)/asset.asset_quantity) + frappe.db.set_value('Depreciation Schedule', term.name, 'depreciation_amount', depreciation_amount) + accumulated_depreciation += depreciation_amount + frappe.db.set_value('Depreciation Schedule', term.name, 'accumulated_depreciation_amount', accumulated_depreciation) + +def create_new_asset_after_split(asset, split_qty): + new_asset = frappe.copy_doc(asset) + new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.asset_quantity) + opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * split_qty) / asset.asset_quantity) + + new_asset.gross_purchase_amount = new_gross_purchase_amount + new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation + new_asset.asset_quantity = split_qty + new_asset.split_from = asset.name + accumulated_depreciation = 0 + + for finance_book in new_asset.get('finance_books'): + finance_book.value_after_depreciation = flt((finance_book.value_after_depreciation * split_qty)/asset.asset_quantity) + finance_book.expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * split_qty)/asset.asset_quantity) + + for term in new_asset.get('schedules'): + depreciation_amount = flt((term.depreciation_amount * split_qty)/asset.asset_quantity) + term.depreciation_amount = depreciation_amount + accumulated_depreciation += depreciation_amount + term.accumulated_depreciation_amount = accumulated_depreciation + + new_asset.submit() + new_asset.set_status() + + for term in new_asset.get('schedules'): + # Update references in JV + if term.journal_entry: + add_reference_in_jv_on_split(term.journal_entry, new_asset.name, asset.name, term.depreciation_amount) + + return new_asset + +def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount): + journal_entry = frappe.get_doc('Journal Entry', entry_name) + entries_to_add = [] + idx = len(journal_entry.get('accounts')) + 1 + + for account in journal_entry.get('accounts'): + if account.reference_name == old_asset_name: + entries_to_add.append(frappe.copy_doc(account).as_dict()) + if account.credit: + account.credit = account.credit - depreciation_amount + account.credit_in_account_currency = account.credit_in_account_currency - \ + account.exchange_rate * depreciation_amount + elif account.debit: + account.debit = account.debit - depreciation_amount + account.debit_in_account_currency = account.debit_in_account_currency - \ + account.exchange_rate * depreciation_amount + + for entry in entries_to_add: + entry.reference_name = new_asset_name + if entry.credit: + entry.credit = depreciation_amount + entry.credit_in_account_currency = entry.exchange_rate * depreciation_amount + elif entry.debit: + entry.debit = depreciation_amount + entry.debit_in_account_currency = entry.exchange_rate * depreciation_amount + + entry.idx = idx + idx += 1 + + journal_entry.append('accounts', entry) + + journal_entry.flags.ignore_validate_update_after_submit = True + journal_entry.save() + + # Repost GL Entries + journal_entry.docstatus = 2 + journal_entry.make_gl_entries(1) + journal_entry.docstatus = 1 + journal_entry.make_gl_entries() \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 44c4ce542d..c08dc21a8f 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -7,7 +7,7 @@ import frappe from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice -from erpnext.assets.doctype.asset.asset import make_sales_invoice +from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset from erpnext.assets.doctype.asset.depreciation import ( post_depreciation_entries, restore_asset, @@ -134,6 +134,29 @@ class TestAsset(AssetSetup): pr.cancel() self.assertEqual(asset.docstatus, 2) + def test_purchase_of_grouped_asset(self): + create_fixed_asset_item("Rack", is_grouped_asset=1) + pr = make_purchase_receipt(item_code="Rack", qty=3, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + self.assertEqual(asset.asset_quantity, 3) + asset.calculate_depreciation = 1 + + month_end_date = get_last_day(nowdate()) + purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + + asset.available_for_use_date = purchase_date + asset.purchase_date = purchase_date + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": month_end_date + }) + asset.submit() + def test_is_fixed_asset_set(self): asset = create_asset(is_existing_asset = 1) doc = frappe.new_doc('Purchase Invoice') @@ -207,9 +230,9 @@ class TestAsset(AssetSetup): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0), + ("_Test Accumulated Depreciations - _TC", 20490.2, 0.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0), ("Debtors - _TC", 25000.0, 0.0) ) @@ -222,6 +245,57 @@ class TestAsset(AssetSetup): si.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + def test_asset_splitting(self): + asset = create_asset( + calculate_depreciation = 1, + asset_quantity=10, + available_for_use_date = '2020-01-01', + purchase_date = '2020-01-01', + expected_value_after_useful_life = 0, + total_number_of_depreciations = 6, + number_of_depreciations_booked = 1, + frequency_of_depreciation = 10, + depreciation_start_date = '2021-01-01', + opening_accumulated_depreciation=20000, + gross_purchase_amount=120000, + submit = 1 + ) + + post_depreciation_entries(date="2021-01-01") + + self.assertEqual(asset.asset_quantity, 10) + self.assertEqual(asset.gross_purchase_amount, 120000) + self.assertEqual(asset.opening_accumulated_depreciation, 20000) + + new_asset = split_asset(asset.name, 2) + asset.load_from_db() + + self.assertEqual(new_asset.asset_quantity, 2) + self.assertEqual(new_asset.gross_purchase_amount, 24000) + self.assertEqual(new_asset.opening_accumulated_depreciation, 4000) + self.assertEqual(new_asset.split_from, asset.name) + self.assertEqual(new_asset.schedules[0].depreciation_amount, 4000) + self.assertEqual(new_asset.schedules[1].depreciation_amount, 4000) + + self.assertEqual(asset.asset_quantity, 8) + self.assertEqual(asset.gross_purchase_amount, 96000) + self.assertEqual(asset.opening_accumulated_depreciation, 16000) + self.assertEqual(asset.schedules[0].depreciation_amount, 16000) + self.assertEqual(asset.schedules[1].depreciation_amount, 16000) + + journal_entry = asset.schedules[0].journal_entry + + jv = frappe.get_doc('Journal Entry', journal_entry) + self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000) + self.assertEqual(jv.accounts[1].debit_in_account_currency, 16000) + self.assertEqual(jv.accounts[2].credit_in_account_currency, 4000) + self.assertEqual(jv.accounts[3].debit_in_account_currency, 4000) + + self.assertEqual(jv.accounts[0].reference_name, asset.name) + self.assertEqual(jv.accounts[1].reference_name, asset.name) + self.assertEqual(jv.accounts[2].reference_name, new_asset.name) + self.assertEqual(jv.accounts[3].reference_name, new_asset.name) + def test_expense_head(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location") @@ -491,10 +565,10 @@ class TestDepreciationMethods(AssetSetup): ) expected_schedules = [ - ["2030-12-31", 27534.25, 27534.25], - ["2031-12-31", 30000.0, 57534.25], - ["2032-12-31", 30000.0, 87534.25], - ["2033-01-30", 2465.75, 90000.0] + ['2030-12-31', 27616.44, 27616.44], + ['2031-12-31', 30000.0, 57616.44], + ['2032-12-31', 30000.0, 87616.44], + ['2033-01-30', 2383.56, 90000.0] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -544,10 +618,10 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 28493.15, 28493.15], - ["2031-12-31", 35753.43, 64246.58], - ["2032-12-31", 17876.71, 82123.29], - ["2033-06-06", 5376.71, 87500.0] + ['2030-12-31', 28630.14, 28630.14], + ['2031-12-31', 35684.93, 64315.07], + ['2032-12-31', 17842.47, 82157.54], + ['2033-06-06', 5342.46, 87500.0] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -580,10 +654,10 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 11780.82, 11780.82], - ["2031-12-31", 44109.59, 55890.41], - ["2032-12-31", 22054.8, 77945.21], - ["2033-07-12", 9554.79, 87500.0] + ["2030-12-31", 11849.32, 11849.32], + ["2031-12-31", 44075.34, 55924.66], + ["2032-12-31", 22037.67, 77962.33], + ["2033-07-12", 9537.67, 87500.0] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -621,7 +695,7 @@ class TestDepreciationBasics(AssetSetup): asset = create_asset( item_code = "Macbook Pro", calculate_depreciation = 1, - available_for_use_date = getdate("2019-12-31"), + available_for_use_date = getdate("2020-01-01"), total_number_of_depreciations = 3, expected_value_after_useful_life = 10000, depreciation_start_date = getdate("2020-07-01"), @@ -632,7 +706,7 @@ class TestDepreciationBasics(AssetSetup): ["2020-07-01", 15000, 15000], ["2021-07-01", 30000, 45000], ["2022-07-01", 30000, 75000], - ["2022-12-31", 15000, 90000] + ["2023-01-01", 15000, 90000] ] for i, schedule in enumerate(asset.schedules): @@ -1109,6 +1183,7 @@ class TestDepreciationBasics(AssetSetup): self.assertEqual(gle, expected_gle) self.assertEqual(asset.get("value_after_depreciation"), 0) + def test_expected_value_change(self): """ tests if changing `expected_value_after_useful_life` @@ -1130,6 +1205,15 @@ class TestDepreciationBasics(AssetSetup): asset.reload() self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0) + def test_asset_cost_center(self): + asset = create_asset(is_existing_asset = 1, do_not_save=1) + asset.cost_center = "Main - WP" + + self.assertRaises(frappe.ValidationError, asset.submit) + + asset.cost_center = "Main - _TC" + asset.submit() + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() @@ -1164,7 +1248,8 @@ def create_asset(**args): "available_for_use_date": args.available_for_use_date or "2020-06-06", "location": args.location or "Test Location", "asset_owner": args.asset_owner or "Company", - "is_existing_asset": args.is_existing_asset or 1 + "is_existing_asset": args.is_existing_asset or 1, + "asset_quantity": args.get("asset_quantity") or 1 }) if asset.calculate_depreciation: @@ -1202,13 +1287,13 @@ def create_asset_category(): }) asset_category.insert() -def create_fixed_asset_item(): +def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0): meta = frappe.get_meta('Asset') naming_series = meta.get_field("naming_series").options.splitlines()[0] or 'ACC-ASS-.YYYY.-' try: - frappe.get_doc({ + item = frappe.get_doc({ "doctype": "Item", - "item_code": "Macbook Pro", + "item_code": item_code or "Macbook Pro", "item_name": "Macbook Pro", "description": "Macbook Pro Retina Display", "asset_category": "Computers", @@ -1216,11 +1301,14 @@ def create_fixed_asset_item(): "stock_uom": "Nos", "is_stock_item": 0, "is_fixed_asset": 1, - "auto_create_assets": 1, + "auto_create_assets": auto_create_assets, + "is_grouped_asset": is_grouped_asset, "asset_naming_series": naming_series - }).insert() + }) + item.insert() except frappe.DuplicateEntryError: pass + return item def set_depreciation_settings_in_company(): company = frappe.get_doc("Company", "_Test Company") diff --git a/erpnext/assets/workspace/assets/assets.json b/erpnext/assets/workspace/assets/assets.json index 495de46e41..26a6609b31 100644 --- a/erpnext/assets/workspace/assets/assets.json +++ b/erpnext/assets/workspace/assets/assets.json @@ -5,7 +5,7 @@ "label": "Asset Value Analytics" } ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Assets\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Asset Value Analytics\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset Category\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Fixed Asset Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assets\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Assets\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Asset Value Analytics\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset Category\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Fixed Asset Register\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assets\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", "creation": "2020-03-02 15:43:27.634865", "docstatus": 0, "doctype": "Workspace", @@ -172,7 +172,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:54.839453", + "modified": "2022-01-13 17:25:41.730628", "modified_by": "Administrator", "module": "Assets", "name": "Assets", @@ -181,7 +181,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 4, + "sequence_id": 4.0, "shortcuts": [ { "label": "Asset", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py index 0163595b51..d288f881de 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py @@ -7,6 +7,7 @@ def get_data(): 'non_standard_fieldnames': { 'Journal Entry': 'reference_name', 'Payment Entry': 'reference_name', + 'Payment Request': 'reference_name', 'Auto Repeat': 'reference_document' }, 'internal_links': { @@ -21,7 +22,7 @@ def get_data(): }, { 'label': _('Payment'), - 'items': ['Payment Entry', 'Journal Entry'] + 'items': ['Payment Entry', 'Journal Entry', 'Payment Request'] }, { 'label': _('Reference'), diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json index 380ef3639f..5ad93f0e59 100644 --- a/erpnext/buying/workspace/buying/buying.json +++ b/erpnext/buying/workspace/buying/buying.json @@ -5,7 +5,7 @@ "label": "Purchase Order Trends" } ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Buying\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Purchase Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Material Request\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Buying\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items & Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier Scorecard\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Regional\", \"col\": 4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Buying\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Purchase Order Trends\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order Analysis\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Buying\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items & Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier Scorecard\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Regional\",\"col\":4}}]", "creation": "2020-01-28 11:50:26.195467", "docstatus": 0, "doctype": "Workspace", @@ -509,7 +509,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:56.218428", + "modified": "2022-01-13 17:26:39.090190", "modified_by": "Administrator", "module": "Buying", "name": "Buying", @@ -518,7 +518,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 6, + "sequence_id": 6.0, "shortcuts": [ { "color": "Green", diff --git a/erpnext/commands/__init__.py b/erpnext/commands/__init__.py index 5931119214..8e12fad3d7 100644 --- a/erpnext/commands/__init__.py +++ b/erpnext/commands/__init__.py @@ -1,49 +1,10 @@ -# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# GPL v3 License. See license.txt import click -import frappe -from frappe.commands import get_site, pass_context def call_command(cmd, context): return click.Context(cmd, obj=context).forward(cmd) -@click.command('make-demo') -@click.option('--site', help='site name') -@click.option('--domain', default='Manufacturing') -@click.option('--days', default=100, - help='Run the demo for so many days. Default 100') -@click.option('--resume', default=False, is_flag=True, - help='Continue running the demo for given days') -@click.option('--reinstall', default=False, is_flag=True, - help='Reinstall site before demo') -@pass_context -def make_demo(context, site, domain='Manufacturing', days=100, - resume=False, reinstall=False): - "Reinstall site and setup demo" - from frappe.commands.site import _reinstall - from frappe.installer import install_app - - site = get_site(context) - - if resume: - with frappe.init_site(site): - frappe.connect() - from erpnext.demo import demo - demo.simulate(days=days) - else: - if reinstall: - _reinstall(site, yes=True) - with frappe.init_site(site=site): - frappe.connect() - if not 'erpnext' in frappe.get_installed_apps(): - install_app('erpnext') - - # import needs site - from erpnext.demo import demo - demo.make(domain, days) - -commands = [ - make_demo -] +commands = [] diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c862774060..bab16a494d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -7,6 +7,7 @@ import json import frappe from frappe import _, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied +from frappe.query_builder.functions import Sum from frappe.utils import ( add_days, add_months, @@ -112,7 +113,7 @@ class AccountsController(TransactionBase): _('{0} is blocked so this transaction cannot proceed').format(supplier_name), raise_exception=1) def validate(self): - if not self.get('is_return'): + if not self.get('is_return') and not self.get('is_debit_note'): self.validate_qty_is_not_zero() if self.get("_action") and self._action != "update_after_submit": @@ -166,9 +167,14 @@ class AccountsController(TransactionBase): validate_regional(self) + validate_einvoice_fields(self) + if self.doctype != 'Material Request': apply_pricing_rule_on_transaction(self) + def before_cancel(self): + validate_einvoice_fields(self) + def on_trash(self): # delete sl and gl entries on deletion of transaction if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'): @@ -184,8 +190,6 @@ class AccountsController(TransactionBase): frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx)) elif getdate(self.posting_date) > getdate(d.service_end_date): frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx)) - elif getdate(self.posting_date) > getdate(d.service_start_date): - frappe.throw(_("Row #{0}: Service Start Date cannot be before Invoice Posting Date").format(d.idx)) def validate_invoice_documents_schedule(self): self.validate_payment_schedule_dates() @@ -1686,58 +1690,69 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, def update_invoice_status(): """Updates status as Overdue for applicable invoices. Runs daily.""" today = getdate() - + payment_schedule = frappe.qb.DocType("Payment Schedule") for doctype in ("Sales Invoice", "Purchase Invoice"): - frappe.db.sql(""" - UPDATE `tab{doctype}` invoice SET invoice.status = 'Overdue' - WHERE invoice.docstatus = 1 - AND invoice.status REGEXP '^Unpaid|^Partly Paid' - AND invoice.outstanding_amount > 0 - AND ( - {or_condition} - ( - ( - CASE - WHEN invoice.party_account_currency = invoice.currency - THEN ( - CASE - WHEN invoice.disable_rounded_total - THEN invoice.grand_total - ELSE invoice.rounded_total - END - ) - ELSE ( - CASE - WHEN invoice.disable_rounded_total - THEN invoice.base_grand_total - ELSE invoice.base_rounded_total - END - ) - END - ) - invoice.outstanding_amount - ) < ( - SELECT SUM( - CASE - WHEN invoice.party_account_currency = invoice.currency - THEN ps.payment_amount - ELSE ps.base_payment_amount - END - ) - FROM `tabPayment Schedule` ps - WHERE ps.parent = invoice.name - AND ps.due_date < %(today)s - ) - ) - """.format( - doctype=doctype, - or_condition=( - "invoice.is_pos AND invoice.due_date < %(today)s OR" - if doctype == "Sales Invoice" - else "" - ) - ), {"today": today} + invoice = frappe.qb.DocType(doctype) + + consider_base_amount = invoice.party_account_currency != invoice.currency + payment_amount = ( + frappe.qb.terms.Case() + .when(consider_base_amount, payment_schedule.base_payment_amount) + .else_(payment_schedule.payment_amount) ) + payable_amount = ( + frappe.qb.from_(payment_schedule) + .select(Sum(payment_amount)) + .where( + (payment_schedule.parent == invoice.name) + & (payment_schedule.due_date < today) + ) + ) + + total = ( + frappe.qb.terms.Case() + .when(invoice.disable_rounded_total, invoice.grand_total) + .else_(invoice.rounded_total) + ) + + base_total = ( + frappe.qb.terms.Case() + .when(invoice.disable_rounded_total, invoice.base_grand_total) + .else_(invoice.base_rounded_total) + ) + + total_amount = ( + frappe.qb.terms.Case() + .when(consider_base_amount, base_total) + .else_(total) + ) + + is_overdue = total_amount - invoice.outstanding_amount < payable_amount + + conditions = ( + (invoice.docstatus == 1) + & (invoice.outstanding_amount > 0) + & ( + invoice.status.like("Unpaid%") + | invoice.status.like("Partly Paid%") + ) + & ( + ((invoice.is_pos & invoice.due_date < today) | is_overdue) + if doctype == "Sales Invoice" + else is_overdue + ) + ) + + status = ( + frappe.qb.terms.Case() + .when(invoice.status.like("%Discounted"), "Overdue and Discounted") + .else_("Overdue") + ) + + frappe.qb.update(invoice).set("status", status).where(conditions).run() + + @frappe.whitelist() def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): if not terms_template: @@ -2107,6 +2122,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.update_status_updater() else: parent.check_credit_limit() + + # reset index of child table + for idx, row in enumerate(parent.get(child_docname), start=1): + row.idx = idx + parent.save() if parent_doctype == 'Purchase Order': @@ -2136,3 +2156,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil @erpnext.allow_regional def validate_regional(doc): pass + +@erpnext.allow_regional +def validate_einvoice_fields(doc): + pass diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index a3d2502268..a181af7313 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -70,9 +70,18 @@ class BuyingController(StockController, Subcontracting): # set contact and address details for supplier, if they are not mentioned if getattr(self, "supplier", None): - self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions, - doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'), - fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'))) + self.update_if_missing( + get_party_details( + self.supplier, + party_type="Supplier", + doctype=self.doctype, + company=self.company, + party_address=self.get("supplier_address"), + shipping_address=self.get('shipping_address'), + fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'), + ignore_permissions=self.flags.ignore_permissions + ) + ) self.set_missing_item_details(for_validate) @@ -554,10 +563,13 @@ class BuyingController(StockController, Subcontracting): # Check for asset naming series if item_data.get('asset_naming_series'): created_assets = [] - - for qty in range(cint(d.qty)): - asset = self.make_asset(d) + if item_data.get('is_grouped_asset'): + asset = self.make_asset(d, is_grouped_asset=True) created_assets.append(asset) + else: + for qty in range(cint(d.qty)): + asset = self.make_asset(d) + created_assets.append(asset) if len(created_assets) > 5: # dont show asset form links if more than 5 assets are created @@ -580,14 +592,18 @@ class BuyingController(StockController, Subcontracting): for message in messages: frappe.msgprint(message, title="Success", indicator="green") - def make_asset(self, row): + def make_asset(self, row, is_grouped_asset=False): if not row.asset_location: frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code)) item_data = frappe.db.get_value('Item', row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1) - purchase_amount = flt(row.base_rate + row.item_tax_amount) + if is_grouped_asset: + purchase_amount = flt(row.base_amount + row.item_tax_amount) + else: + purchase_amount = flt(row.base_rate + row.item_tax_amount) + asset = frappe.get_doc({ 'doctype': 'Asset', 'item_code': row.item_code, @@ -601,6 +617,7 @@ class BuyingController(StockController, Subcontracting): 'calculate_depreciation': 1, 'purchase_receipt_amount': purchase_amount, 'gross_purchase_amount': purchase_amount, + 'asset_quantity': row.qty if is_grouped_asset else 0, 'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None, 'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None }) @@ -687,7 +704,7 @@ class BuyingController(StockController, Subcontracting): def get_asset_item_details(asset_items): asset_items_data = {} - for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"], + for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series", "is_grouped_asset"], filters = {'name': ('in', asset_items)}): asset_items_data.setdefault(d.name, d) diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py index b8dc92efde..ae2c73758c 100644 --- a/erpnext/controllers/employee_boarding_controller.py +++ b/erpnext/controllers/employee_boarding_controller.py @@ -132,13 +132,17 @@ class EmployeeBoardingController(Document): def on_cancel(self): # delete task project - for task in frappe.get_all('Task', filters={'project': self.project}): + project = self.project + for task in frappe.get_all('Task', filters={'project': project}): frappe.delete_doc('Task', task.name, force=1) - frappe.delete_doc('Project', self.project, force=1) + frappe.delete_doc('Project', project, force=1) self.db_set('project', '') for activity in self.activities: activity.db_set('task', '') + frappe.msgprint(_('Linked Project {} and Tasks deleted.').format( + project), alert=True, indicator='blue') + @frappe.whitelist() def get_onboarding_details(parent, parenttype): diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index cc773b7596..75fcaee383 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -204,7 +204,7 @@ class SellingController(StockController): valuation_rate_map = {} for item in self.items: - if not item.item_code: + if not item.item_code or item.is_free_item: continue last_purchase_rate, is_stock_item = frappe.get_cached_value( @@ -251,7 +251,7 @@ class SellingController(StockController): valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate for item in self.items: - if not item.item_code: + if not item.item_code or item.is_free_item: continue last_valuation_rate = valuation_rate_map.get( @@ -385,7 +385,7 @@ class SellingController(StockController): # Get incoming rate based on original item cost based on valuation method qty = flt(d.get('stock_qty') or d.get('actual_qty')) - if not d.incoming_rate: + if not (self.get("is_return") and d.incoming_rate): d.incoming_rate = get_incoming_rate({ "item_code": d.item_code, "warehouse": d.warehouse, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 7073e32f53..2912d3eb0b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -17,7 +17,7 @@ from erpnext.accounts.general_ledger import ( from erpnext.accounts.utils import get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock import get_warehouse_account_map -from erpnext.stock.stock_ledger import get_items_to_be_repost, get_valuation_rate +from erpnext.stock.stock_ledger import get_items_to_be_repost class QualityInspectionRequiredError(frappe.ValidationError): pass @@ -77,17 +77,17 @@ class StockController(AccountsController): .format(d.idx, get_link_to_form("Batch", d.get("batch_no")))) def clean_serial_nos(self): + from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string + for row in self.get("items"): if hasattr(row, "serial_no") and row.serial_no: - # replace commas by linefeed - row.serial_no = row.serial_no.replace(",", "\n") + # remove extra whitespace and store one serial no on each line + row.serial_no = clean_serial_no_string(row.serial_no) - # strip preceeding and succeeding spaces for each SN - # (SN could have valid spaces in between e.g. SN - 123 - 2021) - serial_no_list = row.serial_no.split("\n") - serial_no_list = [sn.strip() for sn in serial_no_list] - - row.serial_no = "\n".join(serial_no_list) + for row in self.get('packed_items') or []: + if hasattr(row, "serial_no") and row.serial_no: + # remove extra whitespace and store one serial no on each line + row.serial_no = clean_serial_no_string(row.serial_no) def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): @@ -111,17 +111,6 @@ class StockController(AccountsController): self.check_expense_account(item_row) - # If the item does not have the allow zero valuation rate flag set - # and ( valuation rate not mentioned in an incoming entry - # or incoming entry not found while delivering the item), - # try to pick valuation rate from previous sle or Item master and update in SLE - # Otherwise, throw an exception - - if not sle.stock_value_difference and self.doctype != "Stock Reconciliation" \ - and not item_row.get("allow_zero_valuation_rate"): - - sle = self.update_stock_ledger_entries(sle) - # expense account/ target_warehouse / source_warehouse if item_row.get('target_warehouse'): warehouse = item_row.get('target_warehouse') @@ -164,26 +153,6 @@ class StockController(AccountsController): return frappe.flags.debit_field_precision - def update_stock_ledger_entries(self, sle): - sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, - self.doctype, self.name, currency=self.company_currency, company=self.company) - - sle.stock_value = flt(sle.qty_after_transaction) * flt(sle.valuation_rate) - sle.stock_value_difference = flt(sle.actual_qty) * flt(sle.valuation_rate) - - if sle.name: - frappe.db.sql(""" - update - `tabStock Ledger Entry` - set - stock_value = %(stock_value)s, - valuation_rate = %(valuation_rate)s, - stock_value_difference = %(stock_value_difference)s - where - name = %(name)s""", (sle)) - - return sle - def get_voucher_details(self, default_expense_account, default_cost_center, sle_map): if self.doctype == "Stock Reconciliation": reconciliation_purpose = frappe.db.get_value(self.doctype, self.name, "purpose") @@ -287,11 +256,7 @@ class StockController(AccountsController): for d in self.items: if not d.batch_no: continue - serial_nos = [sr.name for sr in frappe.get_all("Serial No", - {'batch_no': d.batch_no, 'status': 'Inactive'})] - - if serial_nos: - frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None) + frappe.db.set_value("Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None) d.batch_no = None d.db_set("batch_no", None) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 746c6fd9a4..075e3e38fa 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -139,6 +139,8 @@ class calculate_taxes_and_totals(object): if not item.qty and self.doc.get("is_return"): item.amount = flt(-1 * item.rate, item.precision("amount")) + elif not item.qty and self.doc.get("is_debit_note"): + item.amount = flt(item.rate, item.precision("amount")) else: item.amount = flt(item.rate * item.qty, item.precision("amount")) @@ -594,13 +596,14 @@ class calculate_taxes_and_totals(object): if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]: grand_total = self.doc.rounded_total or self.doc.grand_total + base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total + if self.doc.party_account_currency == self.doc.currency: total_amount_to_pay = flt(grand_total - self.doc.total_advance - flt(self.doc.write_off_amount), self.doc.precision("grand_total")) else: - total_amount_to_pay = flt(flt(grand_total * - self.doc.conversion_rate, self.doc.precision("grand_total")) - self.doc.total_advance - - flt(self.doc.base_write_off_amount), self.doc.precision("grand_total")) + total_amount_to_pay = flt(flt(base_grand_total, self.doc.precision("base_grand_total")) - self.doc.total_advance + - flt(self.doc.base_write_off_amount), self.doc.precision("base_grand_total")) self.doc.round_floats_in(self.doc, ["paid_amount"]) change_amount = 0 diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py index 05541d1688..908d78c15b 100644 --- a/erpnext/controllers/tests/test_queries.py +++ b/erpnext/controllers/tests/test_queries.py @@ -1,6 +1,8 @@ import unittest from functools import partial +import frappe + from erpnext.controllers import queries @@ -85,3 +87,6 @@ class TestQueries(unittest.TestCase): wh = query(filters=[["Bin", "item_code", "=", "_Test Item"]]) self.assertGreaterEqual(len(wh), 1) + + def test_default_uoms(self): + self.assertGreaterEqual(frappe.db.count("UOM", {"enabled": 1}), 10) diff --git a/erpnext/controllers/tests/test_transaction_base.py b/erpnext/controllers/tests/test_transaction_base.py index 13aa697610..f4d3f97ef0 100644 --- a/erpnext/controllers/tests/test_transaction_base.py +++ b/erpnext/controllers/tests/test_transaction_base.py @@ -4,19 +4,72 @@ import frappe class TestUtils(unittest.TestCase): - def test_reset_default_field_value(self): - doc = frappe.get_doc({ - "doctype": "Purchase Receipt", - "set_warehouse": "Warehouse 1", - }) + def test_reset_default_field_value(self): + doc = frappe.get_doc({ + "doctype": "Purchase Receipt", + "set_warehouse": "Warehouse 1", + }) - # Same values - doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}] - doc.reset_default_field_value("set_warehouse", "items", "warehouse") - self.assertEqual(doc.set_warehouse, "Warehouse 1") + # Same values + doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}] + doc.reset_default_field_value("set_warehouse", "items", "warehouse") + self.assertEqual(doc.set_warehouse, "Warehouse 1") - # Mixed values - doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}] - doc.reset_default_field_value("set_warehouse", "items", "warehouse") - self.assertEqual(doc.set_warehouse, None) + # Mixed values + doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}] + doc.reset_default_field_value("set_warehouse", "items", "warehouse") + self.assertEqual(doc.set_warehouse, None) + def test_reset_default_field_value_in_mfg_stock_entry(self): + # manufacture stock entry with rows having blank source/target wh + se = frappe.get_doc( + doctype="Stock Entry", + purpose="Manufacture", + stock_entry_type="Manufacture", + company="_Test Company", + from_warehouse="_Test Warehouse - _TC", + to_warehouse="_Test Warehouse 1 - _TC", + items=[ + frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"), + frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1) + ] + ) + se.save() + + # default fields must be untouched + self.assertEqual(se.from_warehouse, "_Test Warehouse - _TC") + self.assertEqual(se.to_warehouse, "_Test Warehouse 1 - _TC") + + se.delete() + + def test_reset_default_field_value_in_transfer_stock_entry(self): + doc = frappe.get_doc({ + "doctype": "Stock Entry", + "purpose": "Material Receipt", + "from_warehouse": "Warehouse 1", + "to_warehouse": "Warehouse 2", + }) + + # Same values + doc.items = [ + {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}, + {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}, + {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"} + ] + + doc.reset_default_field_value("from_warehouse", "items", "s_warehouse") + doc.reset_default_field_value("to_warehouse", "items", "t_warehouse") + self.assertEqual(doc.from_warehouse, "Warehouse 1") + self.assertEqual(doc.to_warehouse, "Warehouse 2") + + # Mixed values in source wh + doc.items = [ + {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}, + {"s_warehouse": "Warehouse 3", "t_warehouse": "Warehouse 2"}, + {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"} + ] + + doc.reset_default_field_value("from_warehouse", "items", "s_warehouse") + doc.reset_default_field_value("to_warehouse", "items", "t_warehouse") + self.assertEqual(doc.from_warehouse, None) + self.assertEqual(doc.to_warehouse, "Warehouse 2") \ No newline at end of file diff --git a/erpnext/crm/doctype/campaign/campaign.js b/erpnext/crm/doctype/campaign/campaign.js index 11bfa74b29..cac45c682c 100644 --- a/erpnext/crm/doctype/campaign/campaign.js +++ b/erpnext/crm/doctype/campaign/campaign.js @@ -5,7 +5,7 @@ frappe.ui.form.on('Campaign', { refresh: function(frm) { erpnext.toggle_naming_series(); - if (frm.doc.__islocal) { + if (frm.is_new()) { frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series"); } else { cur_frm.add_custom_button(__("View Leads"), function() { diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.json b/erpnext/crm/doctype/crm_settings/crm_settings.json index 8f0fa315c1..a2a19b9e79 100644 --- a/erpnext/crm/doctype/crm_settings/crm_settings.json +++ b/erpnext/crm/doctype/crm_settings/crm_settings.json @@ -17,7 +17,9 @@ "column_break_9", "create_event_on_next_contact_date_opportunity", "quotation_section", - "default_valid_till" + "default_valid_till", + "section_break_13", + "carry_forward_communication_and_comments" ], "fields": [ { @@ -85,13 +87,25 @@ "fieldname": "quotation_section", "fieldtype": "Section Break", "label": "Quotation" + }, + { + "fieldname": "section_break_13", + "fieldtype": "Section Break", + "label": "Other Settings" + }, + { + "default": "0", + "description": "All the Comments and Emails will be copied from one document to another newly created document(Lead -> Opportunity -> Quotation) throughout the CRM documents.", + "fieldname": "carry_forward_communication_and_comments", + "fieldtype": "Check", + "label": "Carry Forward Communication and Comments" } ], "icon": "fa fa-cog", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-11-03 10:00:36.883496", + "modified": "2021-12-20 12:51:38.894252", "modified_by": "Administrator", "module": "CRM", "name": "CRM Settings", @@ -105,6 +119,26 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Sales Master Manager", + "share": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.py b/erpnext/crm/doctype/crm_settings/crm_settings.py index bde52547c9..98cf7d845e 100644 --- a/erpnext/crm/doctype/crm_settings/crm_settings.py +++ b/erpnext/crm/doctype/crm_settings/crm_settings.py @@ -1,9 +1,10 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document class CRMSettings(Document): - pass + def validate(self): + frappe.db.set_default("campaign_naming_by", self.get("campaign_naming_by", "")) diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py index 8fd4978715..d2ac10adea 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py @@ -2,13 +2,14 @@ # For license information, please see license.txt +from urllib.parse import urlencode + import frappe import requests from frappe import _ from frappe.model.document import Document from frappe.utils import get_url_to_form from frappe.utils.file_manager import get_file_path -from six.moves.urllib.parse import urlencode class LinkedInSettings(Document): diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index f8376e6ca9..8e7d67e057 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -24,6 +24,14 @@ frappe.ui.form.on("Opportunity", { frm.trigger('set_contact_link'); } }, + + validate: function(frm) { + if (frm.doc.status == "Lost" && !frm.doc.lost_reasons.length) { + frm.trigger('set_as_lost_dialog'); + frappe.throw(__("Lost Reasons are required in case opportunity is Lost.")); + } + }, + contact_date: function(frm) { if(frm.doc.contact_date < frappe.datetime.now_datetime()){ frm.set_value("contact_date", ""); @@ -82,7 +90,7 @@ frappe.ui.form.on("Opportunity", { frm.trigger('setup_opportunity_from'); erpnext.toggle_naming_series(); - if(!doc.__islocal && doc.status!=="Lost") { + if(!frm.is_new() && doc.status!=="Lost") { if(doc.with_items){ frm.add_custom_button(__('Supplier Quotation'), function() { @@ -187,11 +195,11 @@ frappe.ui.form.on("Opportunity", { change_form_labels: function(frm) { let company_currency = erpnext.get_currency(frm.doc.company); - frm.set_currency_labels(["base_opportunity_amount", "base_total", "base_grand_total"], company_currency); - frm.set_currency_labels(["opportunity_amount", "total", "grand_total"], frm.doc.currency); + frm.set_currency_labels(["base_opportunity_amount", "base_total"], company_currency); + frm.set_currency_labels(["opportunity_amount", "total"], frm.doc.currency); // toggle fields - frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total", "base_grand_total"], + frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total"], frm.doc.currency != company_currency); }, @@ -209,20 +217,15 @@ frappe.ui.form.on("Opportunity", { }, calculate_total: function(frm) { - let total = 0, base_total = 0, grand_total = 0, base_grand_total = 0; + let total = 0, base_total = 0; frm.doc.items.forEach(item => { total += item.amount; base_total += item.base_amount; }) - base_grand_total = base_total + frm.doc.base_opportunity_amount; - grand_total = total + frm.doc.opportunity_amount; - frm.set_value({ 'total': flt(total), - 'base_total': flt(base_total), - 'grand_total': flt(grand_total), - 'base_grand_total': flt(base_grand_total) + 'base_total': flt(base_total) }); } diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index dc32d9a412..089f2d2faa 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -42,10 +42,8 @@ "items", "section_break_32", "base_total", - "base_grand_total", "column_break_33", "total", - "grand_total", "contact_info", "customer_address", "address_display", @@ -475,21 +473,6 @@ "fieldname": "column_break_33", "fieldtype": "Column Break" }, - { - "fieldname": "base_grand_total", - "fieldtype": "Currency", - "label": "Grand Total (Company Currency)", - "options": "Company:company:default_currency", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "grand_total", - "fieldtype": "Currency", - "label": "Grand Total", - "options": "currency", - "read_only": 1 - }, { "fieldname": "lost_detail_section", "fieldtype": "Section Break", @@ -510,7 +493,7 @@ "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2021-10-21 12:04:30.151379", + "modified": "2022-01-29 19:32:26.382896", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", @@ -547,6 +530,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "subject_field": "title", "timeline_field": "party_name", "title_field": "title", diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index fcbd4ded39..2d538748ec 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -11,6 +11,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.query_builder import DocType from frappe.utils import cint, cstr, flt, get_fullname +from erpnext.crm.utils import add_link_in_communication, copy_comments from erpnext.setup.utils import get_exchange_rate from erpnext.utilities.transaction_base import TransactionBase @@ -20,6 +21,11 @@ class Opportunity(TransactionBase): if self.opportunity_from == "Lead": frappe.get_doc("Lead", self.party_name).set_status(update=True) + if self.opportunity_from in ["Lead", "Prospect"]: + if frappe.db.get_single_value("CRM Settings", "carry_forward_communication_and_comments"): + copy_comments(self.opportunity_from, self.party_name, self) + add_link_in_communication(self.opportunity_from, self.party_name, self) + def validate(self): self._prev = frappe._dict({ "contact_date": frappe.db.get_value("Opportunity", self.name, "contact_date") if \ @@ -63,8 +69,6 @@ class Opportunity(TransactionBase): self.total = flt(total) self.base_total = flt(base_total) - self.grand_total = flt(self.total) + flt(self.opportunity_amount) - self.base_grand_total = flt(self.base_total) + flt(self.base_opportunity_amount) def make_new_lead_if_required(self): """Set lead against new opportunity""" diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index 6e6fed58cb..db44b6a3d5 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -4,10 +4,12 @@ import unittest import frappe -from frappe.utils import random_string, today +from frappe.utils import now_datetime, random_string, today from erpnext.crm.doctype.lead.lead import make_customer +from erpnext.crm.doctype.lead.test_lead import make_lead from erpnext.crm.doctype.opportunity.opportunity import make_quotation +from erpnext.crm.utils import get_linked_communication_list test_records = frappe.get_test_records('Opportunity') @@ -28,21 +30,11 @@ class TestOpportunity(unittest.TestCase): self.assertEqual(doc.status, "Quotation") def test_make_new_lead_if_required(self): - new_lead_email_id = "new{}@example.com".format(random_string(5)) - args = { - "doctype": "Opportunity", - "contact_email": new_lead_email_id, - "opportunity_type": "Sales", - "with_items": 0, - "transaction_date": today() - } - # new lead should be created against the new.opportunity@example.com - opp_doc = frappe.get_doc(args).insert(ignore_permissions=True) + opp_doc = make_opportunity_from_lead() self.assertTrue(opp_doc.party_name) self.assertEqual(opp_doc.opportunity_from, "Lead") - self.assertEqual(frappe.db.get_value("Lead", opp_doc.party_name, "email_id"), - new_lead_email_id) + self.assertEqual(frappe.db.get_value("Lead", opp_doc.party_name, "email_id"), opp_doc.contact_email) # create new customer and create new contact against 'new.opportunity@example.com' customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True) @@ -54,18 +46,60 @@ class TestOpportunity(unittest.TestCase): "link_name": customer.name }] }) - contact.add_email(new_lead_email_id, is_primary=True) + contact.add_email(opp_doc.contact_email, is_primary=True) contact.insert(ignore_permissions=True) - opp_doc = frappe.get_doc(args).insert(ignore_permissions=True) - self.assertTrue(opp_doc.party_name) - self.assertEqual(opp_doc.opportunity_from, "Customer") - self.assertEqual(opp_doc.party_name, customer.name) - def test_opportunity_item(self): opportunity_doc = make_opportunity(with_items=1, rate=1100, qty=2) self.assertEqual(opportunity_doc.total, 2200) + def test_carry_forward_of_email_and_comments(self): + frappe.db.set_value("CRM Settings", "CRM Settings", "carry_forward_communication_and_comments", 1) + lead_doc = make_lead() + lead_doc.add_comment('Comment', text='Test Comment 1') + lead_doc.add_comment('Comment', text='Test Comment 2') + create_communication(lead_doc.doctype, lead_doc.name, lead_doc.email_id) + create_communication(lead_doc.doctype, lead_doc.name, lead_doc.email_id) + + opp_doc = make_opportunity(opportunity_from="Lead", lead=lead_doc.name) + opportunity_comment_count = frappe.db.count("Comment", {"reference_doctype": opp_doc.doctype, "reference_name": opp_doc.name}) + opportunity_communication_count = len(get_linked_communication_list(opp_doc.doctype, opp_doc.name)) + self.assertEqual(opportunity_comment_count, 2) + self.assertEqual(opportunity_communication_count, 2) + + opp_doc.add_comment('Comment', text='Test Comment 3') + opp_doc.add_comment('Comment', text='Test Comment 4') + create_communication(opp_doc.doctype, opp_doc.name, opp_doc.contact_email) + create_communication(opp_doc.doctype, opp_doc.name, opp_doc.contact_email) + + quotation_doc = make_quotation(opp_doc.name) + quotation_doc.append('items', { + "item_code": "_Test Item", + "qty": 1 + }) + quotation_doc.run_method("set_missing_values") + quotation_doc.run_method("calculate_taxes_and_totals") + quotation_doc.save() + + quotation_comment_count = frappe.db.count("Comment", {"reference_doctype": quotation_doc.doctype, "reference_name": quotation_doc.name, "comment_type": "Comment"}) + quotation_communication_count = len(get_linked_communication_list(quotation_doc.doctype, quotation_doc.name)) + self.assertEqual(quotation_comment_count, 4) + self.assertEqual(quotation_communication_count, 4) + +def make_opportunity_from_lead(): + new_lead_email_id = "new{}@example.com".format(random_string(5)) + args = { + "doctype": "Opportunity", + "contact_email": new_lead_email_id, + "opportunity_type": "Sales", + "with_items": 0, + "transaction_date": today() + } + # new lead should be created against the new.opportunity@example.com + opp_doc = frappe.get_doc(args).insert(ignore_permissions=True) + + return opp_doc + def make_opportunity(**args): args = frappe._dict(args) @@ -95,3 +129,20 @@ def make_opportunity(**args): opp_doc.insert() return opp_doc + +def create_communication(reference_doctype, reference_name, sender, sent_or_received=None, creation=None): + communication = frappe.get_doc({ + "doctype": "Communication", + "communication_type": "Communication", + "communication_medium": "Email", + "sent_or_received": sent_or_received or "Sent", + "email_status": "Open", + "subject": "Test Subject", + "sender": sender, + "content": "Test", + "status": "Linked", + "reference_doctype": reference_doctype, + "creation": creation or now_datetime(), + "reference_name": reference_name + }) + communication.save() \ No newline at end of file diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js index 67018e1ef9..8721a5b42d 100644 --- a/erpnext/crm/doctype/prospect/prospect.js +++ b/erpnext/crm/doctype/prospect/prospect.js @@ -3,6 +3,8 @@ frappe.ui.form.on('Prospect', { refresh (frm) { + frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype }; + if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) { frm.add_custom_button(__("Customer"), function() { frappe.model.open_mapped_doc({ diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py index 367aa3d312..cc4c1d37f8 100644 --- a/erpnext/crm/doctype/prospect/prospect.py +++ b/erpnext/crm/doctype/prospect/prospect.py @@ -6,6 +6,8 @@ from frappe.contacts.address_and_contact import load_address_and_contact from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc +from erpnext.crm.utils import add_link_in_communication, copy_comments + class Prospect(Document): def onload(self): @@ -20,6 +22,12 @@ class Prospect(Document): def on_trash(self): self.unlink_dynamic_links() + def after_insert(self): + if frappe.db.get_single_value("CRM Settings", "carry_forward_communication_and_comments"): + for row in self.get('prospect_lead'): + copy_comments("Lead", row.lead, self) + add_link_in_communication("Lead", row.lead, self) + def update_lead_details(self): for row in self.get('prospect_lead'): lead = frappe.get_value('Lead', row.lead, ['lead_name', 'status', 'email_id', 'mobile_no'], as_dict=True) diff --git a/erpnext/crm/module_onboarding/crm/crm.json b/erpnext/crm/module_onboarding/crm/crm.json index 8315218c84..0faad1d315 100644 --- a/erpnext/crm/module_onboarding/crm/crm.json +++ b/erpnext/crm/module_onboarding/crm/crm.json @@ -16,7 +16,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:05:42.644448", + "modified": "2022-01-29 20:14:29.502145", "modified_by": "Administrator", "module": "CRM", "name": "CRM", @@ -33,6 +33,9 @@ }, { "step": "Create and Send Quotation" + }, + { + "step": "CRM Settings" } ], "subtitle": "Lead, Opportunity, Customer, and more.", diff --git a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json index 78f7e4de9c..f0f50de5d1 100644 --- a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json +++ b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json @@ -5,7 +5,6 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-05-28 21:07:11.461172", @@ -13,6 +12,7 @@ "name": "Create and Send Quotation", "owner": "Administrator", "reference_document": "Quotation", + "show_form_tour": 0, "show_full_form": 1, "title": "Create and Send Quotation", "validate_action": 1 diff --git a/erpnext/crm/onboarding_step/create_lead/create_lead.json b/erpnext/crm/onboarding_step/create_lead/create_lead.json index c45e8b036c..cb5cce66a5 100644 --- a/erpnext/crm/onboarding_step/create_lead/create_lead.json +++ b/erpnext/crm/onboarding_step/create_lead/create_lead.json @@ -5,7 +5,6 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-05-28 21:07:01.373403", @@ -13,6 +12,7 @@ "name": "Create Lead", "owner": "Administrator", "reference_document": "Lead", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Lead", "validate_action": 1 diff --git a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json index 0ee9317c85..96e0256d70 100644 --- a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json +++ b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json @@ -5,7 +5,6 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2021-01-21 15:28:52.483839", @@ -13,6 +12,7 @@ "name": "Create Opportunity", "owner": "Administrator", "reference_document": "Opportunity", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Opportunity", "validate_action": 1 diff --git a/erpnext/crm/onboarding_step/crm_settings/crm_settings.json b/erpnext/crm/onboarding_step/crm_settings/crm_settings.json new file mode 100644 index 0000000000..555d795987 --- /dev/null +++ b/erpnext/crm/onboarding_step/crm_settings/crm_settings.json @@ -0,0 +1,21 @@ +{ + "action": "Go to Page", + "creation": "2022-01-29 20:14:24.803844", + "description": "# CRM Settings\n\nCRM module\u2019s features are configurable as per your business needs. CRM Settings is the place where you can set your preferences for:\n- Campaign\n- Lead\n- Opportunity\n- Quotation", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 1, + "is_skipped": 0, + "modified": "2022-01-29 20:14:24.803844", + "modified_by": "Administrator", + "name": "CRM Settings", + "owner": "Administrator", + "path": "#crm-settings/CRM%20Settings", + "reference_document": "CRM Settings", + "show_form_tour": 0, + "show_full_form": 0, + "title": "CRM Settings", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json index fa26921ae2..8871753094 100644 --- a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json +++ b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json @@ -5,13 +5,13 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-05-14 17:28:16.448676", "modified_by": "Administrator", "name": "Introduction to CRM", "owner": "Administrator", + "show_form_tour": 0, "show_full_form": 0, "title": "Introduction to CRM", "validate_action": 1, diff --git a/erpnext/crm/utils.py b/erpnext/crm/utils.py index 95b19ec21e..a4576a287e 100644 --- a/erpnext/crm/utils.py +++ b/erpnext/crm/utils.py @@ -21,3 +21,30 @@ def update_lead_phone_numbers(contact, method): lead = frappe.get_doc("Lead", contact_lead) lead.db_set("phone", phone) lead.db_set("mobile_no", mobile_no) + +def copy_comments(doctype, docname, doc): + comments = frappe.db.get_values("Comment", filters={"reference_doctype": doctype, "reference_name": docname, "comment_type": "Comment"}, fieldname="*") + for comment in comments: + comment = frappe.get_doc(comment.update({"doctype":"Comment"})) + comment.name = None + comment.reference_doctype = doc.doctype + comment.reference_name = doc.name + comment.insert() + +def add_link_in_communication(doctype, docname, doc): + communication_list = get_linked_communication_list(doctype, docname) + + for communication in communication_list: + communication_doc = frappe.get_doc("Communication", communication) + communication_doc.add_link(doc.doctype, doc.name, autosave=True) + +def get_linked_communication_list(doctype, docname): + communications = frappe.get_all("Communication", filters={"reference_doctype": doctype, "reference_name": docname}, pluck='name') + communication_links = frappe.get_all('Communication Link', + { + "link_doctype": doctype, + "link_name": docname, + "parent": ("not in", communications) + }, pluck="parent") + + return communications + communication_links diff --git a/erpnext/crm/workspace/crm/crm.json b/erpnext/crm/workspace/crm/crm.json index 5a63dc18d0..83341f5a21 100644 --- a/erpnext/crm/workspace/crm/crm.json +++ b/erpnext/crm/workspace/crm/crm.json @@ -1,10 +1,11 @@ { "charts": [ { - "chart_name": "Territory Wise Sales" + "chart_name": "Territory Wise Sales", + "label": "Territory Wise Sales" } ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"CRM\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Lead\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Opportunity\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customer\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Sales Pipeline\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Campaign\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"CRM\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Territory Wise Sales\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Lead\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Opportunity\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Sales Pipeline\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Campaign\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", "creation": "2020-01-23 14:48:30.183272", "docstatus": 0, "doctype": "Workspace", @@ -144,6 +145,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Pipeline Analytics", + "link_count": 0, "link_to": "Sales Pipeline Analytics", "link_type": "Report", "onboard": 0, @@ -153,6 +155,7 @@ "hidden": 0, "is_query_report": 1, "label": "Opportunity Summary by Sales Stage", + "link_count": 0, "link_to": "Opportunity Summary by Sales Stage", "link_type": "Report", "onboard": 0, @@ -414,7 +417,7 @@ "type": "Link" } ], - "modified": "2021-08-20 12:15:56.913092", + "modified": "2022-01-13 17:53:17.509844", "modified_by": "Administrator", "module": "CRM", "name": "CRM", @@ -423,7 +426,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 7, + "sequence_id": 7.0, "shortcuts": [ { "color": "Blue", diff --git a/erpnext/demo/__init__.py b/erpnext/demo/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/demo/data/account.json b/erpnext/demo/data/account.json deleted file mode 100644 index b50b0c94b0..0000000000 --- a/erpnext/demo/data/account.json +++ /dev/null @@ -1,18 +0,0 @@ -[{ - "account_name": "Debtors EUR", - "parent_account": "Accounts Receivable", - "account_type": "Receivable", - "account_currency": "EUR" -}, -{ - "account_name": "Creditors EUR", - "parent_account": "Accounts Payable", - "account_type": "Payable", - "account_currency": "EUR" -}, -{ - "account_name": "Paypal", - "parent_account": "Bank Accounts", - "account_type": "Bank", - "account_currency": "EUR" -}] \ No newline at end of file diff --git a/erpnext/demo/data/address.json b/erpnext/demo/data/address.json deleted file mode 100644 index 7618c2cf33..0000000000 --- a/erpnext/demo/data/address.json +++ /dev/null @@ -1,218 +0,0 @@ -[ - { - "address_line1": "254 Theotokopoulou Str.", - "address_type": "Office", - "city": "Larnaka", - "country": "Cyprus", - "links": [{"link_doctype": "Customer", "link_name": "Adaptas"}], - "phone": "23566775757" - }, - { - "address_line1": "R Patr\u00e3o Caramelho 116", - "address_type": "Office", - "city": "Fajozes", - "country": "Portugal", - "links": [{"link_doctype": "Customer", "link_name": "Asian Fusion"}], - "phone": "23566775757" - }, - { - "address_line1": "30 Fulford Road", - "address_type": "Office", - "city": "PENTRE-PIOD", - "country": "United Kingdom", - "links": [{"link_doctype": "Customer", "link_name": "Asian Junction"}], - "phone": "23566775757" - }, - { - "address_line1": "Schoenebergerstrasse 13", - "address_type": "Office", - "city": "Raschau", - "country": "Germany", - "links": [{"link_doctype": "Customer", "link_name": "Big D Supermarkets"}], - "phone": "23566775757" - }, - { - "address_line1": "Hoheluftchaussee 43", - "address_type": "Office", - "city": "Kieritzsch", - "country": "Germany", - "links": [{"link_doctype": "Customer", "link_name": "Buttrey Food & Drug"}], - "phone": "23566775757" - }, - { - "address_line1": "R Cimo Vila 6", - "address_type": "Office", - "city": "Rebordosa", - "country": "Portugal", - "links": [{"link_doctype": "Customer", "link_name": "Chi-Chis"}], - "phone": "23566775757" - }, - { - "address_line1": "R 5 Outubro 9", - "address_type": "Office", - "city": "Quinta Nova S\u00e3o Domingos", - "country": "Portugal", - "links": [{"link_doctype": "Customer", "link_name": "Choices"}], - "phone": "23566775757" - }, - { - "address_line1": "Avenida Macambira 953", - "address_type": "Office", - "city": "Goi\u00e2nia", - "country": "Brazil", - "links": [{"link_doctype": "Customer", "link_name": "Consumers and Consumers Express"}], - "phone": "23566775757" - }, - { - "address_line1": "2342 Goyeau Ave", - "address_type": "Office", - "city": "Windsor", - "country": "Canada", - "links": [{"link_doctype": "Customer", "link_name": "Crafts Canada"}], - "phone": "23566775757" - }, - { - "address_line1": "Laukaantie 82", - "address_type": "Office", - "city": "KOKKOLA", - "country": "Finland", - "links": [{"link_doctype": "Customer", "link_name": "Endicott Shoes"}], - "phone": "23566775757" - }, - { - "address_line1": "9 Brown Street", - "address_type": "Office", - "city": "PETERSHAM", - "country": "Australia", - "links": [{"link_doctype": "Customer", "link_name": "Fayva"}], - "phone": "23566775757" - }, - { - "address_line1": "Via Donnalbina 41", - "address_type": "Office", - "city": "Cala Gonone", - "country": "Italy", - "links": [{"link_doctype": "Customer", "link_name": "Intelacard"}], - "phone": "23566775757" - }, - { - "address_line1": "Liljerum Grenadj\u00e4rtorpet 69", - "address_type": "Office", - "city": "TOMTEBODA", - "country": "Sweden", - "links": [{"link_doctype": "Customer", "link_name": "Landskip Yard Care"}], - "phone": "23566775757" - }, - { - "address_line1": "72 Bishopgate Street", - "address_type": "Office", - "city": "SEAHAM", - "country": "United Kingdom", - "links": [{"link_doctype": "Customer", "link_name": "Life Plan Counselling"}], - "phone": "23566775757" - }, - { - "address_line1": "\u03a3\u03ba\u03b1\u03c6\u03af\u03b4\u03b9\u03b1 105", - "address_type": "Office", - "city": "\u03a0\u0391\u03a1\u0395\u039a\u039a\u039b\u0397\u03a3\u0399\u0391", - "country": "Cyprus", - "links": [{"link_doctype": "Customer", "link_name": "Mr Fables"}], - "phone": "23566775757" - }, - { - "address_line1": "Mellemvej 7", - "address_type": "Office", - "city": "Aabybro", - "country": "Denmark", - "links": [{"link_doctype": "Customer", "link_name": "Nelson Brothers"}], - "phone": "23566775757" - }, - { - "address_line1": "Plougg\u00e5rdsvej 98", - "address_type": "Office", - "city": "Karby", - "country": "Denmark", - "links": [{"link_doctype": "Customer", "link_name": "Netobill"}], - "phone": "23566775757" - }, - { - "address_line1": "176 Michalakopoulou Street", - "address_type": "Office", - "city": "Agio Georgoudi", - "country": "Cyprus", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "Helios Air"}] - }, - { - "address_line1": "Fibichova 1102", - "address_type": "Office", - "city": "Kokor\u00edn", - "country": "Czech Republic", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "Ks Merchandise"}] - }, - { - "address_line1": "Zahradn\u00ed 888", - "address_type": "Office", - "city": "Cecht\u00edn", - "country": "Czech Republic", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "HomeBase"}] - }, - { - "address_line1": "ul. Grochowska 94", - "address_type": "Office", - "city": "Warszawa", - "country": "Poland", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "Scott Ties"}] - }, - { - "address_line1": "Norra Esplanaden 87", - "address_type": "Office", - "city": "HELSINKI", - "country": "Finland", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "Reliable Investments"}] - }, - { - "address_line1": "2038 Fallon Drive", - "address_type": "Office", - "city": "Dresden", - "country": "Canada", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "Nan Duskin"}] - }, - { - "address_line1": "77 cours Franklin Roosevelt", - "address_type": "Office", - "city": "MARSEILLE", - "country": "France", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "Rainbow Records"}] - }, - { - "address_line1": "ul. Tuwima Juliana 85", - "address_type": "Office", - "city": "\u0141\u00f3d\u017a", - "country": "Poland", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "New World Realty"}] - }, - { - "address_line1": "Gl. Sygehusvej 41", - "address_type": "Office", - "city": "Narsaq", - "country": "Greenland", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "Asiatic Solutions"}] - }, - { - "address_line1": "Gosposka ulica 50", - "address_type": "Office", - "city": "Nova Gorica", - "country": "Slovenia", - "phone": "23566775757", - "links": [{"link_doctype": "Supplier", "link_name": "Eagle Hardware"}] - } -] \ No newline at end of file diff --git a/erpnext/demo/data/assessment_criteria.json b/erpnext/demo/data/assessment_criteria.json deleted file mode 100644 index 82956822a2..0000000000 --- a/erpnext/demo/data/assessment_criteria.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "doctype": "Assessment Criteria", - "assessment_criteria": "Aptitude" - }, - { - "doctype": "Assessment Criteria", - "assessment_criteria": "Application" - }, - { - "doctype": "Assessment Criteria", - "assessment_criteria": "Understanding" - }, - { - "doctype": "Assessment Criteria", - "assessment_criteria": "Knowledge" - } -] \ No newline at end of file diff --git a/erpnext/demo/data/asset.json b/erpnext/demo/data/asset.json deleted file mode 100644 index 44db2ae9e1..0000000000 --- a/erpnext/demo/data/asset.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "asset_name": "Macbook Pro - 1", - "item_code": "Computer", - "gross_purchase_amount": 100000, - "asset_owner": "Company", - "available_for_use_date": "2017-01-02", - "location": "Main Location" - }, - { - "asset_name": "Macbook Air - 1", - "item_code": "Computer", - "gross_purchase_amount": 60000, - "asset_owner": "Company", - "available_for_use_date": "2017-10-02", - "location": "Avg Location" - }, - { - "asset_name": "Conferrence Table", - "item_code": "Table", - "gross_purchase_amount": 30000, - "asset_owner": "Company", - "available_for_use_date": "2018-10-02", - "location": "Zany Location" - }, - { - "asset_name": "Lunch Table", - "item_code": "Table", - "gross_purchase_amount": 20000, - "asset_owner": "Company", - "available_for_use_date": "2018-06-02", - "location": "Fletcher Location" - }, - { - "asset_name": "ERPNext", - "item_code": "ERP", - "gross_purchase_amount": 100000, - "asset_owner": "Company", - "available_for_use_date": "2018-09-02", - "location":"Main Location" - }, - { - "asset_name": "Chair 1", - "item_code": "Chair", - "gross_purchase_amount": 10000, - "asset_owner": "Company", - "available_for_use_date": "2018-07-02", - "location": "Zany Location" - }, - { - "asset_name": "Chair 2", - "item_code": "Chair", - "gross_purchase_amount": 10000, - "asset_owner": "Company", - "available_for_use_date": "2018-07-02", - "location": "Avg Location" - } -] diff --git a/erpnext/demo/data/asset_category.json b/erpnext/demo/data/asset_category.json deleted file mode 100644 index 54f779da96..0000000000 --- a/erpnext/demo/data/asset_category.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "asset_category_name": "Furnitures", - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 5, - "frequency_of_depreciation": 12, - "accounts": [{ - "company_name": "Wind Power LLC", - "fixed_asset_account": "Furnitures and Fixtures - WPL", - "accumulated_depreciation_account": "Accumulated Depreciation - WPL", - "depreciation_expense_account": "Depreciation - WPL" - }] - }, - { - "asset_category_name": "Electronic Equipments", - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 10, - "frequency_of_depreciation": 6, - "accounts": [{ - "company_name": "Wind Power LLC", - "fixed_asset_account": "Electronic Equipments - WPL", - "accumulated_depreciation_account": "Accumulated Depreciation - WPL", - "depreciation_expense_account": "Depreciation - WPL" - }] - }, - { - "asset_category_name": "Softwares", - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 10, - "frequency_of_depreciation": 12, - "accounts": [{ - "company_name": "Wind Power LLC", - "fixed_asset_account": "Softwares - WPL", - "accumulated_depreciation_account": "Accumulated Depreciation - WPL", - "depreciation_expense_account": "Depreciation - WPL" - }] - } -] \ No newline at end of file diff --git a/erpnext/demo/data/bom.json b/erpnext/demo/data/bom.json deleted file mode 100644 index 30854359b2..0000000000 --- a/erpnext/demo/data/bom.json +++ /dev/null @@ -1,180 +0,0 @@ -[ - { - "item": "Bearing Assembly", - "items": [ - { - "item_code": "Base Bearing Plate", - "qty": 1.0, - "rate": 15.0 - }, - { - "item_code": "Bearing Block", - "qty": 1.0, - "rate": 10.0 - }, - { - "item_code": "Bearing Collar", - "qty": 2.0, - "rate": 20.0 - }, - { - "item_code": "Bearing Pipe", - "qty": 1.0, - "rate": 15.0 - }, - { - "item_code": "Upper Bearing Plate", - "qty": 1.0, - "rate": 50.0 - } - ] - }, - { - "item": "Wind Mill A Series", - "items": [ - { - "item_code": "Base Bearing Plate", - "qty": 1.0, - "rate": 15.0 - }, - { - "item_code": "Base Plate", - "qty": 1.0, - "rate": 20.0 - }, - { - "item_code": "Bearing Block", - "qty": 1.0, - "rate": 10.0 - }, - { - "item_code": "Bearing Pipe", - "qty": 1.0, - "rate": 15.0 - }, - { - "item_code": "External Disc", - "qty": 1.0, - "rate": 45.0 - }, - { - "item_code": "Shaft", - "qty": 1.0, - "rate": 30.0 - }, - { - "item_code": "Wing Sheet", - "qty": 4.0, - "rate": 22.0 - } - ] - }, - { - "item": "Wind MIll C Series", - "items": [ - { - "item_code": "Base Plate", - "qty": 2.0, - "rate": 20.0 - }, - { - "item_code": "Internal Disc", - "qty": 1.0, - "rate": 33.0 - }, - { - "item_code": "External Disc", - "qty": 1.0, - "rate": 45.0 - }, - { - "item_code": "Bearing Assembly", - "qty": 1.0, - "rate": 130.0 - }, - { - "item_code": "Wing Sheet", - "qty": 3.0, - "rate": 22.0 - } - ] - }, - { - "item": "Wind Turbine-S", - "with_operations": 1, - "operations": [ - { - "operation": "Prepare Frame", - "time_in_mins": 30.0, - "workstation": "Drilling Machine 1" - }, - { - "operation": "Setup Fixtures", - "time_in_mins": 15.0, - "workstation": "Assembly Station 1" - }, - { - "operation": "Assembly Operation", - "time_in_mins": 30.0, - "workstation": "Assembly Station 1" - }, - { - "operation": "Wiring", - "time_in_mins": 20.0, - "workstation": "Assembly Station 1" - }, - { - "operation": "Testing", - "time_in_mins": 10.0, - "workstation": "Packing and Testing Station" - }, - { - "operation": "Packing", - "time_in_mins": 25.0, - "workstation": "Packing and Testing Station" - } - ], - "items": [ - { - "item_code": "Base Bearing Plate", - "qty": 1.0, - "rate": 15.0 - }, - { - "item_code": "Base Plate", - "qty": 1.0, - "rate": 20.0 - }, - { - "item_code": "Bearing Collar", - "qty": 1.0, - "rate": 20.0 - }, - { - "item_code": "Blade Rib", - "qty": 1.0, - "rate": 10.0 - }, - { - "item_code": "Shaft", - "qty": 1.0, - "rate": 30.0 - }, - { - "item_code": "Wing Sheet", - "qty": 2.0, - "rate": 22.0 - } - ] - }, - { - "item": "Base Plate", - "items": [ - { - "item_code": "Base Plate Un Painted", - "qty": 1.0, - "rate": 16.0 - } - ] - } -] \ No newline at end of file diff --git a/erpnext/demo/data/contact.json b/erpnext/demo/data/contact.json deleted file mode 100644 index 113b561ce5..0000000000 --- a/erpnext/demo/data/contact.json +++ /dev/null @@ -1,164 +0,0 @@ -[ - { - "email_id": "JanVaclavik@example.com", - "first_name": "January", - "last_name": "V\u00e1clav\u00edk", - "links": [{"link_doctype": "Customer", "link_name": "Adaptas"}] - }, - { - "email_id": "ChidumagaTobeolisa@example.com", - "first_name": "Chidumaga", - "last_name": "Tobeolisa", - "links": [{"link_doctype": "Customer", "link_name": "Asian Fusion"}] - }, - { - "email_id": "JanaKubanova@example.com", - "first_name": "Jana", - "last_name": "Kub\u00e1\u0148ov\u00e1", - "links": [{"link_doctype": "Customer", "link_name": "Asian Junction"}] - }, - { - "email_id": "XuChaoXuan@example.com", - "first_name": "\u7d39\u8431", - "last_name": "\u4e8e", - "links": [{"link_doctype": "Customer", "link_name": "Big D Supermarkets"}] - }, - { - "email_id": "OzlemVerwijmeren@example.com", - "first_name": "\u00d6zlem", - "last_name": "Verwijmeren", - "links": [{"link_doctype": "Customer", "link_name": "Buttrey Food & Drug"}] - }, - { - "email_id": "HansRasmussen@example.com", - "first_name": "Hans", - "last_name": "Rasmussen", - "links": [{"link_doctype": "Customer", "link_name": "Chi-Chis"}] - }, - { - "email_id": "SatomiShigeki@example.com", - "first_name": "Satomi", - "last_name": "Shigeki", - "links": [{"link_doctype": "Customer", "link_name": "Choices"}] - }, - { - "email_id": "SimonVJessen@example.com", - "first_name": "Simon", - "last_name": "Jessen", - "links": [{"link_doctype": "Customer", "link_name": "Consumers and Consumers Express"}] - }, - { - "email_id": "NeguaranShahsaah@example.com", - "first_name": "\u0646\u06af\u0627\u0631\u06cc\u0646", - "last_name": "\u0634\u0627\u0647 \u0633\u06cc\u0627\u0647", - "links": [{"link_doctype": "Customer", "link_name": "Crafts Canada"}] - }, - { - "email_id": "Lom-AliBataev@example.com", - "first_name": "Lom-Ali", - "last_name": "Bataev", - "links": [{"link_doctype": "Customer", "link_name": "Endicott Shoes"}] - }, - { - "email_id": "VanNgocTien@example.com", - "first_name": "Ti\u00ean", - "last_name": "V\u0103n", - "links": [{"link_doctype": "Customer", "link_name": "Fayva"}] - }, - { - "email_id": "QuimeyOsorioRuelas@example.com", - "first_name": "Quimey", - "last_name": "Osorio", - "links": [{"link_doctype": "Customer", "link_name": "Intelacard"}] - }, - { - "email_id": "EdgardaSalcedoRaya@example.com", - "first_name": "Edgarda", - "last_name": "Salcedo", - "links": [{"link_doctype": "Customer", "link_name": "Landskip Yard Care"}] - }, - { - "email_id": "HafsteinnBjarnarsonar@example.com", - "first_name": "Hafsteinn", - "last_name": "Bjarnarsonar", - "links": [{"link_doctype": "Customer", "link_name": "Life Plan Counselling"}] - }, - { - "email_id": "\u0434\u0430\u043d\u0438\u0438\u043b@example.com", - "first_name": "\u0414\u0430\u043d\u0438\u0438\u043b", - "last_name": "\u041a\u043e\u043d\u043e\u0432\u0430\u043b\u043e\u0432", - "links": [{"link_doctype": "Customer", "link_name": "Mr Fables"}] - }, - { - "email_id": "SelmaMAndersen@example.com", - "first_name": "Selma", - "last_name": "Andersen", - "links": [{"link_doctype": "Customer", "link_name": "Nelson Brothers"}] - }, - { - "email_id": "LadislavKolaja@example.com", - "first_name": "Ladislav", - "last_name": "Kolaja", - "links": [{"link_doctype": "Customer", "link_name": "Netobill"}] - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "Helios Air"}], - "email_id": "TewoldeAbaalom@example.com", - "first_name": "Tewolde", - "last_name": "Abaalom" - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "Ks Merchandise"}], - "email_id": "LeilaFernandesRodrigues@example.com", - "first_name": "Leila", - "last_name": "Rodrigues" - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "HomeBase"}], - "email_id": "DmitryBulgakov@example.com", - "first_name": "Dmitry", - "last_name": "Bulgakov" - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "Scott Ties"}], - "email_id": "HaiducWhitfoot@example.com", - "first_name": "Haiduc", - "last_name": "Whitfoot" - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "Reliable Investments"}], - "email_id": "SesseljaPetursdottir@example.com", - "first_name": "Sesselja", - "last_name": "P\u00e9tursd\u00f3ttir" - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "Nan Duskin"}], - "email_id": "HajdarPignar@example.com", - "first_name": "Hajdar", - "last_name": "Pignar" - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "Rainbow Records"}], - "email_id": "GustavaLorenzo@example.com", - "first_name": "Gustava", - "last_name": "Lorenzo" - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "New World Realty"}], - "email_id": "BethanyWood@example.com", - "first_name": "Bethany", - "last_name": "Wood" - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "Asiatic Solutions"}], - "email_id": "GlorianaBrownlock@example.com", - "first_name": "Gloriana", - "last_name": "Brownlock" - }, - { - "links": [{"link_doctype": "Supplier", "link_name": "Eagle Hardware"}], - "email_id": "JensonFraser@gustr.com", - "first_name": "Jenson", - "last_name": "Fraser" - } -] \ No newline at end of file diff --git a/erpnext/demo/data/course.json b/erpnext/demo/data/course.json deleted file mode 100644 index 15728d51d3..0000000000 --- a/erpnext/demo/data/course.json +++ /dev/null @@ -1,134 +0,0 @@ -[ - { - "doctype": "Course", - "course_name": "Communication Skiils", - "course_code": "BCA2040", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Object Oriented Programing - C++", - "course_code": "BCA2030", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Data Structures and Algorithm", - "course_code": "BCA2020", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Operating System", - "course_code": "BCA2010", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Digital Logic", - "course_code": "BCA1040", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Basic Mathematics", - "course_code": "BCA1030", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Programing in C", - "course_code": "BCA1020", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Fundamentals of IT & Programing", - "course_code": "BCA1010", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Microprocessor", - "course_code": "MCA4010", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Probability and Statistics", - "course_code": "MCA4020", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Programing in Java", - "course_code": "MCA4030", - "department": "Information Technology" - }, - { - "doctype": "Course", - "course_name": "Communication Skills", - "course_code": "BBA 101", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Organizational Behavior", - "course_code": "BBA 102", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Business Environment", - "course_code": "BBA 103", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Legal and Regulatory Framework", - "course_code": "BBA 301", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Human Resource Management", - "course_code": "BBA 302", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Advertising and Sales", - "course_code": "BBA 304", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Entrepreneurship Management", - "course_code": "BBA 505", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Visual Merchandising", - "course_code": "BBR 504", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Warehouse Management", - "course_code": "BBR 505", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Store Operations and Job Knowledge", - "course_code": "BBR 501", - "department": "Management Studies" - }, - { - "doctype": "Course", - "course_name": "Management Development and Skills", - "course_code": "BBA 602", - "department": "Management Studies" - } -] diff --git a/erpnext/demo/data/department.json b/erpnext/demo/data/department.json deleted file mode 100644 index f4355ba1e7..0000000000 --- a/erpnext/demo/data/department.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "doctype": "Department", - "department_name": "Information Technology" - }, - { - "doctype": "Department", - "department_name": "Physics" - }, - { - "doctype": "Department", - "department_name": "Chemistry" - }, - { - "doctype": "Department", - "department_name": "Biology" - }, - { - "doctype": "Department", - "department_name": "Commerce" - }, - { - "doctype": "Department", - "department_name": "English" - }, - { - "doctype": "Department", - "department_name": "Management Studies" - } -] \ No newline at end of file diff --git a/erpnext/demo/data/drug_list.json b/erpnext/demo/data/drug_list.json deleted file mode 100644 index 3069042843..0000000000 --- a/erpnext/demo/data/drug_list.json +++ /dev/null @@ -1,5111 +0,0 @@ -[ - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Atocopherol", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Atocopherol", - "item_group": "Drug", - "item_name": "Atocopherol", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - - - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:16.577151", - "name": "Atocopherol", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Abacavir", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Abacavir", - "item_group": "Drug", - "item_name": "Abacavir", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - - - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:16.678257", - "name": "Abacavir", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Abciximab", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Abciximab", - "item_group": "Drug", - "item_name": "Abciximab", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:16.695413", - "name": "Abciximab", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Acacia", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Acacia", - "item_group": "Drug", - "item_name": "Acacia", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:16.797774", - "name": "Acacia", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Acamprosate", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Acamprosate", - "item_group": "Drug", - "item_name": "Acamprosate", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:16.826952", - "name": "Acamprosate", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Acarbose", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Acarbose", - "item_group": "Drug", - "item_name": "Acarbose", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:16.843890", - "name": "Acarbose", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Acebrofylline", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Acebrofylline", - "item_group": "Drug", - "item_name": "Acebrofylline", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:16.969984", - "name": "Acebrofylline", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Acebrofylline (SR)", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Acebrofylline (SR)", - "item_group": "Drug", - "item_name": "Acebrofylline (SR)", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:16.987354", - "name": "Acebrofylline (SR)", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Aceclofenac", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Aceclofenac", - "item_group": "Drug", - "item_name": "Aceclofenac", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.004369", - "name": "Aceclofenac", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Ash", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Ash", - "item_group": "Drug", - "item_name": "Ash", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.021192", - "name": "Ash", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Asparaginase", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Asparaginase", - "item_group": "Drug", - "item_name": "Asparaginase", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.038058", - "name": "Asparaginase", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Aspartame", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Aspartame", - "item_group": "Drug", - "item_name": "Aspartame", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.054463", - "name": "Aspartame", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Aspartic Acid", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Aspartic Acid", - "item_group": "Drug", - "item_name": "Aspartic Acid", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.071001", - "name": "Aspartic Acid", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Bleomycin", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Bleomycin", - "item_group": "Drug", - "item_name": "Bleomycin", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.087170", - "name": "Bleomycin", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Bleomycin Sulphate", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Bleomycin Sulphate", - "item_group": "Drug", - "item_name": "Bleomycin Sulphate", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.103691", - "name": "Bleomycin Sulphate", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Blue cap contains", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Blue cap contains", - "item_group": "Drug", - "item_name": "Blue cap contains", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.120040", - "name": "Blue cap contains", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Boran", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Boran", - "item_group": "Drug", - "item_name": "Boran", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.135964", - "name": "Boran", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Borax", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Borax", - "item_group": "Drug", - "item_name": "Borax", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.152575", - "name": "Borax", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlorbutanol", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlorbutanol", - "item_group": "Drug", - "item_name": "Chlorbutanol", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.168998", - "name": "Chlorbutanol", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlorbutol", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlorbutol", - "item_group": "Drug", - "item_name": "Chlorbutol", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.185316", - "name": "Chlorbutol", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlordiazepoxide", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlordiazepoxide", - "item_group": "Drug", - "item_name": "Chlordiazepoxide", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.208361", - "name": "Chlordiazepoxide", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlordiazepoxide and Clidinium Bromide", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlordiazepoxide and Clidinium Bromide", - "item_group": "Drug", - "item_name": "Chlordiazepoxide and Clidinium Bromide", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.224341", - "name": "Chlordiazepoxide and Clidinium Bromide", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlorhexidine", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlorhexidine", - "item_group": "Drug", - "item_name": "Chlorhexidine", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.240634", - "name": "Chlorhexidine", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlorhexidine 40%", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlorhexidine 40%", - "item_group": "Drug", - "item_name": "Chlorhexidine 40%", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.256922", - "name": "Chlorhexidine 40%", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlorhexidine Acetate", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlorhexidine Acetate", - "item_group": "Drug", - "item_name": "Chlorhexidine Acetate", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.274789", - "name": "Chlorhexidine Acetate", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlorhexidine Gluconate", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlorhexidine Gluconate", - "item_group": "Drug", - "item_name": "Chlorhexidine Gluconate", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.295371", - "name": "Chlorhexidine Gluconate", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlorhexidine HCL", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlorhexidine HCL", - "item_group": "Drug", - "item_name": "Chlorhexidine HCL", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.312916", - "name": "Chlorhexidine HCL", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chlorhexidine Hydrochloride", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chlorhexidine Hydrochloride", - "item_group": "Drug", - "item_name": "Chlorhexidine Hydrochloride", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.329570", - "name": "Chlorhexidine Hydrochloride", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Chloride", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Chloride", - "item_group": "Drug", - "item_name": "Chloride", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.346088", - "name": "Chloride", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Fosfomycin Tromethamine", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Fosfomycin Tromethamine", - "item_group": "Drug", - "item_name": "Fosfomycin Tromethamine", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.362777", - "name": "Fosfomycin Tromethamine", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Fosinopril", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Fosinopril", - "item_group": "Drug", - "item_name": "Fosinopril", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.379465", - "name": "Fosinopril", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Iodochlorhydroxyquinoline", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Iodochlorhydroxyquinoline", - "item_group": "Drug", - "item_name": "Iodochlorhydroxyquinoline", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.396068", - "name": "Iodochlorhydroxyquinoline", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Iodochlorohydroxyquinoline", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Iodochlorohydroxyquinoline", - "item_group": "Drug", - "item_name": "Iodochlorohydroxyquinoline", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.412734", - "name": "Iodochlorohydroxyquinoline", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Ipratropium", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Ipratropium", - "item_group": "Drug", - "item_name": "Ipratropium", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.429333", - "name": "Ipratropium", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Mebeverine hydrochloride", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Mebeverine hydrochloride", - "item_group": "Drug", - "item_name": "Mebeverine hydrochloride", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.445814", - "name": "Mebeverine hydrochloride", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Mecetronium ethylsulphate", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Mecetronium ethylsulphate", - "item_group": "Drug", - "item_name": "Mecetronium ethylsulphate", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.461696", - "name": "Mecetronium ethylsulphate", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Meclizine", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Meclizine", - "item_group": "Drug", - "item_name": "Meclizine", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.478020", - "name": "Meclizine", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Oxaprozin", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Oxaprozin", - "item_group": "Drug", - "item_name": "Oxaprozin", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - - - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.496221", - "name": "Oxaprozin", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Oxazepam", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Oxazepam", - "item_group": "Drug", - "item_name": "Oxazepam", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.511933", - "name": "Oxazepam", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Oxcarbazepine", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Oxcarbazepine", - "item_group": "Drug", - "item_name": "Oxcarbazepine", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.528472", - "name": "Oxcarbazepine", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Oxetacaine", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Oxetacaine", - "item_group": "Drug", - "item_name": "Oxetacaine", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.544177", - "name": "Oxetacaine", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Oxethazaine", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Oxethazaine", - "item_group": "Drug", - "item_name": "Oxethazaine", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.560193", - "name": "Oxethazaine", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Suxamethonium Chloride", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Suxamethonium Chloride", - "item_group": "Drug", - "item_name": "Suxamethonium Chloride", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.576447", - "name": "Suxamethonium Chloride", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Tacrolimus", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Tacrolimus", - "item_group": "Drug", - "item_name": "Tacrolimus", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.593481", - "name": "Tacrolimus", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Ubiquinol", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Ubiquinol", - "item_group": "Drug", - "item_name": "Ubiquinol", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.609930", - "name": "Ubiquinol", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Vitamin B12", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Vitamin B12", - "item_group": "Drug", - "item_name": "Vitamin B12", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.626225", - "name": "Vitamin B12", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Vitamin B1Hydrochloride", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Vitamin B1Hydrochloride", - "item_group": "Drug", - "item_name": "Vitamin B1Hydrochloride", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.642423", - "name": "Vitamin B1Hydrochloride", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Vitamin B1Monohydrate", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Vitamin B1Monohydrate", - "item_group": "Drug", - "item_name": "Vitamin B1Monohydrate", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.658946", - "name": "Vitamin B1Monohydrate", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Vitamin B2", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Vitamin B2", - "item_group": "Drug", - "item_name": "Vitamin B2", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.675234", - "name": "Vitamin B2", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Vitamin B3", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Vitamin B3", - "item_group": "Drug", - "item_name": "Vitamin B3", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.691598", - "name": "Vitamin B3", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Vitamin D4", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Vitamin D4", - "item_group": "Drug", - "item_name": "Vitamin D4", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.707840", - "name": "Vitamin D4", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Vitamin E", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Vitamin E", - "item_group": "Drug", - "item_name": "Vitamin E", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.723859", - "name": "Vitamin E", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Wheat Germ Oil", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Wheat Germ Oil", - "item_group": "Drug", - "item_name": "Wheat Germ Oil", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.739829", - "name": "Wheat Germ Oil", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Wheatgrass extr", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Wheatgrass extr", - "item_group": "Drug", - "item_name": "Wheatgrass extr", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.757695", - "name": "Wheatgrass extr", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Whey Protein", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Whey Protein", - "item_group": "Drug", - "item_name": "Whey Protein", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.774098", - "name": "Whey Protein", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Xylometazoline", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Xylometazoline", - "item_group": "Drug", - "item_name": "Xylometazoline", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.790224", - "name": "Xylometazoline", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Xylometazoline Hydrochloride", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Xylometazoline Hydrochloride", - "item_group": "Drug", - "item_name": "Xylometazoline Hydrochloride", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.806359", - "name": "Xylometazoline Hydrochloride", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Yeast", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Yeast", - "item_group": "Drug", - "item_name": "Yeast", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.823305", - "name": "Yeast", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Yellow Fever Vaccine", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Yellow Fever Vaccine", - "item_group": "Drug", - "item_name": "Yellow Fever Vaccine", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.840250", - "name": "Yellow Fever Vaccine", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Zafirlukast", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Zafirlukast", - "item_group": "Drug", - "item_name": "Zafirlukast", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.856856", - "name": "Zafirlukast", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Zaleplon", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Zaleplon", - "item_group": "Drug", - "item_name": "Zaleplon", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.873287", - "name": "Zaleplon", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Zaltoprofen", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Zaltoprofen", - "item_group": "Drug", - "item_name": "Zaltoprofen", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.889263", - "name": "Zaltoprofen", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - }, - { - "asset_category": null, - "attributes": [], - "barcode": null, - "brand": null, - "buying_cost_center": null, - "country_of_origin": null, - "create_new_batch": 0, - "customer_code": "", - "customer_items": [], - "customs_tariff_number": null, - "default_bom": null, - "default_material_request_type": null, - "default_supplier": null, - "default_warehouse": null, - "delivered_by_supplier": 0, - "description": "Zanamivir", - "disabled": 0, - "docstatus": 0, - "doctype": "Item", - "end_of_life": null, - "expense_account": null, - "gst_hsn_code": null, - "has_batch_no": 0, - "has_serial_no": 0, - "has_variants": 0, - "image": null, - "income_account": null, - "inspection_required_before_delivery": 0, - "inspection_required_before_purchase": 0, - "is_fixed_asset": 0, - "is_purchase_item": 1, - "is_sales_item": 1, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "Zanamivir", - "item_group": "Drug", - "item_name": "Zanamivir", - "last_purchase_rate": 0.0, - "lead_time_days": 0, - "max_discount": 0.0, - "min_order_qty": 0.0, - "modified": "2017-07-06 12:53:17.905022", - "name": "Zanamivir", - "naming_series": null, - "net_weight": 0.0, - "opening_stock": 0.0, - "quality_parameters": [], - "reorder_levels": [], - "route": null, - "safety_stock": 0.0, - "selling_cost_center": null, - "serial_no_series": null, - "show_in_website": 0, - "show_variant_in_website": 0, - "slideshow": null, - "standard_rate": 0.0, - "stock_uom": "Nos", - "supplier_items": [], - "taxes": [], - "thumbnail": null, - "tolerance": 0.0, - "uoms": [ - { - "conversion_factor": 1.0, - "uom": "Nos" - } - ], - "valuation_method": null, - "valuation_rate": 0.0, - "variant_based_on": null, - "variant_of": null, - "warranty_period": null, - "web_long_description": null, - "website_image": null, - "website_item_groups": [], - "website_specifications": [], - "website_warehouse": null, - "weight_uom": null, - "weightage": 0 - } -] diff --git a/erpnext/demo/data/employee.json b/erpnext/demo/data/employee.json deleted file mode 100644 index 2d2dbe894b..0000000000 --- a/erpnext/demo/data/employee.json +++ /dev/null @@ -1,92 +0,0 @@ -[ - { - "date_of_birth": "1982-01-03", - "date_of_joining": "2001-10-10", - "employee_name": "Diana Prince", - "first_name": "Diana", - "last_name": "Prince", - "gender": "Female", - "user_id": "DianaPrince@example.com" - }, - { - "date_of_birth": "1959-02-03", - "date_of_joining": "1976-09-16", - "employee_name": "Zatanna Zatara", - "gender": "Female", - "user_id": "ZatannaZatara@example.com", - "first_name": "Zatanna", - "last_name": "Zatara" - }, - { - "date_of_birth": "1982-03-03", - "date_of_joining": "2000-06-16", - "employee_name": "Holly Granger", - "gender": "Female", - "user_id": "HollyGranger@example.com", - "first_name": "Holly", - "last_name": "Granger" - }, - { - "date_of_birth": "1945-04-04", - "date_of_joining": "1969-07-01", - "employee_name": "Neptunia Aquaria", - "gender": "Female", - "user_id": "NeptuniaAquaria@example.com", - "first_name": "Neptunia", - "last_name": "Aquaria" - }, - { - "date_of_birth": "1978-05-03", - "date_of_joining": "1999-12-24", - "employee_name": "Arthur Curry", - "gender": "Male", - "user_id": "ArthurCurry@example.com", - "first_name": "Arthur", - "last_name": "Curry" - }, - { - "date_of_birth": "1964-06-03", - "date_of_joining": "1981-08-05", - "employee_name": "Thalia Al Ghul", - "gender": "Female", - "user_id": "ThaliaAlGhul@example.com", - "first_name": "Thalia", - "last_name": "Al Ghul" - }, - { - "date_of_birth": "1982-07-03", - "date_of_joining": "2006-06-10", - "employee_name": "Maxwell Lord", - "gender": "Male", - "user_id": "MaxwellLord@example.com", - "first_name": "Maxwell", - "last_name": "Lord" - }, - { - "date_of_birth": "1969-08-03", - "date_of_joining": "1993-10-21", - "employee_name": "Grace Choi", - "gender": "Female", - "user_id": "GraceChoi@example.com", - "first_name": "Grace", - "last_name": "Choi" - }, - { - "date_of_birth": "1982-09-03", - "date_of_joining": "2005-09-06", - "employee_name": "Vandal Savage", - "gender": "Male", - "user_id": "VandalSavage@example.com", - "first_name": "Vandal", - "last_name": "Savage" - }, - { - "date_of_birth": "1985-10-03", - "date_of_joining": "2007-12-25", - "employee_name": "Caitlin Snow", - "gender": "Female", - "user_id": "CaitlinSnow@example.com", - "first_name": "Caitlin", - "last_name": "Snow" - } -] \ No newline at end of file diff --git a/erpnext/demo/data/grading_scale.json b/erpnext/demo/data/grading_scale.json deleted file mode 100644 index 07609197c4..0000000000 --- a/erpnext/demo/data/grading_scale.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "doctype": "Grading Scale", - "grading_scale_name": "Standard Grading", - "description": "Standard Grading Scale", - "intervals": [ - {"threshold": 100.0, "grade_code": "A", "grade_description": "Excellent"}, - {"threshold": 89.9, "grade_code": "B+", "grade_description": "Close to Excellence"}, - {"threshold": 80.0, "grade_code": "B", "grade_description": "Good"}, - {"threshold": 69.9, "grade_code": "C+", "grade_description": "Almost Good"}, - {"threshold": 60.0, "grade_code": "C", "grade_description": "Average"}, - {"threshold": 50.0, "grade_code": "D+", "grade_description": "Have to Work"}, - {"threshold": 40.0, "grade_code": "D", "grade_description": "Not met Baseline Expectations"}, - {"threshold": 0.0, "grade_code": "F", "grade_description": "Have to work a lot"} - ] - } -] \ No newline at end of file diff --git a/erpnext/demo/data/instructor.json b/erpnext/demo/data/instructor.json deleted file mode 100644 index a25d16304d..0000000000 --- a/erpnext/demo/data/instructor.json +++ /dev/null @@ -1,128 +0,0 @@ -[ - { - "doctype": "Instructor", - "instructor_name": "Eddie Jessup", - "naming_series": "INS/", - "department": "Information Technology" - }, - { - "doctype": "Instructor", - "instructor_name": "William Dyer", - "naming_series": "INS/", - "department": "Information Technology" - }, - { - "doctype": "Instructor", - "instructor_name": "Alastor Moody", - "naming_series": "INS/", - "department": "Information Technology" - }, - { - "doctype": "Instructor", - "instructor_name": "Charles Xavier", - "naming_series": "INS/", - "department": "Information Technology" - }, - { - "doctype": "Instructor", - "instructor_name": "Cuthbert Calculus", - "naming_series": "INS/", - "department": "Information Technology" - }, - { - "doctype": "Instructor", - "instructor_name": "Reed Richards", - "naming_series": "INS/", - "department": "Information Technology" - }, - { - "doctype": "Instructor", - "instructor_name": "Urban Chronotis", - "naming_series": "INS/", - "department": "Physics" - }, - { - "doctype": "Instructor", - "instructor_name": "River Song", - "naming_series": "INS/", - "department": "Physics" - }, - { - "doctype": "Instructor", - "instructor_name": "Yana", - "naming_series": "INS/", - "department": "Physics" - }, - { - "doctype": "Instructor", - "instructor_name": "Neil Lasrado", - "naming_series": "INS/", - "department": "Information Technology" - }, - { - "doctype": "Instructor", - "instructor_name": "Deepshi Garg", - "naming_series": "INS/", - "department": "Chemistry" - }, - { - "doctype": "Instructor", - "instructor_name": "Shubham Saxena", - "naming_series": "INS/", - "department": "Physics" - }, - { - "doctype": "Instructor", - "instructor_name": "Rushabh Mehta", - "naming_series": "INS/", - "department": "Information Technology" - }, - { - "doctype": "Instructor", - "instructor_name": "Umari Syed", - "naming_series": "INS/", - "department": "Chemistry" - }, - { - "doctype": "Instructor", - "instructor_name": "Aman Singh", - "naming_series": "INS/", - "department": "Physics" - }, - { - "doctype": "Instructor", - "instructor_name": "Nabin", - "naming_series": "INS/", - "department": "Chemistry" - }, - { - "doctype": "Instructor", - "instructor_name": "Kanchan Chauhan", - "naming_series": "INS/", - "department": "Information Technology" - }, - { - "doctype": "Instructor", - "instructor_name": "Valmik Jangla", - "naming_series": "INS/", - "department": "Chemistry" - }, - { - "doctype": "Instructor", - "instructor_name": "Amit Jain", - "naming_series": "INS/", - "department": "Physics" - }, - { - "doctype": "Instructor", - "instructor_name": "Shreyas P", - "naming_series": "INS/", - "department": "Chemistry" - }, - { - "doctype": "Instructor", - "instructor_name": "Rohit", - "naming_series": "INS/", - "department": "Information Technology" - } -] \ No newline at end of file diff --git a/erpnext/demo/data/item.json b/erpnext/demo/data/item.json deleted file mode 100644 index 1d4ed343be..0000000000 --- a/erpnext/demo/data/item.json +++ /dev/null @@ -1,493 +0,0 @@ -[ - { - "item_defaults": [ - { - "default_supplier": "Asiatic Solutions", - "default_warehouse": "Stores" - } - ], - "description": "For Upper Bearing", - "image": "/assets/erpnext_demo/images/disc.png", - "item_code": "Disc Collars", - "item_group": "Raw Material", - "item_name": "Disc Collars" - }, - { - "item_defaults": [ - { - "default_supplier": "Nan Duskin", - "default_warehouse": "Stores" - } - ], - "description": "CAST IRON, MCMASTER PART NO. 3710T13", - "image": "/assets/erpnext_demo/images/bearing.jpg", - "item_code": "Bearing Block", - "item_group": "Raw Material", - "item_name": "Bearing Block" - }, - { - "item_defaults": [ - { - "default_supplier": null, - "default_warehouse": "Finished Goods" - } - ], - "description": "Wind Mill C Series for Commercial Use 18ft", - "image": "/assets/erpnext_demo/images/wind-turbine-2.png", - "item_code": "Wind MIll C Series", - "item_group": "Products", - "item_name": "Wind MIll C Series" - }, - { - "item_defaults": [ - { - "default_supplier": null, - "default_warehouse": "Finished Goods" - } - ], - "description": "Wind Mill A Series for Home Use 9ft", - "image": "/assets/erpnext_demo/images/wind-turbine.png", - "item_code": "Wind Mill A Series", - "item_group": "Products", - "item_name": "Wind Mill A Series" - }, - { - "item_defaults": [ - { - "default_supplier": null, - "default_warehouse": "Finished Goods" - } - ], - "description": "Small Wind Turbine for Home Use\n\n\n", - "image": "/assets/erpnext_demo/images/wind-turbine-1.jpg", - "item_code": "Wind Turbine", - "item_group": "Products", - "item_name": "Wind Turbine", - "has_variants": 1, - "has_serial_no": 1, - "attributes": [ - { - "attribute": "Size" - } - ] - }, - { - "item_defaults": [ - { - "default_supplier": "HomeBase", - "default_warehouse": "Stores" - } - ], - "description": "1.5 in. Diameter x 36 in. Mild Steel Tubing", - "image": null, - "item_code": "Bearing Pipe", - "item_group": "Raw Material", - "item_name": "Bearing Pipe" - }, - { - "item_defaults": [ - { - "default_supplier": "New World Realty", - "default_warehouse": "Stores" - } - ], - "description": "1/32 in. x 24 in. x 47 in. HDPE Opaque Sheet", - "image": null, - "item_code": "Wing Sheet", - "item_group": "Raw Material", - "item_name": "Wing Sheet" - }, - { - "item_defaults": [ - { - "default_supplier": "Eagle Hardware", - "default_warehouse": "Stores" - } - ], - "description": "3/16 in. x 6 in. x 6 in. Low Carbon Steel Plate", - "image": null, - "item_code": "Upper Bearing Plate", - "item_group": "Raw Material", - "item_name": "Upper Bearing Plate" - }, - { - "item_defaults": [ - { - "default_supplier": "Asiatic Solutions", - "default_warehouse": "Stores" - } - ], - "description": "Bearing Assembly", - "image": null, - "item_code": "Bearing Assembly", - "item_group": "Sub Assemblies", - "item_name": "Bearing Assembly" - }, - { - "item_defaults": [ - { - "default_supplier": "HomeBase", - "default_warehouse": "Stores" - } - ], - "description": "3/4 in. x 2 ft. x 4 ft. Pine Plywood", - "image": null, - "item_code": "Base Plate", - "item_group": "Raw Material", - "item_name": "Base Plate", - "is_sub_contracted_item": 1 - }, - { - "item_defaults": [ - { - "default_supplier": "Scott Ties", - "default_warehouse": "Stores" - } - ], - "description": "N/A", - "image": null, - "item_code": "Stand", - "item_group": "Raw Material", - "item_name": "Stand" - }, - { - "item_defaults": [ - { - "default_supplier": "Eagle Hardware", - "default_warehouse": "Stores" - } - ], - "description": "1 in. x 3 in. x 1 ft. Multipurpose Al Alloy Bar", - "image": null, - "item_code": "Bearing Collar", - "item_group": "Raw Material", - "item_name": "Bearing Collar" - }, - { - "item_defaults": [ - { - "default_supplier": "Eagle Hardware", - "default_warehouse": "Stores" - } - ], - "description": "1/4 in. x 6 in. x 6 in. Mild Steel Plate", - "image": null, - "item_code": "Base Bearing Plate", - "item_group": "Raw Material", - "item_name": "Base Bearing Plate" - }, - { - "item_defaults": [ - { - "default_supplier": "HomeBase", - "default_warehouse": "Stores" - } - ], - "description": "15/32 in. x 4 ft. x 8 ft. 3-Ply Rtd Sheathing", - "image": null, - "item_code": "External Disc", - "item_group": "Raw Material", - "item_name": "External Disc" - }, - { - "item_defaults": [ - { - "default_supplier": "Eagle Hardware", - "default_warehouse": "Stores" - } - ], - "description": "1.25 in. Diameter x 6 ft. Mild Steel Tubing", - "image": null, - "item_code": "Shaft", - "item_group": "Raw Material", - "item_name": "Shaft" - }, - { - "item_defaults": [ - { - "default_supplier": "Ks Merchandise", - "default_warehouse": "Stores" - } - ], - "description": "1/2 in. x 2 ft. x 4 ft. Pine Plywood", - "image": null, - "item_code": "Blade Rib", - "item_group": "Raw Material", - "item_name": "Blade Rib" - }, - { - "item_defaults": [ - { - "default_supplier": "HomeBase", - "default_warehouse": "Stores" - } - ], - "description": "For Bearing Collar", - "image": null, - "item_code": "Internal Disc", - "item_group": "Raw Material", - "item_name": "Internal Disc" - }, - { - "item_defaults": [ - { - "default_supplier": null, - "default_warehouse": "Finished Goods" - } - ], - "description": "Small Wind Turbine for Home Use\n\n\n\n

Size: Small

", - "image": "/assets/erpnext_demo/images/wind-turbine-1.jpg", - "item_code": "Wind Turbine-S", - "item_group": "Products", - "item_name": "Wind Turbine-S", - "variant_of": "Wind Turbine", - "valuation_rate": 300, - "attributes": [ - { - "attribute": "Size", - "attribute_value": "Small" - } - ] - }, - { - "item_defaults": [ - { - "default_supplier": null, - "default_warehouse": "Finished Goods" - } - ], - "description": "Small Wind Turbine for Home Use\n\n\n\n

Size: Medium

", - "image": "/assets/erpnext_demo/images/wind-turbine-1.jpg", - "item_code": "Wind Turbine-M", - "item_group": "Products", - "item_name": "Wind Turbine-M", - "variant_of": "Wind Turbine", - "valuation_rate": 300, - "attributes": [ - { - "attribute": "Size", - "attribute_value": "Medium" - } - ] - }, - { - "item_defaults": [ - { - "default_supplier": null, - "default_warehouse": "Finished Goods" - } - ], - "description": "Small Wind Turbine for Home Use\n\n\n\n

Size: Large

", - "image": "/assets/erpnext_demo/images/wind-turbine-1.jpg", - "item_code": "Wind Turbine-L", - "item_group": "Products", - "item_name": "Wind Turbine-L", - "variant_of": "Wind Turbine", - "valuation_rate": 300, - "attributes": [ - { - "attribute": "Size", - "attribute_value": "Large" - } - ] - }, - { - "is_stock_item": 0, - "description": "Wind Mill A Series with Spare Bearing", - "item_code": "Wind Mill A Series with Spare Bearing", - "item_group": "Products", - "item_name": "Wind Mill A Series with Spare Bearing" - }, - { - "item_defaults": [ - { - "default_supplier": "HomeBase", - "default_warehouse": "Stores" - } - ], - "description": "3/4 in. x 2 ft. x 4 ft. Pine Plywood", - "image": null, - "item_code": "Base Plate Un Painted", - "item_group": "Raw Material", - "item_name": "Base Plate Un Painted" - }, - { - "is_fixed_asset": 1, - "asset_category": "Furnitures", - "is_stock_item": 0, - "description": "Table", - "item_code": "Table", - "item_name": "Table", - "item_group": "Products" - }, - { - "is_fixed_asset": 1, - "asset_category": "Furnitures", - "is_stock_item": 0, - "description": "Chair", - "item_code": "Chair", - "item_name": "Chair", - "item_group": "Products" - }, - { - "is_fixed_asset": 1, - "asset_category": "Electronic Equipments", - "is_stock_item": 0, - "description": "Computer", - "item_code": "Computer", - "item_name": "Computer", - "item_group": "Products" - }, - { - "is_fixed_asset": 1, - "asset_category": "Electronic Equipments", - "is_stock_item": 0, - "description": "Mobile", - "item_code": "Mobile", - "item_name": "Mobile", - "item_group": "Products" - }, - { - "is_fixed_asset": 1, - "asset_category": "Softwares", - "is_stock_item": 0, - "description": "ERP", - "item_code": "ERP", - "item_name": "ERP", - "item_group": "All Item Groups" - }, - { - "is_fixed_asset": 1, - "asset_category": "Softwares", - "is_stock_item": 0, - "description": "Autocad", - "item_code": "Autocad", - "item_name": "Autocad", - "item_group": "All Item Groups" - }, - { - "is_stock_item": 1, - "has_batch_no": 1, - "create_new_batch": 1, - "valuation_rate": 200, - "item_defaults": [ - { - "default_warehouse": "Stores" - } - ], - "description": "Corrugated Box", - "item_code": "Corrugated Box", - "item_name": "Corrugated Box", - "item_group": "All Item Groups" - }, - { - "item_defaults": [ - { - "default_warehouse": "Finished Goods" - } - ], - "is_stock_item": 1, - "description": "OnePlus 6", - "item_code": "OnePlus 6", - "item_name": "OnePlus 6", - "item_group": "Products", - "domain": "Retail" - }, - { - "item_defaults": [ - { - "default_warehouse": "Finished Goods" - } - ], - "is_stock_item": 1, - "description": "OnePlus 6T", - "item_code": "OnePlus 6T", - "item_name": "OnePlus 6T", - "item_group": "Products", - "domain": "Retail" - }, - { - "item_defaults": [ - { - "default_warehouse": "Finished Goods" - } - ], - "is_stock_item": 1, - "description": "Xiaomi Poco F1", - "item_code": "Xiaomi Poco F1", - "item_name": "Xiaomi Poco F1", - "item_group": "Products", - "domain": "Retail" - }, - { - "item_defaults": [ - { - "default_warehouse": "Finished Goods" - } - ], - "is_stock_item": 1, - "description": "Iphone XS", - "item_code": "Iphone XS", - "item_name": "Iphone XS", - "item_group": "Products", - "domain": "Retail" - }, - { - "item_defaults": [ - { - "default_warehouse": "Finished Goods" - } - ], - "is_stock_item": 1, - "description": "Samsung Galaxy S9", - "item_code": "Samsung Galaxy S9", - "item_name": "Samsung Galaxy S9", - "item_group": "Products", - "domain": "Retail" - }, - { - "item_defaults": [ - { - "default_warehouse": "Finished Goods" - } - ], - "is_stock_item": 1, - "description": "Sony Bluetooth Headphone", - "item_code": "Sony Bluetooth Headphone", - "item_name": "Sony Bluetooth Headphone", - "item_group": "Products", - "domain": "Retail" - }, - { - "is_stock_item": 0, - "description": "Samsung Phone Repair", - "item_code": "Samsung Phone Repair", - "item_name": "Samsung Phone Repair", - "item_group": "Services", - "domain": "Retail" - }, - { - "is_stock_item": 0, - "description": "OnePlus Phone Repair", - "item_code": "OnePlus Phone Repair", - "item_name": "OnePlus Phone Repair", - "item_group": "Services", - "domain": "Retail" - }, - { - "is_stock_item": 0, - "description": "Xiaomi Phone Repair", - "item_code": "Xiaomi Phone Repair", - "item_name": "Xiaomi Phone Repair", - "item_group": "Services", - "domain": "Retail" - }, - { - "is_stock_item": 0, - "description": "Apple Phone Repair", - "item_code": "Apple Phone Repair", - "item_name": "Apple Phone Repair", - "item_group": "Services", - "domain": "Retail" - } -] \ No newline at end of file diff --git a/erpnext/demo/data/item_education.json b/erpnext/demo/data/item_education.json deleted file mode 100644 index 40e4701596..0000000000 --- a/erpnext/demo/data/item_education.json +++ /dev/null @@ -1,137 +0,0 @@ -[ - { - "default_supplier": "Asiatic Solutions", - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "item_code": "Books", - "item_group": "Raw Material", - "item_name": "Books" - }, - { - "default_supplier": "HomeBase", - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "item_code": "Pencil", - "item_group": "Raw Material", - "item_name": "Pencil" - }, - { - "default_supplier": "New World Realty", - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "item_code": "Tables", - "item_group": "Raw Material", - "item_name": "Tables" - }, - { - "default_supplier": "Eagle Hardware", - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "item_code": "Chair", - "item_group": "Raw Material", - "item_name": "Chair" - }, - { - "default_supplier": "Asiatic Solutions", - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "item_code": "Black Board", - "item_group": "Sub Assemblies", - "item_name": "Black Board" - }, - { - "default_supplier": "HomeBase", - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "item_code": "Chalk", - "item_group": "Raw Material", - "item_name": "Chalk" - }, - { - "default_supplier": "HomeBase", - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "item_code": "Notepad", - "item_group": "Raw Material", - "item_name": "Notepad" - }, - { - "default_supplier": "Ks Merchandise", - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "item_code": "Uniform", - "item_group": "Raw Material", - "item_name": "Uniform" - }, - { - "is_stock_item": 0, - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "description": "Computer", - "item_code": "Computer", - "item_name": "Computer", - "item_group": "Products" - }, - { - "is_stock_item": 0, - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "description": "Mobile", - "item_code": "Mobile", - "item_name": "Mobile", - "item_group": "Products" - }, - { - "is_stock_item": 0, - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "description": "ERP", - "item_code": "ERP", - "item_name": "ERP", - "item_group": "All Item Groups" - }, - { - "is_stock_item": 0, - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "description": "Autocad", - "item_code": "Autocad", - "item_name": "Autocad", - "item_group": "All Item Groups" - }, - { - "item_defaults": [{ - "default_warehouse": "Stores", - "company": "Whitmore College" - }], - "item_code": "Service", - "item_group": "Services", - "item_name": "Service", - "has_variants": 0, - "is_stock_item": 0 - } -] \ No newline at end of file diff --git a/erpnext/demo/data/lead.json b/erpnext/demo/data/lead.json deleted file mode 100644 index ff78877897..0000000000 --- a/erpnext/demo/data/lead.json +++ /dev/null @@ -1,127 +0,0 @@ -[ - { - "company_name": "Zany Brainy", - "email_id": "MartLakeman@example.com", - "lead_name": "Mart Lakeman" - }, - { - "company_name": "Patterson-Fletcher", - "email_id": "SagaLundqvist@example.com", - "lead_name": "Saga Lundqvist" - }, - { - "company_name": "Griff's Hamburgers", - "email_id": "AdnaSjoberg@example.com", - "lead_name": "Adna Sj\u00f6berg" - }, - { - "company_name": "Rhodes Furniture", - "email_id": "IdaDSvendsen@example.com", - "lead_name": "Ida Svendsen" - }, - { - "company_name": "Burger Chef", - "email_id": "EmppuHameenniemi@example.com", - "lead_name": "Emppu H\u00e4meenniemi" - }, - { - "company_name": "Stratabiz", - "email_id": "EugenioPisano@example.com", - "lead_name": "Eugenio Pisano" - }, - { - "company_name": "Home Quarters Warehouse", - "email_id": "SemharHagos@example.com", - "lead_name": "Semhar Hagos" - }, - { - "company_name": "Enviro Architectural Designs", - "email_id": "BranimiraIvankovic@example.com", - "lead_name": "Branimira Ivankovi\u0107" - }, - { - "company_name": "Ideal Garden Management", - "email_id": "ShellyLFields@example.com", - "lead_name": "Shelly Fields" - }, - { - "company_name": "Listen Up", - "email_id": "LeoMikulic@example.com", - "lead_name": "Leo Mikuli\u0107" - }, - { - "company_name": "I. Magnin", - "email_id": "DenisaJarosova@example.com", - "lead_name": "Denisa Jaro\u0161ov\u00e1" - }, - { - "company_name": "First Rate Choice", - "email_id": "JanekRutkowski@example.com", - "lead_name": "Janek Rutkowski" - }, - { - "company_name": "Multi Tech Development", - "email_id": "mm@example.com", - "lead_name": "\u7f8e\u6708 \u5b87\u85e4" - }, - { - "company_name": "National Auto Parts", - "email_id": "dd@example.com", - "lead_name": "\u0414\u0430\u043d\u0438\u0438\u043b \u0410\u0444\u0430\u043d\u0430\u0441\u044c\u0435\u0432" - }, - { - "company_name": "Integra Investment Plan", - "email_id": "ZorislavPetkovic@example.com", - "lead_name": "Zorislav Petkovi\u0107" - }, - { - "company_name": "The Lawn Guru", - "email_id": "NanaoNiwa@example.com", - "lead_name": "Nanao Niwa" - }, - { - "company_name": "Buena Vista Realty Service", - "email_id": "HreiarJorundsson@example.com", - "lead_name": "Hrei\u00f0ar J\u00f6rundsson" - }, - { - "company_name": "Bountiful Harvest Health Food Store", - "email_id": "ChuThiBichLai@example.com", - "lead_name": "Lai Chu" - }, - { - "company_name": "P. Samuels Men's Clothiers", - "email_id": "VictorAksakov@example.com", - "lead_name": "Victor Aksakov" - }, - { - "company_name": "Vinyl Fever", - "email_id": "SaidalimBisliev@example.com", - "lead_name": "Saidalim Bisliev" - }, - { - "company_name": "Garden Master", - "email_id": "TotteJakobsson@example.com", - "lead_name": "Totte Jakobsson" - }, - { - "company_name": "Big Apple", - "email_id": "NanaArmasRobles@example.com", - "lead_name": "Nan\u00e1 Armas" - }, - { - "company_name": "Monk House Sales", - "email_id": "WalerianDuda@example.com", - "lead_name": "Walerian Duda" - }, - { - "company_name": "ManCharm", - "email_id": "Moarimikashi@example.com", - "lead_name": "Moarimikashi" - }, - { - "company_name": "Custom Lawn Care", - "email_id": "DobromilDabrowski@example.com", - "lead_name": "Dobromi\u0142 D\u0105browski" - } -] \ No newline at end of file diff --git a/erpnext/demo/data/location.json b/erpnext/demo/data/location.json deleted file mode 100644 index b521aa08c4..0000000000 --- a/erpnext/demo/data/location.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "location_name": "Main Location", - "latitude": 40.0, - "longitude": 20.0 - }, - { - "location_name": "Avg Location", - "latitude": 63.0, - "longitude": 99.3 - }, - { - "location_name": "Zany Location", - "latitude": 47.5, - "longitude": 10.0 - }, - { - "location_name": "Fletcher Location", - "latitude": 100.90, - "longitude": 80 - } -] \ No newline at end of file diff --git a/erpnext/demo/data/operation.json b/erpnext/demo/data/operation.json deleted file mode 100644 index 47f26d1d52..0000000000 --- a/erpnext/demo/data/operation.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "description": "Setup Fixtures for Assembly", - "name": "Setup Fixtures", - "workstation": "Assembly Station 1" - }, - { - "description": "Assemble Unit as per Standard Operating Procedures", - "name": "Assembly Operation", - "workstation": "Assembly Station 1" - }, - { - "description": "Final Testing Checklist", - "name": "Testing", - "workstation": "Packing and Testing Station" - }, - { - "description": "Final Packing and add Instructions", - "name": "Packing", - "workstation": "Packing and Testing Station" - }, - { - "description": "Prepare frame for assembly", - "name": "Prepare Frame", - "workstation": "Drilling Machine 1" - }, - { - "description": "Connect wires", - "name": "Wiring", - "workstation": "Assembly Station 1" - } -] \ No newline at end of file diff --git a/erpnext/demo/data/patient.json b/erpnext/demo/data/patient.json deleted file mode 100644 index 6d95a20202..0000000000 --- a/erpnext/demo/data/patient.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "patient_name": "lila", - "gender": "Female" - }, - { - "patient_name": "charline", - "gender": "Female" - }, - { - "patient_name": "soren", - "last_name": "le gall", - "gender": "Male" - }, - { - "patient_name": "fanny", - "gender": "Female" - }, - { - "patient_name": "julie", - "gender": "Female" - }, - { - "patient_name": "louka", - "gender": "Male" - } -] diff --git a/erpnext/demo/data/practitioner.json b/erpnext/demo/data/practitioner.json deleted file mode 100644 index 39c960fcd7..0000000000 --- a/erpnext/demo/data/practitioner.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "doctype": "Healthcare Practitioner", - "first_name": "Eddie Jessup", - "department": "Pathology" - }, - { - "doctype": "Healthcare Practitioner", - "first_name": "Deepshi Garg", - "department": "ENT" - }, - { - "doctype": "Healthcare Practitioner", - "first_name": "Amit Jain", - "department": "Microbiology" - } -] diff --git a/erpnext/demo/data/program.json b/erpnext/demo/data/program.json deleted file mode 100644 index 9c2ec77a4b..0000000000 --- a/erpnext/demo/data/program.json +++ /dev/null @@ -1,46 +0,0 @@ -[ - { - "doctype": "Program", - "name": "MCA", - "program_name": "Masters of Computer Applications", - "program_code": "MCA", - "department": "Information Technology", - "courses": [ - { "course": "MCA4010" }, - { "course": "MCA4020" }, - { "course": "MCA4030" } - ] - }, - { - "doctype": "Program", - "name": "BCA", - "program_name": "Bachelor of Computer Applications", - "program_code": "BCA", - "department": "Information Technology", - "courses": [ - { "course": "BCA2030" }, - { "course": "BCA1030" }, - { "course": "BCA2020" }, - { "course": "BCA1040" }, - { "course": "BCA1010" }, - { "course": "BCA2010" }, - { "course": "BCA1020" } - ] - }, - { - "doctype": "Program", - "name": "BBA", - "program_name": "Bachelor of Business Administration", - "program_code": "BBA", - "department": "Management Studies", - "courses": [ - { "course": "BBA 101" }, - { "course": "BBA 102" }, - { "course": "BBA 103" }, - { "course": "BBA 301" }, - { "course": "BBA 302" }, - { "course": "BBA 304" }, - { "course": "BBA 505" } - ] - } -] \ No newline at end of file diff --git a/erpnext/demo/data/random_student_data.json b/erpnext/demo/data/random_student_data.json deleted file mode 100644 index babcc71576..0000000000 --- a/erpnext/demo/data/random_student_data.json +++ /dev/null @@ -1,1604 +0,0 @@ -[ -{ -"first_name": "amanda", -"last_name": "edwards", -"image": "https://randomuser.me/api/portraits/women/55.jpg", -"gender": "Female" -}, -{ -"first_name": "abbie", -"last_name": "johnston", -"image": "https://randomuser.me/api/portraits/women/46.jpg", -"gender": "Female" -}, -{ -"first_name": "heather", -"last_name": "nelson", -"image": "https://randomuser.me/api/portraits/women/13.jpg", -"gender": "Female" -}, -{ -"first_name": "maxwell", -"last_name": "gilbert", -"image": "https://randomuser.me/api/portraits/men/56.jpg", -"gender": "Male" -}, -{ -"first_name": "molly", -"last_name": "ramirez", -"image": "https://randomuser.me/api/portraits/women/71.jpg", -"gender": "Female" -}, -{ -"first_name": "ian", -"last_name": "barrett", -"image": "https://randomuser.me/api/portraits/men/68.jpg", -"gender": "Male" -}, -{ -"first_name": "kim", -"last_name": "hudson", -"image": "https://randomuser.me/api/portraits/women/53.jpg", -"gender": "Female" -}, -{ -"first_name": "bruce", -"last_name": "murray", -"image": "https://randomuser.me/api/portraits/men/59.jpg", -"gender": "Male" -}, -{ -"first_name": "henry", -"last_name": "powell", -"image": "https://randomuser.me/api/portraits/men/88.jpg", -"gender": "Male" -}, -{ -"first_name": "chris", -"last_name": "foster", -"image": "https://randomuser.me/api/portraits/men/5.jpg", -"gender": "Male" -}, -{ -"first_name": "billy", -"last_name": "kim", -"image": "https://randomuser.me/api/portraits/men/91.jpg", -"gender": "Male" -}, -{ -"first_name": "samuel", -"last_name": "harper", -"image": "https://randomuser.me/api/portraits/men/56.jpg", -"gender": "Male" -}, -{ -"first_name": "jayden", -"last_name": "kelly", -"image": "https://randomuser.me/api/portraits/men/31.jpg", -"gender": "Male" -}, -{ -"first_name": "grace", -"last_name": "berry", -"image": "https://randomuser.me/api/portraits/women/69.jpg", -"gender": "Female" -}, -{ -"first_name": "ronnie", -"last_name": "nelson", -"image": "https://randomuser.me/api/portraits/men/83.jpg", -"gender": "Male" -}, -{ -"first_name": "harvey", -"last_name": "harper", -"image": "https://randomuser.me/api/portraits/men/68.jpg", -"gender": "Male" -}, -{ -"first_name": "maya", -"last_name": "fernandez", -"image": "https://randomuser.me/api/portraits/women/79.jpg", -"gender": "Female" -}, -{ -"first_name": "faith", -"last_name": "lewis", -"image": "https://randomuser.me/api/portraits/women/84.jpg", -"gender": "Female" -}, -{ -"first_name": "kirk", -"last_name": "macrae", -"image": "https://randomuser.me/api/portraits/men/13.jpg", -"gender": "Male" -}, -{ -"first_name": "tracy", -"last_name": "holt", -"image": "https://randomuser.me/api/portraits/women/18.jpg", -"gender": "Female" -}, -{ -"first_name": "mandy", -"last_name": "dean", -"image": "https://randomuser.me/api/portraits/women/0.jpg", -"gender": "Female" -}, -{ -"first_name": "sam", -"last_name": "dunn", -"image": "https://randomuser.me/api/portraits/women/12.jpg", -"gender": "Female" -}, -{ -"first_name": "zoe", -"last_name": "fleming", -"image": "https://randomuser.me/api/portraits/women/9.jpg", -"gender": "Female" -}, -{ -"first_name": "jeffrey", -"last_name": "stewart", -"image": "https://randomuser.me/api/portraits/men/56.jpg", -"gender": "Male" -}, -{ -"first_name": "dick", -"last_name": "ryan", -"image": "https://randomuser.me/api/portraits/men/63.jpg", -"gender": "Male" -}, -{ -"first_name": "carl", -"last_name": "neal", -"image": "https://randomuser.me/api/portraits/men/41.jpg", -"gender": "Male" -}, -{ -"first_name": "scarlett", -"last_name": "ruiz", -"image": "https://randomuser.me/api/portraits/women/24.jpg", -"gender": "Female" -}, -{ -"first_name": "rene", -"last_name": "hughes", -"image": "https://randomuser.me/api/portraits/men/3.jpg", -"gender": "Male" -}, -{ -"first_name": "greg", -"last_name": "montgomery", -"image": "https://randomuser.me/api/portraits/men/12.jpg", -"gender": "Male" -}, -{ -"first_name": "matt", -"last_name": "lane", -"image": "https://randomuser.me/api/portraits/men/85.jpg", -"gender": "Male" -}, -{ -"first_name": "eleanor", -"last_name": "pearson", -"image": "https://randomuser.me/api/portraits/women/61.jpg", -"gender": "Female" -}, -{ -"first_name": "theodore", -"last_name": "burton", -"image": "https://randomuser.me/api/portraits/men/81.jpg", -"gender": "Male" -}, -{ -"first_name": "jesus", -"last_name": "hunt", -"image": "https://randomuser.me/api/portraits/men/50.jpg", -"gender": "Male" -}, -{ -"first_name": "taylor", -"last_name": "alvarez", -"image": "https://randomuser.me/api/portraits/men/0.jpg", -"gender": "Male" -}, -{ -"first_name": "barbara", -"last_name": "lucas", -"image": "https://randomuser.me/api/portraits/women/21.jpg", -"gender": "Female" -}, -{ -"first_name": "nicky", -"last_name": "simmons", -"image": "https://randomuser.me/api/portraits/women/29.jpg", -"gender": "Female" -}, -{ -"first_name": "arthur", -"last_name": "obrien", -"image": "https://randomuser.me/api/portraits/men/11.jpg", -"gender": "Male" -}, -{ -"first_name": "donna", -"last_name": "holmes", -"image": "https://randomuser.me/api/portraits/women/33.jpg", -"gender": "Female" -}, -{ -"first_name": "mitchell", -"last_name": "castro", -"image": "https://randomuser.me/api/portraits/men/26.jpg", -"gender": "Male" -}, -{ -"first_name": "byron", -"last_name": "marshall", -"image": "https://randomuser.me/api/portraits/men/57.jpg", -"gender": "Male" -}, -{ -"first_name": "larry", -"last_name": "king", -"image": "https://randomuser.me/api/portraits/men/58.jpg", -"gender": "Male" -}, -{ -"first_name": "deborah", -"last_name": "fuller", -"image": "https://randomuser.me/api/portraits/women/50.jpg", -"gender": "Female" -}, -{ -"first_name": "eleanor", -"last_name": "elliott", -"image": "https://randomuser.me/api/portraits/women/80.jpg", -"gender": "Female" -}, -{ -"first_name": "derrick", -"last_name": "shaw", -"image": "https://randomuser.me/api/portraits/men/78.jpg", -"gender": "Male" -}, -{ -"first_name": "barbara", -"last_name": "lynch", -"image": "https://randomuser.me/api/portraits/women/15.jpg", -"gender": "Female" -}, -{ -"first_name": "elijah", -"last_name": "allen", -"image": "https://randomuser.me/api/portraits/men/43.jpg", -"gender": "Male" -}, -{ -"first_name": "nicholas", -"last_name": "harper", -"image": "https://randomuser.me/api/portraits/men/2.jpg", -"gender": "Male" -}, -{ -"first_name": "sofia", -"last_name": "riley", -"image": "https://randomuser.me/api/portraits/women/96.jpg", -"gender": "Female" -}, -{ -"first_name": "jar", -"last_name": "hunt", -"image": "https://randomuser.me/api/portraits/men/72.jpg", -"gender": "Male" -}, -{ -"first_name": "philip", -"last_name": "rose", -"image": "https://randomuser.me/api/portraits/men/16.jpg", -"gender": "Male" -}, -{ -"first_name": "ella", -"last_name": "moore", -"image": "https://randomuser.me/api/portraits/women/83.jpg", -"gender": "Female" -}, -{ -"first_name": "seth", -"last_name": "tucker", -"image": "https://randomuser.me/api/portraits/men/6.jpg", -"gender": "Male" -}, -{ -"first_name": "abby", -"last_name": "gonzalez", -"image": "https://randomuser.me/api/portraits/women/18.jpg", -"gender": "Female" -}, -{ -"first_name": "noah", -"last_name": "williamson", -"image": "https://randomuser.me/api/portraits/men/54.jpg", -"gender": "Male" -}, -{ -"first_name": "cathy", -"last_name": "gray", -"image": "https://randomuser.me/api/portraits/women/88.jpg", -"gender": "Female" -}, -{ -"first_name": "barb", -"last_name": "snyder", -"image": "https://randomuser.me/api/portraits/women/49.jpg", -"gender": "Female" -}, -{ -"first_name": "rosalyn", -"last_name": "hale", -"image": "https://randomuser.me/api/portraits/women/64.jpg", -"gender": "Female" -}, -{ -"first_name": "jessica", -"last_name": "armstrong", -"image": "https://randomuser.me/api/portraits/women/95.jpg", -"gender": "Female" -}, -{ -"first_name": "vicki", -"last_name": "wheeler", -"image": "https://randomuser.me/api/portraits/women/49.jpg", -"gender": "Female" -}, -{ -"first_name": "luke", -"last_name": "fisher", -"image": "https://randomuser.me/api/portraits/men/77.jpg", -"gender": "Male" -}, -{ -"first_name": "joey", -"last_name": "wheeler", -"image": "https://randomuser.me/api/portraits/men/50.jpg", -"gender": "Male" -}, -{ -"first_name": "victoria", -"last_name": "jimenez", -"image": "https://randomuser.me/api/portraits/women/25.jpg", -"gender": "Female" -}, -{ -"first_name": "daryl", -"last_name": "patterson", -"image": "https://randomuser.me/api/portraits/men/30.jpg", -"gender": "Male" -}, -{ -"first_name": "dwayne", -"last_name": "jensen", -"image": "https://randomuser.me/api/portraits/men/71.jpg", -"gender": "Male" -}, -{ -"first_name": "herbert", -"last_name": "silva", -"image": "https://randomuser.me/api/portraits/men/83.jpg", -"gender": "Male" -}, -{ -"first_name": "walter", -"last_name": "walker", -"image": "https://randomuser.me/api/portraits/men/91.jpg", -"gender": "Male" -}, -{ -"first_name": "logan", -"last_name": "banks", -"image": "https://randomuser.me/api/portraits/men/67.jpg", -"gender": "Male" -}, -{ -"first_name": "shawn", -"last_name": "harvey", -"image": "https://randomuser.me/api/portraits/men/87.jpg", -"gender": "Male" -}, -{ -"first_name": "lawrence", -"last_name": "bradley", -"image": "https://randomuser.me/api/portraits/men/40.jpg", -"gender": "Male" -}, -{ -"first_name": "jack", -"last_name": "fleming", -"image": "https://randomuser.me/api/portraits/men/37.jpg", -"gender": "Male" -}, -{ -"first_name": "jackson", -"last_name": "boyd", -"image": "https://randomuser.me/api/portraits/men/68.jpg", -"gender": "Male" -}, -{ -"first_name": "cecil", -"last_name": "webb", -"image": "https://randomuser.me/api/portraits/men/9.jpg", -"gender": "Male" -}, -{ -"first_name": "eliza", -"last_name": "mills", -"image": "https://randomuser.me/api/portraits/women/20.jpg", -"gender": "Female" -}, -{ -"first_name": "jenny", -"last_name": "frazier", -"image": "https://randomuser.me/api/portraits/women/61.jpg", -"gender": "Female" -}, -{ -"first_name": "kent", -"last_name": "butler", -"image": "https://randomuser.me/api/portraits/men/64.jpg", -"gender": "Male" -}, -{ -"first_name": "rose", -"last_name": "perry", -"image": "https://randomuser.me/api/portraits/women/74.jpg", -"gender": "Female" -}, -{ -"first_name": "jack", -"last_name": "king", -"image": "https://randomuser.me/api/portraits/men/60.jpg", -"gender": "Male" -}, -{ -"first_name": "elmer", -"last_name": "williams", -"image": "https://randomuser.me/api/portraits/men/26.jpg", -"gender": "Male" -}, -{ -"first_name": "vanessa", -"last_name": "torres", -"image": "https://randomuser.me/api/portraits/women/41.jpg", -"gender": "Female" -}, -{ -"first_name": "tyrone", -"last_name": "coleman", -"image": "https://randomuser.me/api/portraits/men/59.jpg", -"gender": "Male" -}, -{ -"first_name": "julie", -"last_name": "bradley", -"image": "https://randomuser.me/api/portraits/women/50.jpg", -"gender": "Female" -}, -{ -"first_name": "fernando", -"last_name": "castro", -"image": "https://randomuser.me/api/portraits/men/44.jpg", -"gender": "Male" -}, -{ -"first_name": "sara", -"last_name": "craig", -"image": "https://randomuser.me/api/portraits/women/8.jpg", -"gender": "Female" -}, -{ -"first_name": "steven", -"last_name": "stone", -"image": "https://randomuser.me/api/portraits/men/47.jpg", -"gender": "Male" -}, -{ -"first_name": "barb", -"last_name": "rodriquez", -"image": "https://randomuser.me/api/portraits/women/73.jpg", -"gender": "Female" -}, -{ -"first_name": "charlie", -"last_name": "king", -"image": "https://randomuser.me/api/portraits/men/79.jpg", -"gender": "Male" -}, -{ -"first_name": "jessica", -"last_name": "davis", -"image": "https://randomuser.me/api/portraits/women/26.jpg", -"gender": "Female" -}, -{ -"first_name": "lewis", -"last_name": "watson", -"image": "https://randomuser.me/api/portraits/men/56.jpg", -"gender": "Male" -}, -{ -"first_name": "charlotte", -"last_name": "johnson", -"image": "https://randomuser.me/api/portraits/women/46.jpg", -"gender": "Female" -}, -{ -"first_name": "danielle", -"last_name": "bell", -"image": "https://randomuser.me/api/portraits/women/54.jpg", -"gender": "Female" -}, -{ -"first_name": "kristin", -"last_name": "dixon", -"image": "https://randomuser.me/api/portraits/women/23.jpg", -"gender": "Female" -}, -{ -"first_name": "andrea", -"last_name": "thompson", -"image": "https://randomuser.me/api/portraits/women/54.jpg", -"gender": "Female" -}, -{ -"first_name": "ashley", -"last_name": "andrews", -"image": "https://randomuser.me/api/portraits/women/46.jpg", -"gender": "Female" -}, -{ -"first_name": "sharon", -"last_name": "martinez", -"image": "https://randomuser.me/api/portraits/women/6.jpg", -"gender": "Female" -}, -{ -"first_name": "tristan", -"last_name": "cunningham", -"image": "https://randomuser.me/api/portraits/men/62.jpg", -"gender": "Male" -}, -{ -"first_name": "carol", -"last_name": "chavez", -"image": "https://randomuser.me/api/portraits/women/85.jpg", -"gender": "Female" -}, -{ -"first_name": "lauren", -"last_name": "hudson", -"image": "https://randomuser.me/api/portraits/women/88.jpg", -"gender": "Female" -}, -{ -"first_name": "guy", -"last_name": "robertson", -"image": "https://randomuser.me/api/portraits/men/78.jpg", -"gender": "Male" -}, -{ -"first_name": "debra", -"last_name": "long", -"image": "https://randomuser.me/api/portraits/women/23.jpg", -"gender": "Female" -}, -{ -"first_name": "taylor", -"last_name": "carpenter", -"image": "https://randomuser.me/api/portraits/men/0.jpg", -"gender": "Male" -}, -{ -"first_name": "eetu", -"last_name": "annala", -"image": "https://randomuser.me/api/portraits/men/31.jpg", -"gender": "Male" -}, -{ -"first_name": "oliver", -"last_name": "moilanen", -"image": "https://randomuser.me/api/portraits/men/14.jpg", -"gender": "Male" -}, -{ -"first_name": "leo", -"last_name": "maunu", -"image": "https://randomuser.me/api/portraits/men/72.jpg", -"gender": "Male" -}, -{ -"first_name": "iiris", -"last_name": "kalas", -"image": "https://randomuser.me/api/portraits/women/49.jpg", -"gender": "Female" -}, -{ -"first_name": "aada", -"last_name": "kinnunen", -"image": "https://randomuser.me/api/portraits/women/64.jpg", -"gender": "Female" -}, -{ -"first_name": "topias", -"last_name": "walli", -"image": "https://randomuser.me/api/portraits/men/58.jpg", -"gender": "Male" -}, -{ -"first_name": "viivi", -"last_name": "toivonen", -"image": "https://randomuser.me/api/portraits/women/16.jpg", -"gender": "Female" -}, -{ -"first_name": "iina", -"last_name": "makinen", -"image": "https://randomuser.me/api/portraits/women/44.jpg", -"gender": "Female" -}, -{ -"first_name": "lumi", -"last_name": "tuominen", -"image": "https://randomuser.me/api/portraits/women/11.jpg", -"gender": "Female" -}, -{ -"first_name": "ellen", -"last_name": "koski", -"image": "https://randomuser.me/api/portraits/women/22.jpg", -"gender": "Female" -}, -{ -"first_name": "onni", -"last_name": "laurila", -"image": "https://randomuser.me/api/portraits/men/74.jpg", -"gender": "Male" -}, -{ -"first_name": "eevi", -"last_name": "niskanen", -"image": "https://randomuser.me/api/portraits/women/72.jpg", -"gender": "Female" -}, -{ -"first_name": "julius", -"last_name": "maijala", -"image": "https://randomuser.me/api/portraits/men/8.jpg", -"gender": "Male" -}, -{ -"first_name": "sofia", -"last_name": "tuomi", -"image": "https://randomuser.me/api/portraits/women/1.jpg", -"gender": "Female" -}, -{ -"first_name": "oliver", -"last_name": "jarvela", -"image": "https://randomuser.me/api/portraits/men/60.jpg", -"gender": "Male" -}, -{ -"first_name": "luukas", -"last_name": "mikkola", -"image": "https://randomuser.me/api/portraits/men/90.jpg", -"gender": "Male" -}, -{ -"first_name": "amanda", -"last_name": "anttila", -"image": "https://randomuser.me/api/portraits/women/65.jpg", -"gender": "Female" -}, -{ -"first_name": "ella", -"last_name": "sakala", -"image": "https://randomuser.me/api/portraits/women/79.jpg", -"gender": "Female" -}, -{ -"first_name": "siiri", -"last_name": "kinnunen", -"image": "https://randomuser.me/api/portraits/women/37.jpg", -"gender": "Female" -}, -{ -"first_name": "joona", -"last_name": "korhonen", -"image": "https://randomuser.me/api/portraits/men/87.jpg", -"gender": "Male" -}, -{ -"first_name": "topias", -"last_name": "korpi", -"image": "https://randomuser.me/api/portraits/men/75.jpg", -"gender": "Male" -}, -{ -"first_name": "mikael", -"last_name": "remes", -"image": "https://randomuser.me/api/portraits/men/89.jpg", -"gender": "Male" -}, -{ -"first_name": "veera", -"last_name": "peltola", -"image": "https://randomuser.me/api/portraits/women/69.jpg", -"gender": "Female" -}, -{ -"first_name": "emil", -"last_name": "makela", -"image": "https://randomuser.me/api/portraits/men/98.jpg", -"gender": "Male" -}, -{ -"first_name": "luukas", -"last_name": "kujala", -"image": "https://randomuser.me/api/portraits/men/83.jpg", -"gender": "Male" -}, -{ -"first_name": "eemil", -"last_name": "honkala", -"image": "https://randomuser.me/api/portraits/men/85.jpg", -"gender": "Male" -}, -{ -"first_name": "peetu", -"last_name": "kalm", -"image": "https://randomuser.me/api/portraits/men/17.jpg", -"gender": "Male" -}, -{ -"first_name": "eemeli", -"last_name": "lehtonen", -"image": "https://randomuser.me/api/portraits/men/55.jpg", -"gender": "Male" -}, -{ -"first_name": "viivi", -"last_name": "koistinen", -"image": "https://randomuser.me/api/portraits/women/53.jpg", -"gender": "Female" -}, -{ -"first_name": "elli", -"last_name": "savela", -"image": "https://randomuser.me/api/portraits/women/77.jpg", -"gender": "Female" -}, -{ -"first_name": "venla", -"last_name": "walli", -"image": "https://randomuser.me/api/portraits/women/52.jpg", -"gender": "Female" -}, -{ -"first_name": "amanda", -"last_name": "wuollet", -"image": "https://randomuser.me/api/portraits/women/11.jpg", -"gender": "Female" -}, -{ -"first_name": "valtteri", -"last_name": "hokkanen", -"image": "https://randomuser.me/api/portraits/men/30.jpg", -"gender": "Male" -}, -{ -"first_name": "veera", -"last_name": "maki", -"image": "https://randomuser.me/api/portraits/women/34.jpg", -"gender": "Female" -}, -{ -"first_name": "kerttu", -"last_name": "maunu", -"image": "https://randomuser.me/api/portraits/women/1.jpg", -"gender": "Female" -}, -{ -"first_name": "nella", -"last_name": "hanka", -"image": "https://randomuser.me/api/portraits/women/70.jpg", -"gender": "Female" -}, -{ -"first_name": "iiris", -"last_name": "hakala", -"image": "https://randomuser.me/api/portraits/women/33.jpg", -"gender": "Female" -}, -{ -"first_name": "viivi", -"last_name": "ojala", -"image": "https://randomuser.me/api/portraits/women/69.jpg", -"gender": "Female" -}, -{ -"first_name": "iina", -"last_name": "peura", -"image": "https://randomuser.me/api/portraits/women/22.jpg", -"gender": "Female" -}, -{ -"first_name": "samuel", -"last_name": "mattila", -"image": "https://randomuser.me/api/portraits/men/88.jpg", -"gender": "Male" -}, -{ -"first_name": "julius", -"last_name": "kumpula", -"image": "https://randomuser.me/api/portraits/men/26.jpg", -"gender": "Male" -}, -{ -"first_name": "nooa", -"last_name": "haapala", -"image": "https://randomuser.me/api/portraits/men/77.jpg", -"gender": "Male" -}, -{ -"first_name": "elias", -"last_name": "leppo", -"image": "https://randomuser.me/api/portraits/men/50.jpg", -"gender": "Male" -}, -{ -"first_name": "niklas", -"last_name": "elo", -"image": "https://randomuser.me/api/portraits/men/64.jpg", -"gender": "Male" -}, -{ -"first_name": "olivia", -"last_name": "nurmi", -"image": "https://randomuser.me/api/portraits/women/82.jpg", -"gender": "Female" -}, -{ -"first_name": "milja", -"last_name": "lassila", -"image": "https://randomuser.me/api/portraits/women/47.jpg", -"gender": "Female" -}, -{ -"first_name": "daniel", -"last_name": "kalas", -"image": "https://randomuser.me/api/portraits/men/53.jpg", -"gender": "Male" -}, -{ -"first_name": "enni", -"last_name": "ramo", -"image": "https://randomuser.me/api/portraits/women/18.jpg", -"gender": "Female" -}, -{ -"first_name": "matilda", -"last_name": "salmi", -"image": "https://randomuser.me/api/portraits/women/84.jpg", -"gender": "Female" -}, -{ -"first_name": "valtteri", -"last_name": "wirta", -"image": "https://randomuser.me/api/portraits/men/26.jpg", -"gender": "Male" -}, -{ -"first_name": "julius", -"last_name": "maijala", -"image": "https://randomuser.me/api/portraits/men/39.jpg", -"gender": "Male" -}, -{ -"first_name": "kerttu", -"last_name": "peltola", -"image": "https://randomuser.me/api/portraits/women/39.jpg", -"gender": "Female" -}, -{ -"first_name": "aada", -"last_name": "kokko", -"image": "https://randomuser.me/api/portraits/women/26.jpg", -"gender": "Female" -}, -{ -"first_name": "elsa", -"last_name": "niska", -"image": "https://randomuser.me/api/portraits/women/26.jpg", -"gender": "Female" -}, -{ -"first_name": "ella", -"last_name": "kalm", -"image": "https://randomuser.me/api/portraits/women/61.jpg", -"gender": "Female" -}, -{ -"first_name": "lilja", -"last_name": "heinonen", -"image": "https://randomuser.me/api/portraits/women/65.jpg", -"gender": "Female" -}, -{ -"first_name": "akseli", -"last_name": "laakso", -"image": "https://randomuser.me/api/portraits/men/64.jpg", -"gender": "Male" -}, -{ -"first_name": "lotta", -"last_name": "saarela", -"image": "https://randomuser.me/api/portraits/women/69.jpg", -"gender": "Female" -}, -{ -"first_name": "leo", -"last_name": "polon", -"image": "https://randomuser.me/api/portraits/men/5.jpg", -"gender": "Male" -}, -{ -"first_name": "aleksi", -"last_name": "wuollet", -"image": "https://randomuser.me/api/portraits/men/87.jpg", -"gender": "Male" -}, -{ -"first_name": "eemil", -"last_name": "kalas", -"image": "https://randomuser.me/api/portraits/men/6.jpg", -"gender": "Male" -}, -{ -"first_name": "emmi", -"last_name": "koistinen", -"image": "https://randomuser.me/api/portraits/women/66.jpg", -"gender": "Female" -}, -{ -"first_name": "väinö", -"last_name": "halla", -"image": "https://randomuser.me/api/portraits/men/65.jpg", -"gender": "Male" -}, -{ -"first_name": "eemil", -"last_name": "heikkila", -"image": "https://randomuser.me/api/portraits/men/18.jpg", -"gender": "Male" -}, -{ -"first_name": "amanda", -"last_name": "lakso", -"image": "https://randomuser.me/api/portraits/women/29.jpg", -"gender": "Female" -}, -{ -"first_name": "vilho", -"last_name": "kivela", -"image": "https://randomuser.me/api/portraits/men/19.jpg", -"gender": "Male" -}, -{ -"first_name": "peppi", -"last_name": "lehtinen", -"image": "https://randomuser.me/api/portraits/women/80.jpg", -"gender": "Female" -}, -{ -"first_name": "onni", -"last_name": "lehtinen", -"image": "https://randomuser.me/api/portraits/men/0.jpg", -"gender": "Male" -}, -{ -"first_name": "onni", -"last_name": "ahonen", -"image": "https://randomuser.me/api/portraits/men/49.jpg", -"gender": "Male" -}, -{ -"first_name": "venla", -"last_name": "ranta", -"image": "https://randomuser.me/api/portraits/women/0.jpg", -"gender": "Female" -}, -{ -"first_name": "ronja", -"last_name": "korhonen", -"image": "https://randomuser.me/api/portraits/women/69.jpg", -"gender": "Female" -}, -{ -"first_name": "emmi", -"last_name": "niva", -"image": "https://randomuser.me/api/portraits/women/65.jpg", -"gender": "Female" -}, -{ -"first_name": "oskari", -"last_name": "leppanen", -"image": "https://randomuser.me/api/portraits/men/43.jpg", -"gender": "Male" -}, -{ -"first_name": "arttu", -"last_name": "heinonen", -"image": "https://randomuser.me/api/portraits/men/94.jpg", -"gender": "Male" -}, -{ -"first_name": "toivo", -"last_name": "makela", -"image": "https://randomuser.me/api/portraits/men/23.jpg", -"gender": "Male" -}, -{ -"first_name": "otto", -"last_name": "leino", -"image": "https://randomuser.me/api/portraits/men/51.jpg", -"gender": "Male" -}, -{ -"first_name": "milla", -"last_name": "kokko", -"image": "https://randomuser.me/api/portraits/women/66.jpg", -"gender": "Female" -}, -{ -"first_name": "konsta", -"last_name": "lehto", -"image": "https://randomuser.me/api/portraits/men/29.jpg", -"gender": "Male" -}, -{ -"first_name": "eeli", -"last_name": "heikkinen", -"image": "https://randomuser.me/api/portraits/men/50.jpg", -"gender": "Male" -}, -{ -"first_name": "matilda", -"last_name": "tanner", -"image": "https://randomuser.me/api/portraits/women/2.jpg", -"gender": "Female" -}, -{ -"first_name": "elias", -"last_name": "kivisto", -"image": "https://randomuser.me/api/portraits/men/40.jpg", -"gender": "Male" -}, -{ -"first_name": "akseli", -"last_name": "wirta", -"image": "https://randomuser.me/api/portraits/men/90.jpg", -"gender": "Male" -}, -{ -"first_name": "leevi", -"last_name": "kallio", -"image": "https://randomuser.me/api/portraits/men/89.jpg", -"gender": "Male" -}, -{ -"first_name": "emilia", -"last_name": "pelto", -"image": "https://randomuser.me/api/portraits/women/0.jpg", -"gender": "Female" -}, -{ -"first_name": "niilo", -"last_name": "keranen", -"image": "https://randomuser.me/api/portraits/men/29.jpg", -"gender": "Male" -}, -{ -"first_name": "mikael", -"last_name": "wainio", -"image": "https://randomuser.me/api/portraits/men/85.jpg", -"gender": "Male" -}, -{ -"first_name": "elias", -"last_name": "saksa", -"image": "https://randomuser.me/api/portraits/men/53.jpg", -"gender": "Male" -}, -{ -"first_name": "aatu", -"last_name": "erkkila", -"image": "https://randomuser.me/api/portraits/men/6.jpg", -"gender": "Male" -}, -{ -"first_name": "arttu", -"last_name": "jarvela", -"image": "https://randomuser.me/api/portraits/men/49.jpg", -"gender": "Male" -}, -{ -"first_name": "matilda", -"last_name": "lassila", -"image": "https://randomuser.me/api/portraits/women/46.jpg", -"gender": "Female" -}, -{ -"first_name": "alisa", -"last_name": "waara", -"image": "https://randomuser.me/api/portraits/women/67.jpg", -"gender": "Female" -}, -{ -"first_name": "emilia", -"last_name": "saksa", -"image": "https://randomuser.me/api/portraits/women/66.jpg", -"gender": "Female" -}, -{ -"first_name": "valtteri", -"last_name": "tikkanen", -"image": "https://randomuser.me/api/portraits/men/88.jpg", -"gender": "Male" -}, -{ -"first_name": "konsta", -"last_name": "rantala", -"image": "https://randomuser.me/api/portraits/men/50.jpg", -"gender": "Male" -}, -{ -"first_name": "minttu", -"last_name": "murto", -"image": "https://randomuser.me/api/portraits/women/14.jpg", -"gender": "Female" -}, -{ -"first_name": "vilma", -"last_name": "hatala", -"image": "https://randomuser.me/api/portraits/women/60.jpg", -"gender": "Female" -}, -{ -"first_name": "anni", -"last_name": "linna", -"image": "https://randomuser.me/api/portraits/women/59.jpg", -"gender": "Female" -}, -{ -"first_name": "niklas", -"last_name": "hautala", -"image": "https://randomuser.me/api/portraits/men/7.jpg", -"gender": "Male" -}, -{ -"first_name": "niilo", -"last_name": "lehtinen", -"image": "https://randomuser.me/api/portraits/men/54.jpg", -"gender": "Male" -}, -{ -"first_name": "oona", -"last_name": "saarinen", -"image": "https://randomuser.me/api/portraits/women/71.jpg", -"gender": "Female" -}, -{ -"first_name": "constance", -"last_name": "marie", -"image": "https://randomuser.me/api/portraits/women/40.jpg", -"gender": "Female" -}, -{ -"first_name": "charles", -"last_name": "pierre", -"image": "https://randomuser.me/api/portraits/men/96.jpg", -"gender": "Male" -}, -{ -"first_name": "bérénice", -"last_name": "leclerc", -"image": "https://randomuser.me/api/portraits/women/39.jpg", -"gender": "Female" -}, -{ -"first_name": "clémence", -"last_name": "arnaud", -"image": "https://randomuser.me/api/portraits/women/48.jpg", -"gender": "Female" -}, -{ -"first_name": "melvin", -"last_name": "lemoine", -"image": "https://randomuser.me/api/portraits/men/47.jpg", -"gender": "Male" -}, -{ -"first_name": "marceau", -"last_name": "joly", -"image": "https://randomuser.me/api/portraits/men/56.jpg", -"gender": "Male" -}, -{ -"first_name": "garance", -"last_name": "mathieu", -"image": "https://randomuser.me/api/portraits/women/87.jpg", -"gender": "Female" -}, -{ -"first_name": "angèle", -"last_name": "perrin", -"image": "https://randomuser.me/api/portraits/women/88.jpg", -"gender": "Female" -}, -{ -"first_name": "pauline", -"last_name": "simon", -"image": "https://randomuser.me/api/portraits/women/82.jpg", -"gender": "Female" -}, -{ -"first_name": "apolline", -"last_name": "laurent", -"image": "https://randomuser.me/api/portraits/women/27.jpg", -"gender": "Female" -}, -{ -"first_name": "luca", -"last_name": "lefevre", -"image": "https://randomuser.me/api/portraits/men/40.jpg", -"gender": "Male" -}, -{ -"first_name": "bastien", -"last_name": "roger", -"image": "https://randomuser.me/api/portraits/men/73.jpg", -"gender": "Male" -}, -{ -"first_name": "marie", -"last_name": "rodriguez", -"image": "https://randomuser.me/api/portraits/women/18.jpg", -"gender": "Female" -}, -{ -"first_name": "tristan", -"last_name": "renaud", -"image": "https://randomuser.me/api/portraits/men/41.jpg", -"gender": "Male" -}, -{ -"first_name": "eva", -"last_name": "philippe", -"image": "https://randomuser.me/api/portraits/women/26.jpg", -"gender": "Female" -}, -{ -"first_name": "coline", -"last_name": "dufour", -"image": "https://randomuser.me/api/portraits/women/64.jpg", -"gender": "Female" -}, -{ -"first_name": "marilou", -"last_name": "adam", -"image": "https://randomuser.me/api/portraits/women/53.jpg", -"gender": "Female" -}, -{ -"first_name": "lia", -"last_name": "renard", -"image": "https://randomuser.me/api/portraits/women/88.jpg", -"gender": "Female" -}, -{ -"first_name": "timothee", -"last_name": "rolland", -"image": "https://randomuser.me/api/portraits/men/75.jpg", -"gender": "Male" -}, -{ -"first_name": "hélèna", -"last_name": "boyer", -"image": "https://randomuser.me/api/portraits/women/8.jpg", -"gender": "Female" -}, -{ -"first_name": "mélody", -"last_name": "andre", -"image": "https://randomuser.me/api/portraits/women/75.jpg", -"gender": "Female" -}, -{ -"first_name": "jeanne", -"last_name": "duval", -"image": "https://randomuser.me/api/portraits/women/44.jpg", -"gender": "Female" -}, -{ -"first_name": "elias", -"last_name": "dupont", -"image": "https://randomuser.me/api/portraits/men/60.jpg", -"gender": "Male" -}, -{ -"first_name": "estelle", -"last_name": "bernard", -"image": "https://randomuser.me/api/portraits/women/23.jpg", -"gender": "Female" -}, -{ -"first_name": "roxane", -"last_name": "garnier", -"image": "https://randomuser.me/api/portraits/women/14.jpg", -"gender": "Female" -}, -{ -"first_name": "maëva", -"last_name": "guerin", -"image": "https://randomuser.me/api/portraits/women/44.jpg", -"gender": "Female" -}, -{ -"first_name": "liam", -"last_name": "carpentier", -"image": "https://randomuser.me/api/portraits/men/41.jpg", -"gender": "Male" -}, -{ -"first_name": "théo", -"last_name": "gaillard", -"image": "https://randomuser.me/api/portraits/men/40.jpg", -"gender": "Male" -}, -{ -"first_name": "angelina", -"last_name": "clement", -"image": "https://randomuser.me/api/portraits/women/53.jpg", -"gender": "Female" -}, -{ -"first_name": "emma", -"last_name": "bertrand", -"image": "https://randomuser.me/api/portraits/women/86.jpg", -"gender": "Female" -}, -{ -"first_name": "charles", -"last_name": "rolland", -"image": "https://randomuser.me/api/portraits/men/14.jpg", -"gender": "Male" -}, -{ -"first_name": "nolan", -"last_name": "gautier", -"image": "https://randomuser.me/api/portraits/men/6.jpg", -"gender": "Male" -}, -{ -"first_name": "agathe", -"last_name": "menard", -"image": "https://randomuser.me/api/portraits/women/69.jpg", -"gender": "Female" -}, -{ -"first_name": "gaëtan", -"last_name": "leclerc", -"image": "https://randomuser.me/api/portraits/men/60.jpg", -"gender": "Male" -}, -{ -"first_name": "clarisse", -"last_name": "lemaire", -"image": "https://randomuser.me/api/portraits/women/21.jpg", -"gender": "Female" -}, -{ -"first_name": "samuel", -"last_name": "garnier", -"image": "https://randomuser.me/api/portraits/men/16.jpg", -"gender": "Male" -}, -{ -"first_name": "eden", -"last_name": "fontai", -"image": "https://randomuser.me/api/portraits/women/17.jpg", -"gender": "Female" -}, -{ -"first_name": "maëva", -"last_name": "pierre", -"image": "https://randomuser.me/api/portraits/women/19.jpg", -"gender": "Female" -}, -{ -"first_name": "thomas", -"last_name": "barbier", -"image": "https://randomuser.me/api/portraits/men/31.jpg", -"gender": "Male" -}, -{ -"first_name": "lily", -"last_name": "lefebvre", -"image": "https://randomuser.me/api/portraits/women/76.jpg", -"gender": "Female" -}, -{ -"first_name": "lise", -"last_name": "perez", -"image": "https://randomuser.me/api/portraits/women/74.jpg", -"gender": "Female" -}, -{ -"first_name": "mila", -"last_name": "moulin", -"image": "https://randomuser.me/api/portraits/women/43.jpg", -"gender": "Female" -}, -{ -"first_name": "dylan", -"last_name": "picard", -"image": "https://randomuser.me/api/portraits/men/37.jpg", -"gender": "Male" -}, -{ -"first_name": "amandine", -"last_name": "rodriguez", -"image": "https://randomuser.me/api/portraits/women/65.jpg", -"gender": "Female" -}, -{ -"first_name": "diego", -"last_name": "girard", -"image": "https://randomuser.me/api/portraits/men/84.jpg", -"gender": "Male" -}, -{ -"first_name": "elouan", -"last_name": "garnier", -"image": "https://randomuser.me/api/portraits/men/94.jpg", -"gender": "Male" -}, -{ -"first_name": "apolline", -"last_name": "fleury", -"image": "https://randomuser.me/api/portraits/women/65.jpg", -"gender": "Female" -}, -{ -"first_name": "coline", -"last_name": "menard", -"image": "https://randomuser.me/api/portraits/women/83.jpg", -"gender": "Female" -}, -{ -"first_name": "maëly", -"last_name": "le gall", -"image": "https://randomuser.me/api/portraits/women/60.jpg", -"gender": "Female" -}, -{ -"first_name": "justin", -"last_name": "robert", -"image": "https://randomuser.me/api/portraits/men/20.jpg", -"gender": "Male" -}, -{ -"first_name": "ryan", -"last_name": "faure", -"image": "https://randomuser.me/api/portraits/men/16.jpg", -"gender": "Male" -}, -{ -"first_name": "ninon", -"last_name": "brunet", -"image": "https://randomuser.me/api/portraits/women/68.jpg", -"gender": "Female" -}, -{ -"first_name": "tessa", -"last_name": "garnier", -"image": "https://randomuser.me/api/portraits/women/54.jpg", -"gender": "Female" -}, -{ -"first_name": "ryan", -"last_name": "bonnet", -"image": "https://randomuser.me/api/portraits/men/28.jpg", -"gender": "Male" -}, -{ -"first_name": "aurélien", -"last_name": "andre", -"image": "https://randomuser.me/api/portraits/men/29.jpg", -"gender": "Male" -}, -{ -"first_name": "clément", -"last_name": "dumas", -"image": "https://randomuser.me/api/portraits/men/10.jpg", -"gender": "Male" -}, -{ -"first_name": "alexis", -"last_name": "fournier", -"image": "https://randomuser.me/api/portraits/men/83.jpg", -"gender": "Male" -}, -{ -"first_name": "valentin", -"last_name": "lecomte", -"image": "https://randomuser.me/api/portraits/men/44.jpg", -"gender": "Male" -}, -{ -"first_name": "florian", -"last_name": "olivier", -"image": "https://randomuser.me/api/portraits/men/36.jpg", -"gender": "Male" -}, -{ -"first_name": "ewen", -"last_name": "lefebvre", -"image": "https://randomuser.me/api/portraits/men/32.jpg", -"gender": "Male" -}, -{ -"first_name": "titouan", -"last_name": "charles", -"image": "https://randomuser.me/api/portraits/men/59.jpg", -"gender": "Male" -}, -{ -"first_name": "lila", -"last_name": "aubert", -"image": "https://randomuser.me/api/portraits/women/6.jpg", -"gender": "Female" -}, -{ -"first_name": "charline", -"last_name": "caron", -"image": "https://randomuser.me/api/portraits/women/49.jpg", -"gender": "Female" -}, -{ -"first_name": "soren", -"last_name": "le gall", -"image": "https://randomuser.me/api/portraits/men/77.jpg", -"gender": "Male" -}, -{ -"first_name": "fanny", -"last_name": "louis", -"image": "https://randomuser.me/api/portraits/women/90.jpg", -"gender": "Female" -}, -{ -"first_name": "julie", -"last_name": "adam", -"image": "https://randomuser.me/api/portraits/women/34.jpg", -"gender": "Female" -}, -{ -"first_name": "louka", -"last_name": "boyer", -"image": "https://randomuser.me/api/portraits/men/98.jpg", -"gender": "Male" -} -] diff --git a/erpnext/demo/data/room.json b/erpnext/demo/data/room.json deleted file mode 100644 index 82f0868b5a..0000000000 --- a/erpnext/demo/data/room.json +++ /dev/null @@ -1,122 +0,0 @@ -[ - { - "doctype": "Room", - "room_name": "Lecture Hall 1", - "room_number": "101", - "seating_capacity": 80 - }, - { - "doctype": "Room", - "room_name": "Lecture Hall 2", - "room_number": "102", - "seating_capacity": 80 - }, - { - "doctype": "Room", - "room_name": "Lecture Hall 3", - "room_number": "103", - "seating_capacity": 80 - }, - { - "doctype": "Room", - "room_name": "Lecture Hall 4", - "room_number": "104", - "seating_capacity": 80 - }, - { - "doctype": "Room", - "room_name": "Lecture Hall 4", - "room_number": "104", - "seating_capacity": 80 - }, - { - "doctype": "Room", - "room_name": "Lecture Hall 5", - "room_number": "201", - "seating_capacity": 120 - }, - { - "doctype": "Room", - "room_name": "Lecture Hall 6", - "room_number": "202", - "seating_capacity": 120 - }, - { - "doctype": "Room", - "room_name": "Lecture Hall 7", - "room_number": "203", - "seating_capacity": 120 - }, - { - "doctype": "Room", - "room_name": "Computer Lab 1", - "room_number": "301", - "seating_capacity": 40 - }, - { - "doctype": "Room", - "room_name": "Computer Lab 2", - "room_number": "302", - "seating_capacity": 60 - }, - { - "doctype": "Room", - "room_name": "Seminar Hall 1", - "room_number": "303", - "seating_capacity": 240 - }, - { - "doctype": "Room", - "room_name": "Auditorium", - "room_number": "400", - "seating_capacity": 450 - }, - { - "doctype": "Room", - "room_name": "Exam hall 1", - "room_number": "560", - "seating_capacity": 70 - }, - { - "doctype": "Room", - "room_name": "Exam hall 2", - "room_number": "561", - "seating_capacity": 70 - }, - { - "doctype": "Room", - "room_name": "Exam hall 2", - "room_number": "562", - "seating_capacity": 70 - }, - { - "doctype": "Room", - "room_name": "Exam hall 3", - "room_number": "563", - "seating_capacity": 70 - }, - { - "doctype": "Room", - "room_name": "Exam hall 4", - "room_number": "564", - "seating_capacity": 70 - }, - { - "doctype": "Room", - "room_name": "Exam hall 5", - "room_number": "565", - "seating_capacity": 70 - }, - { - "doctype": "Room", - "room_name": "Exam hall 6", - "room_number": "566", - "seating_capacity": 70 - }, - { - "doctype": "Room", - "room_name": "Exam hall 7", - "room_number": "567", - "seating_capacity": 70 - } -] \ No newline at end of file diff --git a/erpnext/demo/data/student_batch_name.json b/erpnext/demo/data/student_batch_name.json deleted file mode 100644 index ef3f18dcf2..0000000000 --- a/erpnext/demo/data/student_batch_name.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "doctype": "Student Batch Name", - "batch_name": "Section-A" - }, - { - "doctype": "Student Batch Name", - "batch_name": "Section-B" - } -] \ No newline at end of file diff --git a/erpnext/demo/data/user.json b/erpnext/demo/data/user.json deleted file mode 100644 index 9ee5e780ac..0000000000 --- a/erpnext/demo/data/user.json +++ /dev/null @@ -1,112 +0,0 @@ -[ - { - "email": "test_demo@erpnext.com", - "first_name": "Test", - "last_name": "User" - }, - { - "email": "DianaPrince@example.com", - "first_name": "Diana", - "last_name": "Prince" - }, - { - "email": "ZatannaZatara@example.com", - "first_name": "Zatanna", - "last_name": "Zatara" - }, - { - "email": "HollyGranger@example.com", - "first_name": "Holly", - "last_name": "Granger" - }, - { - "email": "NeptuniaAquaria@example.com", - "first_name": "Neptunia", - "last_name": "Aquaria" - }, - { - "email": "ArthurCurry@example.com", - "first_name": "Arthur", - "last_name": "Curry" - }, - { - "email": "ThaliaAlGhul@example.com", - "first_name": "Thalia", - "last_name": "Al Ghul" - }, - { - "email": "MaxwellLord@example.com", - "first_name": "Maxwell", - "last_name": "Lord" - }, - { - "email": "GraceChoi@example.com", - "first_name": "Grace", - "last_name": "Choi" - }, - { - "email": "VandalSavage@example.com", - "first_name": "Vandal", - "last_name": "Savage" - }, - { - "email": "CaitlinSnow@example.com", - "first_name": "Caitlin", - "last_name": "Snow" - }, - { - "email": "RipHunter@example.com", - "first_name": "Rip", - "last_name": "Hunter" - }, - { - "email": "NicholasFury@example.com", - "first_name": "Nicholas", - "last_name": "Fury" - }, - { - "email": "PeterParker@example.com", - "first_name": "Peter", - "last_name": "Parker" - }, - { - "email": "JohnConstantine@example.com", - "first_name": "John", - "last_name": "Constantine" - }, - { - "email": "HalJordan@example.com", - "first_name": "Hal", - "last_name": "Jordan" - }, - { - "email": "VictorStone@example.com", - "first_name": "Victor", - "last_name": "Stone" - }, - { - "email": "BruceWayne@example.com", - "first_name": "Bruce", - "last_name": "Wayne" - }, - { - "email": "ClarkKent@example.com", - "first_name": "Clark", - "last_name": "Kent" - }, - { - "email": "BarryAllen@example.com", - "first_name": "Barry", - "last_name": "Allen" - }, - { - "email": "KaraZorEl@example.com", - "first_name": "Kara", - "last_name": "Zor El" - }, - { - "email": "demo@erpnext.com", - "first_name": "Demo", - "last_name": "User" - } -] \ No newline at end of file diff --git a/erpnext/demo/demo.py b/erpnext/demo/demo.py deleted file mode 100644 index 4a18a99f41..0000000000 --- a/erpnext/demo/demo.py +++ /dev/null @@ -1,97 +0,0 @@ -import sys - -import frappe -import frappe.utils - -import erpnext -from erpnext.demo.setup import education, manufacture, retail, setup_data -from erpnext.demo.user import accounts -from erpnext.demo.user import education as edu -from erpnext.demo.user import fixed_asset, hr, manufacturing, projects, purchase, sales, stock - -""" -Make a demo - -1. Start with a fresh account - -bench --site demo.erpnext.dev reinstall - -2. Install Demo - -bench --site demo.erpnext.dev execute erpnext.demo.demo.make - -3. If Demo breaks, to continue - -bench --site demo.erpnext.dev execute erpnext.demo.demo.simulate - -""" - -def make(domain='Manufacturing', days=100): - frappe.flags.domain = domain - frappe.flags.mute_emails = True - setup_data.setup(domain) - if domain== 'Manufacturing': - manufacture.setup_data() - elif domain == "Retail": - retail.setup_data() - elif domain== 'Education': - education.setup_data() - - site = frappe.local.site - frappe.destroy() - frappe.init(site) - frappe.connect() - - simulate(domain, days) - -def simulate(domain='Manufacturing', days=100): - runs_for = frappe.flags.runs_for or days - frappe.flags.company = erpnext.get_default_company() - frappe.flags.mute_emails = True - - if not frappe.flags.start_date: - # start date = 100 days back - frappe.flags.start_date = frappe.utils.add_days(frappe.utils.nowdate(), - -1 * runs_for) - - current_date = frappe.utils.getdate(frappe.flags.start_date) - - # continue? - demo_last_date = frappe.db.get_global('demo_last_date') - if demo_last_date: - current_date = frappe.utils.add_days(frappe.utils.getdate(demo_last_date), 1) - - # run till today - if not runs_for: - runs_for = frappe.utils.date_diff(frappe.utils.nowdate(), current_date) - # runs_for = 100 - - fixed_asset.work() - for i in range(runs_for): - sys.stdout.write("\rSimulating {0}: Day {1}".format( - current_date.strftime("%Y-%m-%d"), i)) - sys.stdout.flush() - frappe.flags.current_date = current_date - if current_date.weekday() in (5, 6): - current_date = frappe.utils.add_days(current_date, 1) - continue - try: - hr.work() - purchase.work() - stock.work() - accounts.work() - projects.run_projects(current_date) - sales.work(domain) - # run_messages() - - if domain=='Manufacturing': - manufacturing.work() - elif domain=='Education': - edu.work() - - except Exception: - frappe.db.set_global('demo_last_date', current_date) - raise - finally: - current_date = frappe.utils.add_days(current_date, 1) - frappe.db.commit() diff --git a/erpnext/demo/domains.py b/erpnext/demo/domains.py deleted file mode 100644 index 5fa181dfa4..0000000000 --- a/erpnext/demo/domains.py +++ /dev/null @@ -1,23 +0,0 @@ -data = { - 'Manufacturing': { - 'company_name': 'Wind Power LLC' - }, - 'Retail': { - 'company_name': 'Mobile Next', - }, - 'Distribution': { - 'company_name': 'Soltice Hardware', - }, - 'Services': { - 'company_name': 'Acme Consulting' - }, - 'Education': { - 'company_name': 'Whitmore College' - }, - 'Agriculture': { - 'company_name': 'Schrute Farms' - }, - 'Non Profit': { - 'company_name': 'Erpnext Foundation' - } -} diff --git a/erpnext/demo/setup/__init__.py b/erpnext/demo/setup/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/demo/setup/education.py b/erpnext/demo/setup/education.py deleted file mode 100644 index eb833f4e0c..0000000000 --- a/erpnext/demo/setup/education.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import json -import random -from datetime import datetime - -import frappe -from frappe.utils.make_random import get_random - -from erpnext.demo.setup.setup_data import import_json - - -def setup_data(): - frappe.flags.mute_emails = True - make_masters() - setup_item() - make_student_applicants() - make_student_group() - make_fees_category() - make_fees_structure() - make_assessment_groups() - frappe.db.commit() - frappe.clear_cache() - -def make_masters(): - import_json("Room") - import_json("Department") - import_json("Instructor") - import_json("Course") - import_json("Program") - import_json("Student Batch Name") - import_json("Assessment Criteria") - import_json("Grading Scale") - frappe.db.commit() - -def setup_item(): - items = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'item_education.json')).read()) - for i in items: - item = frappe.new_doc('Item') - item.update(i) - item.min_order_qty = random.randint(10, 30) - item.item_defaults[0].default_warehouse = frappe.get_all('Warehouse', - filters={'warehouse_name': item.item_defaults[0].default_warehouse}, limit=1)[0].name - item.insert() - -def make_student_applicants(): - blood_group = ["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"] - male_names = [] - female_names = [] - - file_path = get_json_path("Random Student Data") - with open(file_path, "r") as open_file: - random_student_data = json.loads(open_file.read()) - count = 1 - - for d in random_student_data: - if d.get('gender') == "Male": - male_names.append(d.get('first_name').title()) - - if d.get('gender') == "Female": - female_names.append(d.get('first_name').title()) - - for idx, d in enumerate(random_student_data): - student_applicant = frappe.new_doc("Student Applicant") - student_applicant.first_name = d.get('first_name').title() - student_applicant.last_name = d.get('last_name').title() - student_applicant.image = d.get('image') - student_applicant.gender = d.get('gender') - student_applicant.program = get_random("Program") - student_applicant.blood_group = random.choice(blood_group) - year = random.randint(1990, 1998) - month = random.randint(1, 12) - day = random.randint(1, 28) - student_applicant.date_of_birth = datetime(year, month, day) - student_applicant.mother_name = random.choice(female_names) + " " + d.get('last_name').title() - student_applicant.father_name = random.choice(male_names) + " " + d.get('last_name').title() - if student_applicant.gender == "Male": - student_applicant.middle_name = random.choice(male_names) - else: - student_applicant.middle_name = random.choice(female_names) - student_applicant.student_email_id = d.get('first_name') + "_" + \ - student_applicant.middle_name + "_" + d.get('last_name') + "@example.com" - if count <5: - student_applicant.insert() - frappe.db.commit() - else: - student_applicant.submit() - frappe.db.commit() - count+=1 - -def make_student_group(): - for term in frappe.db.get_list("Academic Term"): - for program in frappe.db.get_list("Program"): - sg_tool = frappe.new_doc("Student Group Creation Tool") - sg_tool.academic_year = "2017-18" - sg_tool.academic_term = term.name - sg_tool.program = program.name - for d in sg_tool.get_courses(): - d = frappe._dict(d) - student_group = frappe.new_doc("Student Group") - student_group.student_group_name = d.student_group_name - student_group.group_based_on = d.group_based_on - student_group.program = program.name - student_group.course = d.course - student_group.batch = d.batch - student_group.academic_term = term.name - student_group.academic_year = "2017-18" - student_group.save() - frappe.db.commit() - -def make_fees_category(): - fee_type = ["Tuition Fee", "Hostel Fee", "Logistics Fee", - "Medical Fee", "Mess Fee", "Security Deposit"] - - fee_desc = {"Tuition Fee" : "Curricular activities which includes books, notebooks and faculty charges" , - "Hostel Fee" : "Stay of students in institute premises", - "Logistics Fee" : "Lodging boarding of the students" , - "Medical Fee" : "Medical welfare of the students", - "Mess Fee" : "Food and beverages for your ward", - "Security Deposit" : "In case your child is found to have damaged institutes property" - } - - for i in fee_type: - fee_category = frappe.new_doc("Fee Category") - fee_category.category_name = i - fee_category.description = fee_desc[i] - fee_category.insert() - frappe.db.commit() - -def make_fees_structure(): - for d in frappe.db.get_list("Program"): - program = frappe.get_doc("Program", d.name) - for academic_term in ["2017-18 (Semester 1)", "2017-18 (Semester 2)", "2017-18 (Semester 3)"]: - fee_structure = frappe.new_doc("Fee Structure") - fee_structure.program = d.name - fee_structure.academic_term = random.choice(frappe.db.get_list("Academic Term")).name - for j in range(1,4): - temp = {"fees_category": random.choice(frappe.db.get_list("Fee Category")).name , "amount" : random.randint(500,1000)} - fee_structure.append("components", temp) - fee_structure.insert() - program.append("fees", {"academic_term": academic_term, "fee_structure": fee_structure.name, "amount": fee_structure.total_amount}) - program.save() - frappe.db.commit() - -def make_assessment_groups(): - for year in frappe.db.get_list("Academic Year"): - ag = frappe.new_doc('Assessment Group') - ag.assessment_group_name = year.name - ag.parent_assessment_group = "All Assessment Groups" - ag.is_group = 1 - ag.insert() - for term in frappe.db.get_list("Academic Term", filters = {"academic_year": year.name}): - ag1 = frappe.new_doc('Assessment Group') - ag1.assessment_group_name = term.name - ag1.parent_assessment_group = ag.name - ag1.is_group = 1 - ag1.insert() - for assessment_group in ['Term I', 'Term II']: - ag2 = frappe.new_doc('Assessment Group') - ag2.assessment_group_name = ag1.name + " " + assessment_group - ag2.parent_assessment_group = ag1.name - ag2.insert() - frappe.db.commit() - - -def get_json_path(doctype): - return frappe.get_app_path('erpnext', 'demo', 'data', frappe.scrub(doctype) + '.json') - -def weighted_choice(weights): - totals = [] - running_total = 0 - - for w in weights: - running_total += w - totals.append(running_total) - - rnd = random.random() * running_total - for i, total in enumerate(totals): - if rnd < total: - return i diff --git a/erpnext/demo/setup/manufacture.py b/erpnext/demo/setup/manufacture.py deleted file mode 100644 index fe1a1fb203..0000000000 --- a/erpnext/demo/setup/manufacture.py +++ /dev/null @@ -1,140 +0,0 @@ -import json -import random - -import frappe -from frappe.utils import add_days, nowdate - -from erpnext.demo.domains import data -from erpnext.demo.setup.setup_data import import_json - - -def setup_data(): - import_json("Location") - import_json("Asset Category") - setup_item() - setup_workstation() - setup_asset() - import_json('Operation') - setup_item_price() - show_item_groups_in_website() - import_json('BOM', submit=True) - frappe.db.commit() - frappe.clear_cache() - -def setup_workstation(): - workstations = [u'Drilling Machine 1', u'Lathe 1', u'Assembly Station 1', u'Assembly Station 2', u'Packing and Testing Station'] - for w in workstations: - frappe.get_doc({ - "doctype": "Workstation", - "workstation_name": w, - "holiday_list": frappe.get_all("Holiday List")[0].name, - "hour_rate_consumable": int(random.random() * 20), - "hour_rate_electricity": int(random.random() * 10), - "hour_rate_labour": int(random.random() * 40), - "hour_rate_rent": int(random.random() * 10), - "working_hours": [ - { - "enabled": 1, - "start_time": "8:00:00", - "end_time": "15:00:00" - } - ] - }).insert() - -def show_item_groups_in_website(): - """set show_in_website=1 for Item Groups""" - products = frappe.get_doc("Item Group", "Products") - products.show_in_website = 1 - products.route = 'products' - products.save() - -def setup_asset(): - assets = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'asset.json')).read()) - for d in assets: - asset = frappe.new_doc('Asset') - asset.update(d) - asset.purchase_date = add_days(nowdate(), -random.randint(20, 1500)) - asset.next_depreciation_date = add_days(asset.purchase_date, 30) - asset.warehouse = "Stores - WPL" - asset.set_missing_values() - asset.make_depreciation_schedule() - asset.flags.ignore_validate = True - asset.flags.ignore_mandatory = True - asset.save() - asset.submit() - -def setup_item(): - items = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'item.json')).read()) - for i in items: - item = frappe.new_doc('Item') - item.update(i) - if hasattr(item, 'item_defaults') and item.item_defaults[0].default_warehouse: - item.item_defaults[0].company = data.get("Manufacturing").get('company_name') - warehouse = frappe.get_all('Warehouse', filters={'warehouse_name': item.item_defaults[0].default_warehouse}, limit=1) - if warehouse: - item.item_defaults[0].default_warehouse = warehouse[0].name - item.insert() - -def setup_product_bundle(): - frappe.get_doc({ - 'doctype': 'Product Bundle', - 'new_item_code': 'Wind Mill A Series with Spare Bearing', - 'items': [ - {'item_code': 'Wind Mill A Series', 'qty': 1}, - {'item_code': 'Bearing Collar', 'qty': 1}, - {'item_code': 'Bearing Assembly', 'qty': 1}, - ] - }).insert() - -def setup_item_price(): - frappe.db.sql("delete from `tabItem Price`") - - standard_selling = { - "Base Bearing Plate": 28, - "Base Plate": 21, - "Bearing Assembly": 300, - "Bearing Block": 14, - "Bearing Collar": 103.6, - "Bearing Pipe": 63, - "Blade Rib": 46.2, - "Disc Collars": 42, - "External Disc": 56, - "Internal Disc": 70, - "Shaft": 340, - "Stand": 400, - "Upper Bearing Plate": 300, - "Wind Mill A Series": 320, - "Wind Mill A Series with Spare Bearing": 750, - "Wind MIll C Series": 400, - "Wind Turbine": 400, - "Wing Sheet": 30.8 - } - - standard_buying = { - "Base Bearing Plate": 20, - "Base Plate": 28, - "Base Plate Un Painted": 16, - "Bearing Block": 13, - "Bearing Collar": 96.4, - "Bearing Pipe": 55, - "Blade Rib": 38, - "Disc Collars": 34, - "External Disc": 50, - "Internal Disc": 60, - "Shaft": 250, - "Stand": 300, - "Upper Bearing Plate": 200, - "Wing Sheet": 25 - } - - for price_list in ("standard_buying", "standard_selling"): - for item, rate in locals().get(price_list).items(): - frappe.get_doc({ - "doctype": "Item Price", - "price_list": price_list.replace("_", " ").title(), - "item_code": item, - "selling": 1 if price_list=="standard_selling" else 0, - "buying": 1 if price_list=="standard_buying" else 0, - "price_list_rate": rate, - "currency": "USD" - }).insert() diff --git a/erpnext/demo/setup/retail.py b/erpnext/demo/setup/retail.py deleted file mode 100644 index 0469264da1..0000000000 --- a/erpnext/demo/setup/retail.py +++ /dev/null @@ -1,62 +0,0 @@ -import json - -import frappe - -from erpnext.demo.domains import data - - -def setup_data(): - setup_item() - setup_item_price() - frappe.db.commit() - frappe.clear_cache() - -def setup_item(): - items = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'item.json')).read()) - for i in items: - if not i.get("domain") == "Retail": continue - item = frappe.new_doc('Item') - item.update(i) - if hasattr(item, 'item_defaults') and item.item_defaults[0].default_warehouse: - item.item_defaults[0].company = data.get("Retail").get('company_name') - warehouse = frappe.get_all('Warehouse', filters={'warehouse_name': item.item_defaults[0].default_warehouse}, limit=1) - if warehouse: - item.item_defaults[0].default_warehouse = warehouse[0].name - item.insert() - -def setup_item_price(): - frappe.db.sql("delete from `tabItem Price`") - - standard_selling = { - "OnePlus 6": 579, - "OnePlus 6T": 600, - "Xiaomi Poco F1": 300, - "Iphone XS": 999, - "Samsung Galaxy S9": 720, - "Sony Bluetooth Headphone": 99, - "Xiaomi Phone Repair": 10, - "Samsung Phone Repair": 20, - "OnePlus Phone Repair": 15, - "Apple Phone Repair": 30, - } - - standard_buying = { - "OnePlus 6": 300, - "OnePlus 6T": 350, - "Xiaomi Poco F1": 200, - "Iphone XS": 600, - "Samsung Galaxy S9": 500, - "Sony Bluetooth Headphone": 69 - } - - for price_list in ("standard_buying", "standard_selling"): - for item, rate in locals().get(price_list).items(): - frappe.get_doc({ - "doctype": "Item Price", - "price_list": price_list.replace("_", " ").title(), - "item_code": item, - "selling": 1 if price_list=="standard_selling" else 0, - "buying": 1 if price_list=="standard_buying" else 0, - "price_list_rate": rate, - "currency": "USD" - }).insert() diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py deleted file mode 100644 index 7137c6ef56..0000000000 --- a/erpnext/demo/setup/setup_data.py +++ /dev/null @@ -1,447 +0,0 @@ -import json -import random - -import frappe -from frappe import _ -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from frappe.utils import cstr, flt, now_datetime, random_string -from frappe.utils.make_random import add_random_children, get_random -from frappe.utils.nestedset import get_root_of - -import erpnext -from erpnext.demo.domains import data - - -def setup(domain): - frappe.flags.in_demo = 1 - complete_setup(domain) - setup_demo_page() - setup_fiscal_year() - setup_holiday_list() - setup_user() - setup_employee() - setup_user_roles(domain) - setup_role_permissions() - setup_custom_field_for_domain() - - employees = frappe.get_all('Employee', fields=['name', 'date_of_joining']) - - # monthly salary - setup_salary_structure(employees[:5], 0) - - # based on timesheet - setup_salary_structure(employees[5:], 1) - - setup_leave_allocation() - setup_customer() - setup_supplier() - setup_warehouse() - import_json('Address') - import_json('Contact') - import_json('Lead') - setup_currency_exchange() - #setup_mode_of_payment() - setup_account_to_expense_type() - setup_budget() - setup_pos_profile() - - frappe.db.commit() - frappe.clear_cache() - -def complete_setup(domain='Manufacturing'): - print("Complete Setup...") - from frappe.desk.page.setup_wizard.setup_wizard import setup_complete - - if not frappe.get_all('Company', limit=1): - setup_complete({ - "full_name": "Test User", - "email": "test_demo@erpnext.com", - "company_tagline": 'Awesome Products and Services', - "password": "demo", - "fy_start_date": "2015-01-01", - "fy_end_date": "2015-12-31", - "bank_account": "National Bank", - "domains": [domain], - "company_name": data.get(domain).get('company_name'), - "chart_of_accounts": "Standard", - "company_abbr": ''.join([d[0] for d in data.get(domain).get('company_name').split()]).upper(), - "currency": 'USD', - "timezone": 'America/New_York', - "country": 'United States', - "language": "english" - }) - - company = erpnext.get_default_company() - - if company: - company_doc = frappe.get_doc("Company", company) - company_doc.db_set('default_payroll_payable_account', - frappe.db.get_value('Account', dict(account_name='Payroll Payable'))) - -def setup_demo_page(): - # home page should always be "start" - website_settings = frappe.get_doc("Website Settings", "Website Settings") - website_settings.home_page = "demo" - website_settings.save() - -def setup_fiscal_year(): - fiscal_year = None - for year in range(2010, now_datetime().year + 1, 1): - try: - fiscal_year = frappe.get_doc({ - "doctype": "Fiscal Year", - "year": cstr(year), - "year_start_date": "{0}-01-01".format(year), - "year_end_date": "{0}-12-31".format(year) - }).insert() - except frappe.DuplicateEntryError: - pass - - # set the last fiscal year (current year) as default - if fiscal_year: - fiscal_year.set_as_default() - -def setup_holiday_list(): - """Setup Holiday List for the current year""" - year = now_datetime().year - holiday_list = frappe.get_doc({ - "doctype": "Holiday List", - "holiday_list_name": str(year), - "from_date": "{0}-01-01".format(year), - "to_date": "{0}-12-31".format(year), - }) - holiday_list.insert() - holiday_list.weekly_off = "Saturday" - holiday_list.get_weekly_off_dates() - holiday_list.weekly_off = "Sunday" - holiday_list.get_weekly_off_dates() - holiday_list.save() - - frappe.set_value("Company", erpnext.get_default_company(), "default_holiday_list", holiday_list.name) - - -def setup_user(): - frappe.db.sql('delete from tabUser where name not in ("Guest", "Administrator")') - for u in json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'user.json')).read()): - user = frappe.new_doc("User") - user.update(u) - user.flags.no_welcome_mail = True - user.new_password = 'Demo1234567!!!' - user.insert() - -def setup_employee(): - frappe.db.set_value("HR Settings", None, "emp_created_by", "Naming Series") - frappe.db.commit() - - for d in frappe.get_all('Salary Component'): - salary_component = frappe.get_doc('Salary Component', d.name) - salary_component.append('accounts', dict( - company=erpnext.get_default_company(), - account=frappe.get_value('Account', dict(account_name=('like', 'Salary%'))) - )) - salary_component.save() - - import_json('Employee') - holiday_list = frappe.db.get_value("Holiday List", {"holiday_list_name": str(now_datetime().year)}, 'name') - frappe.db.sql('''update tabEmployee set holiday_list={0}'''.format(holiday_list)) - -def setup_salary_structure(employees, salary_slip_based_on_timesheet=0): - ss = frappe.new_doc('Salary Structure') - ss.name = "Sample Salary Structure - " + random_string(5) - ss.salary_slip_based_on_timesheet = salary_slip_based_on_timesheet - - if salary_slip_based_on_timesheet: - ss.salary_component = 'Basic' - ss.hour_rate = flt(random.random() * 10, 2) - else: - ss.payroll_frequency = 'Monthly' - - ss.payment_account = frappe.get_value('Account', - {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") - - ss.append('earnings', { - 'salary_component': 'Basic', - "abbr":'B', - 'formula': 'base*.2', - 'amount_based_on_formula': 1, - "idx": 1 - }) - ss.append('deductions', { - 'salary_component': 'Income Tax', - "abbr":'IT', - 'condition': 'base > 10000', - 'formula': 'base*.1', - "idx": 1 - }) - ss.insert() - ss.submit() - - for e in employees: - sa = frappe.new_doc("Salary Structure Assignment") - sa.employee = e.name - sa.salary_structure = ss.name - sa.from_date = "2015-01-01" - sa.base = random.random() * 10000 - sa.insert() - sa.submit() - - return ss - -def setup_user_roles(domain): - user = frappe.get_doc('User', 'demo@erpnext.com') - user.add_roles('HR User', 'HR Manager', 'Accounts User', 'Accounts Manager', - 'Stock User', 'Stock Manager', 'Sales User', 'Sales Manager', 'Purchase User', - 'Purchase Manager', 'Projects User', 'Manufacturing User', 'Manufacturing Manager', - 'Support Team') - - if domain == "Education": - user.add_roles('Academics User') - - if not frappe.db.get_global('demo_hr_user'): - user = frappe.get_doc('User', 'CaitlinSnow@example.com') - user.add_roles('HR User', 'HR Manager', 'Accounts User') - frappe.db.set_global('demo_hr_user', user.name) - update_employee_department(user.name, 'Human Resources') - for d in frappe.get_all('User Permission', filters={"user": "CaitlinSnow@example.com"}): - frappe.delete_doc('User Permission', d.name) - - if not frappe.db.get_global('demo_sales_user_1'): - user = frappe.get_doc('User', 'VandalSavage@example.com') - user.add_roles('Sales User') - update_employee_department(user.name, 'Sales') - frappe.db.set_global('demo_sales_user_1', user.name) - - if not frappe.db.get_global('demo_sales_user_2'): - user = frappe.get_doc('User', 'GraceChoi@example.com') - user.add_roles('Sales User', 'Sales Manager', 'Accounts User') - update_employee_department(user.name, 'Sales') - frappe.db.set_global('demo_sales_user_2', user.name) - - if not frappe.db.get_global('demo_purchase_user'): - user = frappe.get_doc('User', 'MaxwellLord@example.com') - user.add_roles('Purchase User', 'Purchase Manager', 'Accounts User', 'Stock User') - update_employee_department(user.name, 'Purchase') - frappe.db.set_global('demo_purchase_user', user.name) - - if not frappe.db.get_global('demo_manufacturing_user'): - user = frappe.get_doc('User', 'NeptuniaAquaria@example.com') - user.add_roles('Manufacturing User', 'Stock Manager', 'Stock User', 'Purchase User', 'Accounts User') - update_employee_department(user.name, 'Production') - frappe.db.set_global('demo_manufacturing_user', user.name) - - if not frappe.db.get_global('demo_stock_user'): - user = frappe.get_doc('User', 'HollyGranger@example.com') - user.add_roles('Manufacturing User', 'Stock User', 'Purchase User', 'Accounts User') - update_employee_department(user.name, 'Production') - frappe.db.set_global('demo_stock_user', user.name) - - if not frappe.db.get_global('demo_accounts_user'): - user = frappe.get_doc('User', 'BarryAllen@example.com') - user.add_roles('Accounts User', 'Accounts Manager', 'Sales User', 'Purchase User') - update_employee_department(user.name, 'Accounts') - frappe.db.set_global('demo_accounts_user', user.name) - - if not frappe.db.get_global('demo_projects_user'): - user = frappe.get_doc('User', 'PeterParker@example.com') - user.add_roles('HR User', 'Projects User') - update_employee_department(user.name, 'Management') - frappe.db.set_global('demo_projects_user', user.name) - - if domain == "Education": - if not frappe.db.get_global('demo_education_user'): - user = frappe.get_doc('User', 'ArthurCurry@example.com') - user.add_roles('Academics User') - update_employee_department(user.name, 'Management') - frappe.db.set_global('demo_education_user', user.name) - - #Add Expense Approver - user = frappe.get_doc('User', 'ClarkKent@example.com') - user.add_roles('Expense Approver') - -def setup_leave_allocation(): - year = now_datetime().year - for employee in frappe.get_all('Employee', fields=['name']): - leave_types = frappe.get_all("Leave Type", fields=['name', 'max_continuous_days_allowed']) - for leave_type in leave_types: - if not leave_type.max_continuous_days_allowed: - leave_type.max_continuous_days_allowed = 10 - - leave_allocation = frappe.get_doc({ - "doctype": "Leave Allocation", - "employee": employee.name, - "from_date": "{0}-01-01".format(year), - "to_date": "{0}-12-31".format(year), - "leave_type": leave_type.name, - "new_leaves_allocated": random.randint(1, int(leave_type.max_continuous_days_allowed)) - }) - leave_allocation.insert() - leave_allocation.submit() - frappe.db.commit() - -def setup_customer(): - customers = [u'Asian Junction', u'Life Plan Counselling', u'Two Pesos', u'Mr Fables', u'Intelacard', u'Big D Supermarkets', u'Adaptas', u'Nelson Brothers', u'Landskip Yard Care', u'Buttrey Food & Drug', u'Fayva', u'Asian Fusion', u'Crafts Canada', u'Consumers and Consumers Express', u'Netobill', u'Choices', u'Chi-Chis', u'Red Food', u'Endicott Shoes', u'Hind Enterprises'] - for c in customers: - frappe.get_doc({ - "doctype": "Customer", - "customer_name": c, - "customer_group": "Commercial", - "customer_type": random.choice(["Company", "Individual"]), - "territory": "Rest Of The World" - }).insert() - -def setup_supplier(): - suppliers = [u'Helios Air', u'Ks Merchandise', u'HomeBase', u'Scott Ties', u'Reliable Investments', u'Nan Duskin', u'Rainbow Records', u'New World Realty', u'Asiatic Solutions', u'Eagle Hardware', u'Modern Electricals'] - for s in suppliers: - frappe.get_doc({ - "doctype": "Supplier", - "supplier_name": s, - "supplier_group": random.choice(["Services", "Raw Material"]), - }).insert() - -def setup_warehouse(): - w = frappe.new_doc('Warehouse') - w.warehouse_name = 'Supplier' - w.insert() - -def setup_currency_exchange(): - frappe.get_doc({ - 'doctype': 'Currency Exchange', - 'from_currency': 'EUR', - 'to_currency': 'USD', - 'exchange_rate': 1.13 - }).insert() - - frappe.get_doc({ - 'doctype': 'Currency Exchange', - 'from_currency': 'CNY', - 'to_currency': 'USD', - 'exchange_rate': 0.16 - }).insert() - -def setup_mode_of_payment(): - company_abbr = frappe.get_cached_value('Company', erpnext.get_default_company(), "abbr") - account_dict = {'Cash': 'Cash - '+ company_abbr , 'Bank': 'National Bank - '+ company_abbr} - for payment_mode in frappe.get_all('Mode of Payment', fields = ["name", "type"]): - if payment_mode.type: - mop = frappe.get_doc('Mode of Payment', payment_mode.name) - mop.append('accounts', { - 'company': erpnext.get_default_company(), - 'default_account': account_dict.get(payment_mode.type) - }) - mop.save(ignore_permissions=True) - -def setup_account(): - frappe.flags.in_import = True - data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', - 'account.json')).read()) - for d in data: - doc = frappe.new_doc('Account') - doc.update(d) - doc.parent_account = frappe.db.get_value('Account', {'account_name': doc.parent_account}) - doc.insert() - - frappe.flags.in_import = False - -def setup_account_to_expense_type(): - company_abbr = frappe.get_cached_value('Company', erpnext.get_default_company(), "abbr") - expense_types = [{'name': _('Calls'), "account": "Sales Expenses - "+ company_abbr}, - {'name': _('Food'), "account": "Entertainment Expenses - "+ company_abbr}, - {'name': _('Medical'), "account": "Utility Expenses - "+ company_abbr}, - {'name': _('Others'), "account": "Miscellaneous Expenses - "+ company_abbr}, - {'name': _('Travel'), "account": "Travel Expenses - "+ company_abbr}] - - for expense_type in expense_types: - doc = frappe.get_doc("Expense Claim Type", expense_type["name"]) - doc.append("accounts", { - "company" : erpnext.get_default_company(), - "default_account" : expense_type["account"] - }) - doc.save(ignore_permissions=True) - -def setup_budget(): - fiscal_years = frappe.get_all("Fiscal Year", order_by="year_start_date")[-2:] - - for fy in fiscal_years: - budget = frappe.new_doc("Budget") - budget.cost_center = get_random("Cost Center") - budget.fiscal_year = fy.name - budget.action_if_annual_budget_exceeded = "Warn" - expense_ledger_count = frappe.db.count("Account", {"is_group": "0", "root_type": "Expense"}) - - add_random_children(budget, "accounts", rows=random.randint(10, expense_ledger_count), - randomize = { - "account": ("Account", {"is_group": "0", "root_type": "Expense"}) - }, unique="account") - - for d in budget.accounts: - d.budget_amount = random.randint(5, 100) * 10000 - - budget.save() - budget.submit() - -def setup_pos_profile(): - company_abbr = frappe.get_cached_value('Company', erpnext.get_default_company(), "abbr") - pos = frappe.new_doc('POS Profile') - pos.user = frappe.db.get_global('demo_accounts_user') - pos.name = "Demo POS Profile" - pos.naming_series = 'SINV-' - pos.update_stock = 0 - pos.write_off_account = 'Cost of Goods Sold - '+ company_abbr - pos.write_off_cost_center = 'Main - '+ company_abbr - pos.customer_group = get_root_of('Customer Group') - pos.territory = get_root_of('Territory') - - pos.append('payments', { - 'mode_of_payment': frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'), - 'amount': 0.0, - 'default': 1 - }) - - pos.insert() - -def setup_role_permissions(): - role_permissions = {'Batch': ['Accounts User', 'Item Manager']} - for doctype, roles in role_permissions.items(): - for role in roles: - if not frappe.db.get_value('Custom DocPerm', - {'parent': doctype, 'role': role}): - frappe.get_doc({ - 'doctype': 'Custom DocPerm', - 'role': role, - 'read': 1, - 'write': 1, - 'create': 1, - 'delete': 1, - 'parent': doctype - }).insert(ignore_permissions=True) - -def import_json(doctype, submit=False, values=None): - frappe.flags.in_import = True - data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', - frappe.scrub(doctype) + '.json')).read()) - for d in data: - doc = frappe.new_doc(doctype) - doc.update(d) - doc.insert() - if submit: - doc.submit() - - frappe.db.commit() - - frappe.flags.in_import = False - -def update_employee_department(user_id, department): - employee = frappe.db.get_value('Employee', {"user_id": user_id}, 'name') - department = frappe.db.get_value('Department', {'department_name': department}, 'name') - frappe.db.set_value('Employee', employee, 'department', department) - -def setup_custom_field_for_domain(): - field = { - "Item": [ - dict(fieldname='domain', label='Domain', - fieldtype='Select', hidden=1, default="Manufacturing", - options="Manufacturing\nService\nDistribution\nRetail" - ) - ] - } - create_custom_fields(field) diff --git a/erpnext/demo/user/__init__.py b/erpnext/demo/user/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/demo/user/accounts.py b/erpnext/demo/user/accounts.py deleted file mode 100644 index 273a3f92f3..0000000000 --- a/erpnext/demo/user/accounts.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import random - -import frappe -from frappe.desk import query_report -from frappe.utils import random_string -from frappe.utils.make_random import get_random - -import erpnext -from erpnext.accounts.doctype.journal_entry.journal_entry import get_payment_entry_against_invoice -from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry -from erpnext.accounts.doctype.payment_request.payment_request import ( - make_payment_entry, - make_payment_request, -) -from erpnext.demo.user.sales import make_sales_order -from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice -from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice - - -def work(): - frappe.set_user(frappe.db.get_global('demo_accounts_user')) - - if random.random() <= 0.6: - report = "Ordered Items to be Billed" - for so in list(set([r[0] for r in query_report.run(report)["result"] - if r[0]!="Total"]))[:random.randint(1, 5)]: - try: - si = frappe.get_doc(make_sales_invoice(so)) - si.posting_date = frappe.flags.current_date - for d in si.get("items"): - if not d.income_account: - d.income_account = "Sales - {}".format(frappe.get_cached_value('Company', si.company, 'abbr')) - si.insert() - si.submit() - frappe.db.commit() - except frappe.ValidationError: - pass - - if random.random() <= 0.6: - report = "Received Items to be Billed" - for pr in list(set([r[0] for r in query_report.run(report)["result"] - if r[0]!="Total"]))[:random.randint(1, 5)]: - try: - pi = frappe.get_doc(make_purchase_invoice(pr)) - pi.posting_date = frappe.flags.current_date - pi.bill_no = random_string(6) - pi.insert() - pi.submit() - frappe.db.commit() - except frappe.ValidationError: - pass - - - if random.random() < 0.5: - make_payment_entries("Sales Invoice", "Accounts Receivable") - - if random.random() < 0.5: - make_payment_entries("Purchase Invoice", "Accounts Payable") - - if random.random() < 0.4: - #make payment request against sales invoice - sales_invoice_name = get_random("Sales Invoice", filters={"docstatus": 1}) - if sales_invoice_name: - si = frappe.get_doc("Sales Invoice", sales_invoice_name) - if si.outstanding_amount > 0: - payment_request = make_payment_request(dt="Sales Invoice", dn=si.name, recipient_id=si.contact_email, - submit_doc=True, mute_email=True, use_dummy_message=True) - - payment_entry = frappe.get_doc(make_payment_entry(payment_request.name)) - payment_entry.posting_date = frappe.flags.current_date - payment_entry.submit() - - make_pos_invoice() - -def make_payment_entries(ref_doctype, report): - - outstanding_invoices = frappe.get_all(ref_doctype, fields=["name"], - filters={ - "company": erpnext.get_default_company(), - "outstanding_amount": (">", 0.0) - }) - - # make Payment Entry - for inv in outstanding_invoices[:random.randint(1, 2)]: - pe = get_payment_entry(ref_doctype, inv.name) - pe.posting_date = frappe.flags.current_date - pe.reference_no = random_string(6) - pe.reference_date = frappe.flags.current_date - pe.insert() - pe.submit() - frappe.db.commit() - outstanding_invoices.remove(inv) - - # make payment via JV - for inv in outstanding_invoices[:1]: - jv = frappe.get_doc(get_payment_entry_against_invoice(ref_doctype, inv.name)) - jv.posting_date = frappe.flags.current_date - jv.cheque_no = random_string(6) - jv.cheque_date = frappe.flags.current_date - jv.insert() - jv.submit() - frappe.db.commit() - -def make_pos_invoice(): - make_sales_order() - - for data in frappe.get_all('Sales Order', fields=["name"], - filters = [["per_billed", "<", "100"]]): - si = frappe.get_doc(make_sales_invoice(data.name)) - si.is_pos =1 - si.posting_date = frappe.flags.current_date - for d in si.get("items"): - if not d.income_account: - d.income_account = "Sales - {}".format(frappe.get_cached_value('Company', si.company, 'abbr')) - si.set_missing_values() - make_payment_entries_for_pos_invoice(si) - si.insert() - si.submit() - -def make_payment_entries_for_pos_invoice(si): - for data in si.payments: - data.amount = si.outstanding_amount - return diff --git a/erpnext/demo/user/education.py b/erpnext/demo/user/education.py deleted file mode 100644 index 47519c1664..0000000000 --- a/erpnext/demo/user/education.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import random -from datetime import timedelta - -import frappe -from frappe.utils import cstr -from frappe.utils.make_random import get_random - -from erpnext.education.api import ( - collect_fees, - enroll_student, - get_course, - get_fee_schedule, - get_student_group_students, - make_attendance_records, -) - - -def work(): - frappe.set_user(frappe.db.get_global('demo_education_user')) - for d in range(20): - approve_random_student_applicant() - enroll_random_student(frappe.flags.current_date) - # if frappe.flags.current_date.weekday()== 0: - # make_course_schedule(frappe.flags.current_date, frappe.utils.add_days(frappe.flags.current_date, 5)) - mark_student_attendance(frappe.flags.current_date) - # make_assessment_plan() - make_fees() - -def approve_random_student_applicant(): - random_student = get_random("Student Applicant", {"application_status": "Applied"}) - if random_student: - status = ["Approved", "Rejected"] - frappe.db.set_value("Student Applicant", random_student, "application_status", status[weighted_choice([9,3])]) - -def enroll_random_student(current_date): - batch = ["Section-A", "Section-B"] - random_student = get_random("Student Applicant", {"application_status": "Approved"}) - if random_student: - enrollment = enroll_student(random_student) - enrollment.academic_year = get_random("Academic Year") - enrollment.enrollment_date = current_date - enrollment.student_batch_name = batch[weighted_choice([9,3])] - fee_schedule = get_fee_schedule(enrollment.program) - for fee in fee_schedule: - enrollment.append("fees", fee) - enrolled_courses = get_course(enrollment.program) - for course in enrolled_courses: - enrollment.append("courses", course) - enrollment.submit() - frappe.db.commit() - assign_student_group(enrollment.student, enrollment.student_name, enrollment.program, - enrolled_courses, enrollment.student_batch_name) - -def assign_student_group(student, student_name, program, courses, batch): - course_list = [d["course"] for d in courses] - for d in frappe.get_list("Student Group", fields=("name"), filters={"program": program, "course":("in", course_list), "disabled": 0}): - student_group = frappe.get_doc("Student Group", d.name) - student_group.append("students", {"student": student, "student_name": student_name, - "group_roll_number":len(student_group.students)+1, "active":1}) - student_group.save() - student_batch = frappe.get_list("Student Group", fields=("name"), filters={"program": program, "group_based_on":"Batch", "batch":batch, "disabled": 0})[0] - student_batch_doc = frappe.get_doc("Student Group", student_batch.name) - student_batch_doc.append("students", {"student": student, "student_name": student_name, - "group_roll_number":len(student_batch_doc.students)+1, "active":1}) - student_batch_doc.save() - frappe.db.commit() - -def mark_student_attendance(current_date): - status = ["Present", "Absent"] - for d in frappe.db.get_list("Student Group", filters={"group_based_on": "Batch", "disabled": 0}): - students = get_student_group_students(d.name) - for stud in students: - make_attendance_records(stud.student, stud.student_name, status[weighted_choice([9,4])], None, d.name, current_date) - -def make_fees(): - for d in range(1,10): - random_fee = get_random("Fees", {"paid_amount": 0}) - collect_fees(random_fee, frappe.db.get_value("Fees", random_fee, "outstanding_amount")) - -def make_assessment_plan(date): - for d in range(1,4): - random_group = get_random("Student Group", {"group_based_on": "Course", "disabled": 0}, True) - doc = frappe.new_doc("Assessment Plan") - doc.student_group = random_group.name - doc.course = random_group.course - doc.assessment_group = get_random("Assessment Group", {"is_group": 0, "parent": "2017-18 (Semester 2)"}) - doc.grading_scale = get_random("Grading Scale") - doc.maximum_assessment_score = 100 - -def make_course_schedule(start_date, end_date): - for d in frappe.db.get_list("Student Group"): - cs = frappe.new_doc("Scheduling Tool") - cs.student_group = d.name - cs.room = get_random("Room") - cs.instructor = get_random("Instructor") - cs.course_start_date = cstr(start_date) - cs.course_end_date = cstr(end_date) - day = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] - for x in range(3): - random_day = random.choice(day) - cs.day = random_day - cs.from_time = timedelta(hours=(random.randrange(7, 17,1))) - cs.to_time = cs.from_time + timedelta(hours=1) - cs.schedule_course() - day.remove(random_day) - - -def weighted_choice(weights): - totals = [] - running_total = 0 - - for w in weights: - running_total += w - totals.append(running_total) - - rnd = random.random() * running_total - for i, total in enumerate(totals): - if rnd < total: - return i diff --git a/erpnext/demo/user/fixed_asset.py b/erpnext/demo/user/fixed_asset.py deleted file mode 100644 index 72cd420550..0000000000 --- a/erpnext/demo/user/fixed_asset.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import frappe -from frappe.utils.make_random import get_random - -from erpnext.assets.doctype.asset.asset import make_sales_invoice -from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset - - -def work(): - frappe.set_user(frappe.db.get_global('demo_accounts_user')) - - # Enable booking asset depreciation entry automatically - frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) - - # post depreciation entries as on today - post_depreciation_entries() - - # scrap a random asset - frappe.db.set_value("Company", "Wind Power LLC", "disposal_account", "Gain/Loss on Asset Disposal - WPL") - - asset = get_random_asset() - scrap_asset(asset.name) - - # Sell a random asset - sell_an_asset() - - -def sell_an_asset(): - asset = get_random_asset() - si = make_sales_invoice(asset.name, asset.item_code, "Wind Power LLC") - si.customer = get_random("Customer") - si.get("items")[0].rate = asset.value_after_depreciation * 0.8 \ - if asset.value_after_depreciation else asset.gross_purchase_amount * 0.9 - si.save() - si.submit() - - -def get_random_asset(): - return frappe.db.sql(""" select name, item_code, value_after_depreciation, gross_purchase_amount - from `tabAsset` - where docstatus=1 and status not in ("Scrapped", "Sold") order by rand() limit 1""", as_dict=1)[0] diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py deleted file mode 100644 index f84a853a79..0000000000 --- a/erpnext/demo/user/hr.py +++ /dev/null @@ -1,223 +0,0 @@ -import datetime -import random - -import frappe -from frappe.utils import add_days, get_last_day, getdate, random_string -from frappe.utils.make_random import get_random - -import erpnext -from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry -from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account -from erpnext.hr.doctype.leave_application.leave_application import ( - AttendanceAlreadyMarkedError, - OverlapError, - get_leave_balance_on, -) -from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet -from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice - - -def work(): - frappe.set_user(frappe.db.get_global('demo_hr_user')) - year, month = frappe.flags.current_date.strftime("%Y-%m").split("-") - setup_department_approvers() - mark_attendance() - make_leave_application() - - # payroll entry - if not frappe.db.sql('select name from `tabSalary Slip` where month(adddate(start_date, interval 1 month))=month(curdate())'): - # based on frequency - payroll_entry = get_payroll_entry() - payroll_entry.salary_slip_based_on_timesheet = 0 - payroll_entry.save() - payroll_entry.create_salary_slips() - payroll_entry.submit_salary_slips() - payroll_entry.make_accrual_jv_entry() - payroll_entry.submit() - # payroll_entry.make_journal_entry(reference_date=frappe.flags.current_date, - # reference_number=random_string(10)) - - # based on timesheet - payroll_entry = get_payroll_entry() - payroll_entry.salary_slip_based_on_timesheet = 1 - payroll_entry.save() - payroll_entry.create_salary_slips() - payroll_entry.submit_salary_slips() - payroll_entry.make_accrual_jv_entry() - payroll_entry.submit() - # payroll_entry.make_journal_entry(reference_date=frappe.flags.current_date, - # reference_number=random_string(10)) - - if frappe.db.get_global('demo_hr_user'): - make_timesheet_records() - - #expense claim - expense_claim = frappe.new_doc("Expense Claim") - expense_claim.extend('expenses', get_expenses()) - expense_claim.employee = get_random("Employee") - expense_claim.company = frappe.flags.company - expense_claim.payable_account = get_payable_account(expense_claim.company) - expense_claim.posting_date = frappe.flags.current_date - expense_claim.expense_approver = frappe.db.get_global('demo_hr_user') - expense_claim.save() - - rand = random.random() - - if rand < 0.4: - update_sanctioned_amount(expense_claim) - expense_claim.approval_status = 'Approved' - expense_claim.submit() - - if random.randint(0, 1): - #make journal entry against expense claim - je = frappe.get_doc(make_bank_entry("Expense Claim", expense_claim.name)) - je.posting_date = frappe.flags.current_date - je.cheque_no = random_string(10) - je.cheque_date = frappe.flags.current_date - je.flags.ignore_permissions = 1 - je.submit() - -def get_payroll_entry(): - # process payroll for previous month - payroll_entry = frappe.new_doc("Payroll Entry") - payroll_entry.company = frappe.flags.company - payroll_entry.payroll_frequency = 'Monthly' - - # select a posting date from the previous month - payroll_entry.posting_date = get_last_day(getdate(frappe.flags.current_date) - datetime.timedelta(days=10)) - payroll_entry.payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") - - payroll_entry.set_start_end_dates() - return payroll_entry - -def get_expenses(): - expenses = [] - expese_types = frappe.db.sql("""select ect.name, eca.default_account from `tabExpense Claim Type` ect, - `tabExpense Claim Account` eca where eca.parent=ect.name - and eca.company=%s """, frappe.flags.company,as_dict=1) - - for expense_type in expese_types[:random.randint(1,4)]: - claim_amount = random.randint(1,20)*10 - - expenses.append({ - "expense_date": frappe.flags.current_date, - "expense_type": expense_type.name, - "default_account": expense_type.default_account or "Miscellaneous Expenses - WPL", - "amount": claim_amount, - "sanctioned_amount": claim_amount - }) - - return expenses - -def update_sanctioned_amount(expense_claim): - for expense in expense_claim.expenses: - sanctioned_amount = random.randint(1,20)*10 - - if sanctioned_amount < expense.amount: - expense.sanctioned_amount = sanctioned_amount - -def get_timesheet_based_salary_slip_employee(): - sal_struct = frappe.db.sql(""" - select name from `tabSalary Structure` - where salary_slip_based_on_timesheet = 1 - and docstatus != 2""") - if sal_struct: - employees = frappe.db.sql(""" - select employee from `tabSalary Structure Assignment` - where salary_structure IN %(sal_struct)s""", {"sal_struct": sal_struct}, as_dict=True) - return employees - else: - return [] - -def make_timesheet_records(): - employees = get_timesheet_based_salary_slip_employee() - for e in employees: - ts = make_timesheet(e.employee, simulate = True, billable = 1, activity_type=get_random("Activity Type"), company=frappe.flags.company) - frappe.db.commit() - - rand = random.random() - if rand >= 0.3: - make_salary_slip_for_timesheet(ts.name) - - rand = random.random() - if rand >= 0.2: - make_sales_invoice_for_timesheet(ts.name) - -def make_salary_slip_for_timesheet(name): - salary_slip = make_salary_slip(name) - salary_slip.insert() - salary_slip.submit() - frappe.db.commit() - -def make_sales_invoice_for_timesheet(name): - sales_invoice = make_sales_invoice(name) - sales_invoice.customer = get_random("Customer") - sales_invoice.append('items', { - 'item_code': get_random("Item", {"has_variants": 0, "is_stock_item": 0, - "is_fixed_asset": 0}), - 'qty': 1, - 'rate': 1000 - }) - sales_invoice.flags.ignore_permissions = 1 - sales_invoice.set_missing_values() - sales_invoice.calculate_taxes_and_totals() - sales_invoice.insert() - sales_invoice.submit() - frappe.db.commit() - -def make_leave_application(): - allocated_leaves = frappe.get_all("Leave Allocation", fields=['employee', 'leave_type']) - - for allocated_leave in allocated_leaves: - leave_balance = get_leave_balance_on(allocated_leave.employee, allocated_leave.leave_type, frappe.flags.current_date, - consider_all_leaves_in_the_allocation_period=True) - if leave_balance != 0: - if leave_balance == 1: - to_date = frappe.flags.current_date - else: - to_date = add_days(frappe.flags.current_date, random.randint(0, leave_balance-1)) - - leave_application = frappe.get_doc({ - "doctype": "Leave Application", - "employee": allocated_leave.employee, - "from_date": frappe.flags.current_date, - "to_date": to_date, - "leave_type": allocated_leave.leave_type, - }) - try: - leave_application.insert() - leave_application.submit() - frappe.db.commit() - except (OverlapError, AttendanceAlreadyMarkedError): - frappe.db.rollback() - -def mark_attendance(): - attendance_date = frappe.flags.current_date - for employee in frappe.get_all('Employee', fields=['name'], filters = {'status': 'Active'}): - - if not frappe.db.get_value("Attendance", {"employee": employee.name, "attendance_date": attendance_date}): - attendance = frappe.get_doc({ - "doctype": "Attendance", - "employee": employee.name, - "attendance_date": attendance_date - }) - - leave = frappe.db.sql("""select name from `tabLeave Application` - where employee = %s and %s between from_date and to_date - and docstatus = 1""", (employee.name, attendance_date)) - - if leave: - attendance.status = "Absent" - else: - attendance.status = "Present" - attendance.save() - attendance.submit() - frappe.db.commit() - -def setup_department_approvers(): - for d in frappe.get_all('Department', filters={'department_name': ['!=', 'All Departments']}): - doc = frappe.get_doc('Department', d.name) - doc.append("leave_approvers", {'approver': frappe.session.user}) - doc.append("expense_approvers", {'approver': frappe.session.user}) - doc.flags.ignore_mandatory = True - doc.save() diff --git a/erpnext/demo/user/manufacturing.py b/erpnext/demo/user/manufacturing.py deleted file mode 100644 index 6b61776171..0000000000 --- a/erpnext/demo/user/manufacturing.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import random -from datetime import timedelta - -import frappe -from frappe.desk import query_report -from frappe.utils.make_random import how_many - -import erpnext -from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record - - -def work(): - if random.random() < 0.3: return - - frappe.set_user(frappe.db.get_global('demo_manufacturing_user')) - if not frappe.get_all('Sales Order'): return - - ppt = frappe.new_doc("Production Plan") - ppt.company = erpnext.get_default_company() - # ppt.use_multi_level_bom = 1 #refactored - ppt.get_items_from = "Sales Order" - # ppt.purchase_request_for_warehouse = "Stores - WPL" # refactored - ppt.run_method("get_open_sales_orders") - if not ppt.get("sales_orders"): return - ppt.run_method("get_items") - ppt.run_method("raise_material_requests") - ppt.save() - ppt.submit() - ppt.run_method("raise_work_orders") - frappe.db.commit() - - # submit work orders - for pro in frappe.db.get_values("Work Order", {"docstatus": 0}, "name"): - b = frappe.get_doc("Work Order", pro[0]) - b.wip_warehouse = "Work in Progress - WPL" - b.submit() - frappe.db.commit() - - # submit material requests - for pro in frappe.db.get_values("Material Request", {"docstatus": 0}, "name"): - b = frappe.get_doc("Material Request", pro[0]) - b.submit() - frappe.db.commit() - - # stores -> wip - if random.random() < 0.4: - for pro in query_report.run("Open Work Orders")["result"][:how_many("Stock Entry for WIP")]: - make_stock_entry_from_pro(pro[0], "Material Transfer for Manufacture") - - # wip -> fg - if random.random() < 0.4: - for pro in query_report.run("Work Orders in Progress")["result"][:how_many("Stock Entry for FG")]: - make_stock_entry_from_pro(pro[0], "Manufacture") - - for bom in frappe.get_all('BOM', fields=['item'], filters = {'with_operations': 1}): - pro_order = make_wo_order_test_record(item=bom.item, qty=2, - source_warehouse="Stores - WPL", wip_warehouse = "Work in Progress - WPL", - fg_warehouse = "Stores - WPL", company = erpnext.get_default_company(), - stock_uom = frappe.db.get_value('Item', bom.item, 'stock_uom'), - planned_start_date = frappe.flags.current_date) - - # submit job card - if random.random() < 0.4: - submit_job_cards() - -def make_stock_entry_from_pro(pro_id, purpose): - from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry - from erpnext.stock.doctype.stock_entry.stock_entry import ( - DuplicateEntryForWorkOrderError, - IncorrectValuationRateError, - OperationsNotCompleteError, - ) - from erpnext.stock.stock_ledger import NegativeStockError - - try: - st = frappe.get_doc(make_stock_entry(pro_id, purpose)) - st.posting_date = frappe.flags.current_date - st.fiscal_year = str(frappe.flags.current_date.year) - for d in st.get("items"): - d.cost_center = "Main - " + frappe.get_cached_value('Company', st.company, 'abbr') - st.insert() - frappe.db.commit() - st.submit() - frappe.db.commit() - except (NegativeStockError, IncorrectValuationRateError, DuplicateEntryForWorkOrderError, - OperationsNotCompleteError): - frappe.db.rollback() - -def submit_job_cards(): - work_orders = frappe.get_all("Work Order", ["name", "creation"], {"docstatus": 1, "status": "Not Started"}) - work_order = random.choice(work_orders) - # for work_order in work_orders: - start_date = work_order.creation - work_order = frappe.get_doc("Work Order", work_order.name) - job = frappe.get_all("Job Card", ["name", "operation", "work_order"], - {"docstatus": 0, "work_order": work_order.name}) - - if not job: return - job_map = {} - for d in job: - job_map[d.operation] = frappe.get_doc("Job Card", d.name) - - for operation in work_order.operations: - job = job_map[operation.operation] - job_time_log = frappe.new_doc("Job Card Time Log") - job_time_log.from_time = start_date - minutes = operation.get("time_in_mins") - 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/projects.py b/erpnext/demo/user/projects.py deleted file mode 100644 index 1203be4408..0000000000 --- a/erpnext/demo/user/projects.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import frappe -from frappe.utils import flt -from frappe.utils.make_random import get_random - -import erpnext -from erpnext.demo.user.hr import make_sales_invoice_for_timesheet -from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet - - -def run_projects(current_date): - frappe.set_user(frappe.db.get_global('demo_projects_user')) - if frappe.db.get_global('demo_projects_user'): - make_project(current_date) - make_timesheet_for_projects(current_date) - close_tasks(current_date) - -def make_timesheet_for_projects(current_date ): - for data in frappe.get_all("Task", ["name", "project"], {"status": "Open", "exp_end_date": ("<", current_date)}): - employee = get_random("Employee") - ts = make_timesheet(employee, simulate = True, billable = 1, company = erpnext.get_default_company(), - activity_type=get_random("Activity Type"), project=data.project, task =data.name) - - if flt(ts.total_billable_amount) > 0.0: - make_sales_invoice_for_timesheet(ts.name) - frappe.db.commit() - -def close_tasks(current_date): - for task in frappe.get_all("Task", ["name"], {"status": "Open", "exp_end_date": ("<", current_date)}): - task = frappe.get_doc("Task", task.name) - task.status = "Completed" - task.save() - -def make_project(current_date): - if not frappe.db.exists('Project', - "New Product Development " + current_date.strftime("%Y-%m-%d")): - project = frappe.get_doc({ - "doctype": "Project", - "project_name": "New Product Development " + current_date.strftime("%Y-%m-%d"), - }) - project.insert() diff --git a/erpnext/demo/user/purchase.py b/erpnext/demo/user/purchase.py deleted file mode 100644 index 61f081c26f..0000000000 --- a/erpnext/demo/user/purchase.py +++ /dev/null @@ -1,180 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import json -import random - -import frappe -from frappe.desk import query_report -from frappe.utils.make_random import get_random, how_many - -import erpnext -from erpnext.accounts.party import get_party_account_currency -from erpnext.buying.doctype.request_for_quotation.request_for_quotation import ( - make_supplier_quotation_from_rfq, -) -from erpnext.exceptions import InvalidCurrency -from erpnext.setup.utils import get_exchange_rate -from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation - - -def work(): - frappe.set_user(frappe.db.get_global('demo_purchase_user')) - - if random.random() < 0.6: - report = "Items To Be Requested" - for row in query_report.run(report)["result"][:random.randint(1, 5)]: - item_code, qty = row[0], abs(row[-1]) - - mr = make_material_request(item_code, qty) - - if random.random() < 0.6: - for mr in frappe.get_all('Material Request', - filters={'material_request_type': 'Purchase', 'status': 'Open'}, - limit=random.randint(1,6)): - if not frappe.get_all('Request for Quotation', - filters={'material_request': mr.name}, limit=1): - rfq = make_request_for_quotation(mr.name) - rfq.transaction_date = frappe.flags.current_date - add_suppliers(rfq) - rfq.save() - rfq.submit() - - # Make suppier quotation from RFQ against each supplier. - if random.random() < 0.6: - for rfq in frappe.get_all('Request for Quotation', - filters={'status': 'Open'}, limit=random.randint(1, 6)): - if not frappe.get_all('Supplier Quotation', - filters={'request_for_quotation': rfq.name}, limit=1): - rfq = frappe.get_doc('Request for Quotation', rfq.name) - - for supplier in rfq.suppliers: - supplier_quotation = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier.supplier) - supplier_quotation.save() - supplier_quotation.submit() - - # get supplier details - supplier = get_random("Supplier") - - company_currency = frappe.get_cached_value('Company', erpnext.get_default_company(), "default_currency") - party_account_currency = get_party_account_currency("Supplier", supplier, erpnext.get_default_company()) - if company_currency == party_account_currency: - exchange_rate = 1 - else: - exchange_rate = get_exchange_rate(party_account_currency, company_currency, args="for_buying") - - # make supplier quotations - if random.random() < 0.5: - from erpnext.stock.doctype.material_request.material_request import make_supplier_quotation - - report = "Material Requests for which Supplier Quotations are not created" - for row in query_report.run(report)["result"][:random.randint(1, 3)]: - if row[0] != "Total": - sq = frappe.get_doc(make_supplier_quotation(row[0])) - sq.transaction_date = frappe.flags.current_date - sq.supplier = supplier - sq.currency = party_account_currency or company_currency - sq.conversion_rate = exchange_rate - sq.insert() - sq.submit() - frappe.db.commit() - - # make purchase orders - if random.random() < 0.5: - from erpnext.stock.doctype.material_request.material_request import make_purchase_order - report = "Requested Items To Be Ordered" - for row in query_report.run(report)["result"][:how_many("Purchase Order")]: - if row[0] != "Total": - try: - po = frappe.get_doc(make_purchase_order(row[0])) - po.supplier = supplier - po.currency = party_account_currency or company_currency - po.conversion_rate = exchange_rate - po.transaction_date = frappe.flags.current_date - po.insert() - po.submit() - except Exception: - pass - else: - frappe.db.commit() - - if random.random() < 0.5: - make_subcontract() - -def make_material_request(item_code, qty): - mr = frappe.new_doc("Material Request") - - variant_of = frappe.db.get_value('Item', item_code, 'variant_of') or item_code - - if frappe.db.get_value('BOM', {'item': variant_of, 'is_default': 1, 'is_active': 1}): - mr.material_request_type = 'Manufacture' - else: - mr.material_request_type = "Purchase" - - mr.transaction_date = frappe.flags.current_date - mr.schedule_date = frappe.utils.add_days(mr.transaction_date, 7) - - mr.append("items", { - "doctype": "Material Request Item", - "schedule_date": frappe.utils.add_days(mr.transaction_date, 7), - "item_code": item_code, - "qty": qty - }) - mr.insert() - mr.submit() - return mr - -def add_suppliers(rfq): - for i in range(2): - supplier = get_random("Supplier") - if supplier not in [d.supplier for d in rfq.get('suppliers')]: - rfq.append("suppliers", { "supplier": supplier }) - -def make_subcontract(): - from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry - item_code = get_random("Item", {"is_sub_contracted_item": 1}) - if item_code: - # make sub-contract PO - po = frappe.new_doc("Purchase Order") - po.is_subcontracted = "Yes" - po.supplier = get_random("Supplier") - po.transaction_date = frappe.flags.current_date # added - po.schedule_date = frappe.utils.add_days(frappe.flags.current_date, 7) - - item_code = get_random("Item", {"is_sub_contracted_item": 1}) - - po.append("items", { - "item_code": item_code, - "schedule_date": frappe.utils.add_days(frappe.flags.current_date, 7), - "qty": random.randint(10, 30) - }) - po.set_missing_values() - try: - po.insert() - except InvalidCurrency: - return - - po.submit() - - # make material request for - make_material_request(po.items[0].item_code, po.items[0].qty) - - # transfer material for sub-contract - rm_items = get_rm_item(po.items[0], po.supplied_items[0]) - stock_entry = frappe.get_doc(make_rm_stock_entry(po.name, json.dumps([rm_items]))) - stock_entry.from_warehouse = "Stores - WPL" - stock_entry.to_warehouse = "Supplier - WPL" - stock_entry.insert() - -def get_rm_item(items, supplied_items): - return { - "item_code": items.get("item_code"), - "rm_item_code": supplied_items.get("rm_item_code"), - "item_name": supplied_items.get("rm_item_code"), - "qty": supplied_items.get("required_qty") + random.randint(3,10), - "amount": supplied_items.get("amount"), - "warehouse": supplied_items.get("reserve_warehouse"), - "rate": supplied_items.get("rate"), - "stock_uom": supplied_items.get("stock_uom") - } diff --git a/erpnext/demo/user/sales.py b/erpnext/demo/user/sales.py deleted file mode 100644 index ef6e4c42cd..0000000000 --- a/erpnext/demo/user/sales.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import random - -import frappe -from frappe.utils import flt -from frappe.utils.make_random import add_random_children, get_random - -import erpnext -from erpnext.accounts.doctype.payment_request.payment_request import ( - make_payment_entry, - make_payment_request, -) -from erpnext.accounts.party import get_party_account_currency -from erpnext.setup.utils import get_exchange_rate - - -def work(domain="Manufacturing"): - frappe.set_user(frappe.db.get_global('demo_sales_user_2')) - - for i in range(random.randint(1,7)): - if random.random() < 0.5: - make_opportunity(domain) - - for i in range(random.randint(1,3)): - 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([{'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([{'lost_reason': 'Did not ask'}]) - - for i in range(random.randint(1,3)): - if random.random() < 0.6: - make_sales_order() - - if random.random() < 0.5: - #make payment request against Sales Order - sales_order_name = get_random("Sales Order", filters={"docstatus": 1}) - try: - if sales_order_name: - so = frappe.get_doc("Sales Order", sales_order_name) - if flt(so.per_billed) != 100: - payment_request = make_payment_request(dt="Sales Order", dn=so.name, recipient_id=so.contact_email, - submit_doc=True, mute_email=True, use_dummy_message=True) - - payment_entry = frappe.get_doc(make_payment_entry(payment_request.name)) - payment_entry.posting_date = frappe.flags.current_date - payment_entry.submit() - except Exception: - pass - -def make_opportunity(domain): - b = frappe.get_doc({ - "doctype": "Opportunity", - "opportunity_from": "Customer", - "party_name": frappe.get_value("Customer", get_random("Customer"), 'name'), - "opportunity_type": "Sales", - "with_items": 1, - "transaction_date": frappe.flags.current_date, - }) - - add_random_children(b, "items", rows=4, randomize = { - "qty": (1, 5), - "item_code": ("Item", {"has_variants": 0, "is_fixed_asset": 0, "domain": domain}) - }, unique="item_code") - - b.insert() - frappe.db.commit() - -def make_quotation(domain): - # get open opportunites - opportunity = get_random("Opportunity", {"status": "Open", "with_items": 1}) - - if opportunity: - from erpnext.crm.doctype.opportunity.opportunity import make_quotation - qtn = frappe.get_doc(make_quotation(opportunity)) - qtn.insert() - frappe.db.commit() - qtn.submit() - frappe.db.commit() - else: - # make new directly - - # get customer, currency and exchange_rate - customer = get_random("Customer") - - company_currency = frappe.get_cached_value('Company', erpnext.get_default_company(), "default_currency") - party_account_currency = get_party_account_currency("Customer", customer, erpnext.get_default_company()) - if company_currency == party_account_currency: - exchange_rate = 1 - else: - exchange_rate = get_exchange_rate(party_account_currency, company_currency, args="for_selling") - - qtn = frappe.get_doc({ - "creation": frappe.flags.current_date, - "doctype": "Quotation", - "quotation_to": "Customer", - "party_name": customer, - "currency": party_account_currency or company_currency, - "conversion_rate": exchange_rate, - "order_type": "Sales", - "transaction_date": frappe.flags.current_date, - }) - - add_random_children(qtn, "items", rows=3, randomize = { - "qty": (1, 5), - "item_code": ("Item", {"has_variants": "0", "is_fixed_asset": 0, "domain": domain}) - }, unique="item_code") - - qtn.insert() - frappe.db.commit() - qtn.submit() - frappe.db.commit() - -def make_sales_order(): - q = get_random("Quotation", {"status": "Submitted"}) - if q: - from erpnext.selling.doctype.quotation.quotation import make_sales_order as mso - so = frappe.get_doc(mso(q)) - so.transaction_date = frappe.flags.current_date - so.delivery_date = frappe.utils.add_days(frappe.flags.current_date, 10) - so.insert() - frappe.db.commit() - so.submit() - frappe.db.commit() diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py deleted file mode 100644 index de379753b3..0000000000 --- a/erpnext/demo/user/stock.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import random - -import frappe -from frappe.desk import query_report - -import erpnext -from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError -from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return -from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return -from erpnext.stock.doctype.serial_no.serial_no import SerialNoQtyError, SerialNoRequiredError -from erpnext.stock.stock_ledger import NegativeStockError - - -def work(): - frappe.set_user(frappe.db.get_global('demo_manufacturing_user')) - - make_purchase_receipt() - make_delivery_note() - make_stock_reconciliation() - submit_draft_stock_entries() - make_sales_return_records() - make_purchase_return_records() - -def make_purchase_receipt(): - if random.random() < 0.6: - from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt - report = "Purchase Order Items To Be Received" - po_list =list(set([r[0] for r in query_report.run(report)["result"] if r[0]!="Total"]))[:random.randint(1, 10)] - for po in po_list: - pr = frappe.get_doc(make_purchase_receipt(po)) - - if pr.is_subcontracted=="Yes": - pr.supplier_warehouse = "Supplier - WPL" - - pr.posting_date = frappe.flags.current_date - pr.insert() - try: - pr.submit() - except NegativeStockError: - print('Negative stock for {0}'.format(po)) - pass - frappe.db.commit() - -def make_delivery_note(): - # make purchase requests - - # make delivery notes (if possible) - if random.random() < 0.6: - from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note - report = "Ordered Items To Be Delivered" - for so in list(set([r[0] for r in query_report.run(report)["result"] - if r[0]!="Total"]))[:random.randint(1, 3)]: - dn = frappe.get_doc(make_delivery_note(so)) - dn.posting_date = frappe.flags.current_date - for d in dn.get("items"): - if not d.expense_account: - d.expense_account = ("Cost of Goods Sold - {0}".format( - frappe.get_cached_value('Company', dn.company, 'abbr'))) - - try: - dn.insert() - dn.submit() - frappe.db.commit() - except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError, UnableToSelectBatchError): - frappe.db.rollback() - -def make_stock_reconciliation(): - # random set some items as damaged - from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( - EmptyStockReconciliationItemsError, - OpeningEntryAccountError, - ) - - if random.random() < 0.4: - 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 - 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(ignore_permissions=True, ignore_mandatory=True) - stock_reco.submit() - frappe.db.commit() - except OpeningEntryAccountError: - frappe.db.rollback() - except EmptyStockReconciliationItemsError: - frappe.db.rollback() - -def submit_draft_stock_entries(): - from erpnext.stock.doctype.stock_entry.stock_entry import ( - DuplicateEntryForWorkOrderError, - IncorrectValuationRateError, - OperationsNotCompleteError, - ) - - # try posting older drafts (if exists) - frappe.db.commit() - for st in frappe.db.get_values("Stock Entry", {"docstatus":0}, "name"): - try: - ste = frappe.get_doc("Stock Entry", st[0]) - ste.posting_date = frappe.flags.current_date - ste.save() - ste.submit() - frappe.db.commit() - except (NegativeStockError, IncorrectValuationRateError, DuplicateEntryForWorkOrderError, - OperationsNotCompleteError): - frappe.db.rollback() - -def make_sales_return_records(): - if random.random() < 0.1: - for data in frappe.get_all('Delivery Note', fields=["name"], filters={"docstatus": 1}): - if random.random() < 0.1: - try: - dn = make_sales_return(data.name) - dn.insert() - dn.submit() - frappe.db.commit() - except Exception: - frappe.db.rollback() - -def make_purchase_return_records(): - if random.random() < 0.1: - for data in frappe.get_all('Purchase Receipt', fields=["name"], filters={"docstatus": 1}): - if random.random() < 0.1: - try: - pr = make_purchase_return(data.name) - pr.insert() - pr.submit() - frappe.db.commit() - except Exception: - frappe.db.rollback() diff --git a/erpnext/domains/agriculture.py b/erpnext/domains/agriculture.py deleted file mode 100644 index e5414a9c09..0000000000 --- a/erpnext/domains/agriculture.py +++ /dev/null @@ -1,26 +0,0 @@ -data = { - 'desktop_icons': [ - 'Agriculture Task', - 'Crop', - 'Crop Cycle', - 'Fertilizer', - 'Item', - 'Location', - 'Disease', - 'Plant Analysis', - 'Soil Analysis', - 'Soil Texture', - 'Task', - 'Water Analysis', - 'Weather' - ], - 'restricted_roles': [ - 'Agriculture Manager', - 'Agriculture User' - ], - 'modules': [ - 'Agriculture' - ], - 'default_portal_role': 'System Manager', - 'on_setup': 'erpnext.agriculture.setup.setup_agriculture' -} diff --git a/erpnext/domains/hospitality.py b/erpnext/domains/hospitality.py deleted file mode 100644 index 09b98c288b..0000000000 --- a/erpnext/domains/hospitality.py +++ /dev/null @@ -1,35 +0,0 @@ -data = { - 'desktop_icons': [ - 'Restaurant', - 'Hotels', - 'Accounts', - 'Buying', - 'Stock', - 'HR', - 'Project', - 'ToDo' - ], - 'restricted_roles': [ - 'Restaurant Manager', - 'Hotel Manager', - 'Hotel Reservation User' - ], - 'custom_fields': { - 'Sales Invoice': [ - { - 'fieldname': 'restaurant', 'fieldtype': 'Link', 'options': 'Restaurant', - 'insert_after': 'customer_name', 'label': 'Restaurant', - }, - { - 'fieldname': 'restaurant_table', 'fieldtype': 'Link', 'options': 'Restaurant Table', - 'insert_after': 'restaurant', 'label': 'Restaurant Table', - } - ], - 'Price List': [ - { - 'fieldname':'restaurant_menu', 'fieldtype':'Link', 'options':'Restaurant Menu', 'label':'Restaurant Menu', - 'insert_after':'currency' - } - ] - } -} diff --git a/erpnext/education/api.py b/erpnext/education/api.py index d9013b0816..636b948a1c 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -201,8 +201,8 @@ def get_course_schedule_events(start, end, filters=None): conditions = get_event_conditions("Course Schedule", filters) data = frappe.db.sql("""select name, course, color, - timestamp(schedule_date, from_time) as from_datetime, - timestamp(schedule_date, to_time) as to_datetime, + timestamp(schedule_date, from_time) as from_time, + timestamp(schedule_date, to_time) as to_time, room, student_group, 0 as 'allDay' from `tabCourse Schedule` where ( schedule_date between %(start)s and %(end)s ) diff --git a/erpnext/education/doctype/course_schedule/course_schedule.py b/erpnext/education/doctype/course_schedule/course_schedule.py index ffd323d3d2..615d2c4709 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule.py +++ b/erpnext/education/doctype/course_schedule/course_schedule.py @@ -3,6 +3,8 @@ # For license information, please see license.txt +from datetime import datetime + import frappe from frappe import _ from frappe.model.document import Document @@ -30,6 +32,14 @@ class CourseSchedule(Document): if self.from_time > self.to_time: frappe.throw(_("From Time cannot be greater than To Time.")) + """Handles specicfic case to update schedule date in calendar """ + if isinstance(self.from_time, str): + try: + datetime_obj = datetime.strptime(self.from_time, '%Y-%m-%d %H:%M:%S') + self.schedule_date = datetime_obj + except ValueError: + pass + def validate_overlap(self): """Validates overlap for Student Group, Instructor, Room""" @@ -47,4 +57,4 @@ class CourseSchedule(Document): validate_overlap_for(self, "Assessment Plan", "student_group") validate_overlap_for(self, "Assessment Plan", "room") - validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor) + validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor) \ No newline at end of file diff --git a/erpnext/education/doctype/course_schedule/course_schedule_calendar.js b/erpnext/education/doctype/course_schedule/course_schedule_calendar.js index 803527e548..cacd539b22 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule_calendar.js +++ b/erpnext/education/doctype/course_schedule/course_schedule_calendar.js @@ -1,11 +1,10 @@ frappe.views.calendar["Course Schedule"] = { field_map: { - // from_datetime and to_datetime don't exist as docfields but are used in onload - "start": "from_datetime", - "end": "to_datetime", + "start": "from_time", + "end": "to_time", "id": "name", "title": "course", - "allDay": "allDay" + "allDay": "allDay", }, gantt: false, order_by: "schedule_date", diff --git a/erpnext/education/doctype/course_schedule/test_course_schedule.py b/erpnext/education/doctype/course_schedule/test_course_schedule.py index a732419555..56149affce 100644 --- a/erpnext/education/doctype/course_schedule/test_course_schedule.py +++ b/erpnext/education/doctype/course_schedule/test_course_schedule.py @@ -6,6 +6,7 @@ import unittest import frappe from frappe.utils import to_timedelta, today +from frappe.utils.data import add_to_date from erpnext.education.utils import OverlapError @@ -39,6 +40,11 @@ class TestCourseSchedule(unittest.TestCase): make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", room=frappe.get_all("Room")[1].name) + def test_update_schedule_date(self): + doc = make_course_schedule_test_record(schedule_date= add_to_date(today(), days=1)) + doc.schedule_date = add_to_date(doc.schedule_date, days=1) + doc.save() + def make_course_schedule_test_record(**args): args = frappe._dict(args) diff --git a/erpnext/education/workspace/education/education.json b/erpnext/education/workspace/education/education.json index 1465295658..0c7f19894c 100644 --- a/erpnext/education/workspace/education/education.json +++ b/erpnext/education/workspace/education/education.json @@ -5,7 +5,7 @@ "label": "Program Enrollments" } ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Education\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Program Enrollments\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Instructor\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Program\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Course\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Fees\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student Monthly Attendance Sheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Course Scheduling Tool\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student Attendance Tool\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Student and Instructor\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Content Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Admission\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Fees\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Schedule\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"LMS Activity\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assessment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assessment Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Education\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Program Enrollments\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Student\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Instructor\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Program\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Fees\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Student Monthly Attendance Sheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course Scheduling Tool\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Student Attendance Tool\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Student and Instructor\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Content Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Admission\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Fees\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Schedule\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Attendance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"LMS Activity\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assessment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assessment Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", "creation": "2020-03-02 17:22:57.066401", "docstatus": 0, "doctype": "Workspace", @@ -692,7 +692,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:57.929276", + "modified": "2022-01-13 17:29:13.676542", "modified_by": "Administrator", "module": "Education", "name": "Education", @@ -701,7 +701,7 @@ "public": 1, "restrict_to_domain": "Education", "roles": [], - "sequence_id": 9, + "sequence_id": 9.0, "shortcuts": [ { "color": "Grey", diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py index 66826ba8d7..03c1a1a0b5 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py @@ -5,11 +5,11 @@ import csv import math import time +from io import StringIO import dateutil import frappe from frappe import _ -from six import StringIO import erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_api as mws diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py index e242ace60f..a8119ac86c 100644 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py +++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py @@ -2,13 +2,14 @@ # For license information, please see license.txt +from urllib.parse import urlencode + import frappe import gocardless_pro from frappe import _ from frappe.integrations.utils import create_payment_gateway, create_request_log from frappe.model.document import Document from frappe.utils import call_hook_method, cint, flt, get_url -from six.moves.urllib.parse import urlencode class GoCardlessSettings(Document): diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py index 8da52f49f1..309d2cb0e3 100644 --- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py +++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py @@ -2,12 +2,13 @@ # For license information, please see license.txt +from urllib.parse import urlparse + 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.utils.nestedset import get_root_of -from six.moves.urllib.parse import urlparse class WoocommerceSettings(Document): diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index d922d875fd..30d3948fee 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -1,10 +1,10 @@ import base64 import hashlib import hmac +from urllib.parse import urlparse import frappe from frappe import _ -from six.moves.urllib.parse import urlparse from erpnext import get_default_company diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json index 8e4f92791a..45077aa66c 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Marketplace\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payments\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Marketplace\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", "creation": "2020-08-20 19:30:48.138801", "docstatus": 0, "doctype": "Workspace", @@ -40,17 +40,6 @@ "onboard": 0, "type": "Link" }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Shopify Settings", - "link_count": 0, - "link_to": "Shopify Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -112,7 +101,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:58.740247", + "modified": "2022-01-13 17:35:35.508718", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "ERPNext Integrations", @@ -121,7 +110,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 10, + "sequence_id": 10.0, "shortcuts": [], "title": "ERPNext Integrations" } diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 1d11f20ab7..04f5793836 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -65,10 +65,8 @@ webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"] domains = { - 'Agriculture': 'erpnext.domains.agriculture', 'Distribution': 'erpnext.domains.distribution', 'Education': 'erpnext.domains.education', - 'Hospitality': 'erpnext.domains.hospitality', 'Manufacturing': 'erpnext.domains.manufacturing', 'Non Profit': 'erpnext.domains.non_profit', 'Retail': 'erpnext.domains.retail', @@ -445,6 +443,7 @@ regional_overrides = { 'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts', 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', + 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount', 'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code' }, @@ -567,18 +566,6 @@ global_search_doctypes = { {'doctype': 'Assessment Code', 'index': 39}, {'doctype': 'Discussion', 'index': 40}, ], - "Agriculture": [ - {'doctype': 'Weather', 'index': 1}, - {'doctype': 'Soil Texture', 'index': 2}, - {'doctype': 'Water Analysis', 'index': 3}, - {'doctype': 'Soil Analysis', 'index': 4}, - {'doctype': 'Plant Analysis', 'index': 5}, - {'doctype': 'Agriculture Analysis Criteria', 'index': 6}, - {'doctype': 'Disease', 'index': 7}, - {'doctype': 'Crop', 'index': 8}, - {'doctype': 'Fertilizer', 'index': 9}, - {'doctype': 'Crop Cycle', 'index': 10} - ], "Non Profit": [ {'doctype': 'Certified Consultant', 'index': 1}, {'doctype': 'Certification Application', 'index': 2}, @@ -592,13 +579,6 @@ global_search_doctypes = { {'doctype': 'Donor Type', 'index': 10}, {'doctype': 'Membership Type', 'index': 11} ], - "Hospitality": [ - {'doctype': 'Hotel Room', 'index': 0}, - {'doctype': 'Hotel Room Reservation', 'index': 1}, - {'doctype': 'Hotel Room Pricing', 'index': 2}, - {'doctype': 'Hotel Room Package', 'index': 3}, - {'doctype': 'Hotel Room Type', 'index': 4} - ] } additional_timeline_content = { diff --git a/erpnext/hotels/__init__.py b/erpnext/hotels/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/__init__.py b/erpnext/hotels/doctype/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room/__init__.py b/erpnext/hotels/doctype/hotel_room/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.js b/erpnext/hotels/doctype/hotel_room/hotel_room.js deleted file mode 100644 index 76f22d5d4e..0000000000 --- a/erpnext/hotels/doctype/hotel_room/hotel_room.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Hotel Room', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.json b/erpnext/hotels/doctype/hotel_room/hotel_room.json deleted file mode 100644 index 2567c077b6..0000000000 --- a/erpnext/hotels/doctype/hotel_room/hotel_room.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "prompt", - "beta": 1, - "creation": "2017-12-08 12:33:56.320420", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hotel_room_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Hotel Room Type", - "length": 0, - "no_copy": 0, - "options": "Hotel Room 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": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "capacity", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Capacity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "extra_bed_capacity", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Extra Bed Capacity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-12-09 12:10:50.670113", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 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 - }, - { - "amend": 0, - "apply_user_permissions": 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": "Hotel Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.py b/erpnext/hotels/doctype/hotel_room/hotel_room.py deleted file mode 100644 index e4bd1c8846..0000000000 --- a/erpnext/hotels/doctype/hotel_room/hotel_room.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe.model.document import Document - - -class HotelRoom(Document): - def validate(self): - if not self.capacity: - self.capacity, self.extra_bed_capacity = frappe.db.get_value('Hotel Room Type', - self.hotel_room_type, ['capacity', 'extra_bed_capacity']) diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.py b/erpnext/hotels/doctype/hotel_room/test_hotel_room.py deleted file mode 100644 index 95efe2c606..0000000000 --- a/erpnext/hotels/doctype/hotel_room/test_hotel_room.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -test_dependencies = ["Hotel Room Package"] -test_records = [ - dict(doctype="Hotel Room", name="1001", - hotel_room_type="Basic Room"), - dict(doctype="Hotel Room", name="1002", - hotel_room_type="Basic Room"), - dict(doctype="Hotel Room", name="1003", - hotel_room_type="Basic Room"), - dict(doctype="Hotel Room", name="1004", - hotel_room_type="Basic Room"), - dict(doctype="Hotel Room", name="1005", - hotel_room_type="Basic Room"), - dict(doctype="Hotel Room", name="1006", - hotel_room_type="Basic Room") -] - -class TestHotelRoom(unittest.TestCase): - pass diff --git a/erpnext/hotels/doctype/hotel_room_amenity/__init__.py b/erpnext/hotels/doctype/hotel_room_amenity/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json deleted file mode 100644 index 29a0407a8a..0000000000 --- a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-08 12:35:36.572185", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billable", - "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": "Billable", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-12-09 12:05:07.125687", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room Amenity", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py deleted file mode 100644 index 166493124a..0000000000 --- a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class HotelRoomAmenity(Document): - pass diff --git a/erpnext/hotels/doctype/hotel_room_package/__init__.py b/erpnext/hotels/doctype/hotel_room_package/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js deleted file mode 100644 index 5b09ae568e..0000000000 --- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Hotel Room Package', { - hotel_room_type: function(frm) { - if (frm.doc.hotel_room_type) { - frappe.model.with_doc('Hotel Room Type', frm.doc.hotel_room_type, () => { - let hotel_room_type = frappe.get_doc('Hotel Room Type', frm.doc.hotel_room_type); - - // reset the amenities - frm.doc.amenities = []; - - for (let amenity of hotel_room_type.amenities) { - let d = frm.add_child('amenities'); - d.item = amenity.item; - d.billable = amenity.billable; - } - - frm.refresh(); - }); - } - } -}); diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json deleted file mode 100644 index 57dad44b7d..0000000000 --- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json +++ /dev/null @@ -1,215 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "prompt", - "beta": 1, - "creation": "2017-12-08 12:43:17.211064", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hotel_room_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Hotel Room Type", - "length": 0, - "no_copy": 0, - "options": "Hotel Room 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": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "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": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amenities", - "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": "Amenities", - "length": 0, - "no_copy": 0, - "options": "Hotel Room Amenity", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-12-09 12:10:31.111952", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room Package", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 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, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py deleted file mode 100644 index aedc83a846..0000000000 --- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe.model.document import Document - - -class HotelRoomPackage(Document): - def validate(self): - if not self.item: - item = frappe.get_doc(dict( - doctype = 'Item', - item_code = self.name, - item_group = 'Products', - is_stock_item = 0, - stock_uom = 'Unit' - )) - item.insert() - self.item = item.name diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py deleted file mode 100644 index 749731f491..0000000000 --- a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -test_records = [ - dict(doctype='Item', item_code='Breakfast', - item_group='Products', is_stock_item=0), - dict(doctype='Item', item_code='Lunch', - item_group='Products', is_stock_item=0), - dict(doctype='Item', item_code='Dinner', - item_group='Products', is_stock_item=0), - dict(doctype='Item', item_code='WiFi', - item_group='Products', is_stock_item=0), - dict(doctype='Hotel Room Type', name="Delux Room", - capacity=4, - extra_bed_capacity=2, - amenities = [ - dict(item='WiFi', billable=0) - ]), - dict(doctype='Hotel Room Type', name="Basic Room", - capacity=4, - extra_bed_capacity=2, - amenities = [ - dict(item='Breakfast', billable=0) - ]), - dict(doctype="Hotel Room Package", name="Basic Room with Breakfast", - hotel_room_type="Basic Room", - amenities = [ - dict(item="Breakfast", billable=0) - ]), - dict(doctype="Hotel Room Package", name="Basic Room with Lunch", - hotel_room_type="Basic Room", - amenities = [ - dict(item="Breakfast", billable=0), - dict(item="Lunch", billable=0) - ]), - dict(doctype="Hotel Room Package", name="Basic Room with Dinner", - hotel_room_type="Basic Room", - amenities = [ - dict(item="Breakfast", billable=0), - dict(item="Dinner", billable=0) - ]) -] - -class TestHotelRoomPackage(unittest.TestCase): - pass diff --git a/erpnext/hotels/doctype/hotel_room_pricing/__init__.py b/erpnext/hotels/doctype/hotel_room_pricing/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js deleted file mode 100644 index 87bb192570..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Hotel Room Pricing', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json deleted file mode 100644 index 0f5a776211..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json +++ /dev/null @@ -1,266 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "prompt", - "beta": 1, - "creation": "2017-12-08 12:51:47.088174", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "enabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Enabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "currency", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Currency", - "length": 0, - "no_copy": 0, - "options": "Currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_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": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_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": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items", - "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": "Items", - "length": 0, - "no_copy": 0, - "options": "Hotel Room Pricing Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-12-09 12:10:41.559559", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room Pricing", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 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 - }, - { - "amend": 0, - "apply_user_permissions": 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": "Hotel Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py deleted file mode 100644 index d28e573426..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class HotelRoomPricing(Document): - pass diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py deleted file mode 100644 index 34550096dd..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -test_dependencies = ["Hotel Room Package"] -test_records = [ - dict(doctype="Hotel Room Pricing", enabled=1, - name="Winter 2017", - from_date="2017-01-01", to_date="2017-01-10", - items = [ - dict(item="Basic Room with Breakfast", rate=10000), - dict(item="Basic Room with Lunch", rate=11000), - dict(item="Basic Room with Dinner", rate=12000) - ]) -] - -class TestHotelRoomPricing(unittest.TestCase): - pass diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py b/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json deleted file mode 100644 index d6cd826bcc..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-08 12:50:13.486090", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-12-09 12:04:58.641703", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room Pricing Item", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py deleted file mode 100644 index 2e6bb5fac2..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class HotelRoomPricingItem(Document): - pass diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py b/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js deleted file mode 100644 index f6decd9e95..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Hotel Room Pricing Package', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json deleted file mode 100644 index 1e529325cd..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-08 12:50:13.486090", - "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": "from_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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": "to_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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": "hotel_room_package", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Hotel Room Package", - "length": 0, - "no_copy": 0, - "options": "Hotel Room Package", - "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": "rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rate", - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:34:02.551811", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room Pricing Package", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "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/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py deleted file mode 100644 index ebbdb6ec6c..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class HotelRoomPricingPackage(Document): - pass diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py deleted file mode 100644 index 196e6504b5..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestHotelRoomPricingPackage(unittest.TestCase): - pass diff --git a/erpnext/hotels/doctype/hotel_room_reservation/__init__.py b/erpnext/hotels/doctype/hotel_room_reservation/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js deleted file mode 100644 index e58d763e47..0000000000 --- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Hotel Room Reservation', { - refresh: function(frm) { - if(frm.doc.docstatus == 1){ - frm.add_custom_button(__('Create Invoice'), ()=> { - frm.trigger("make_invoice"); - }); - } - }, - from_date: function(frm) { - frm.trigger("recalculate_rates"); - }, - to_date: function(frm) { - frm.trigger("recalculate_rates"); - }, - recalculate_rates: function(frm) { - if (!frm.doc.from_date || !frm.doc.to_date - || !frm.doc.items.length){ - return; - } - frappe.call({ - "method": "erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation.get_room_rate", - "args": {"hotel_room_reservation": frm.doc} - }).done((r)=> { - for (var i = 0; i < r.message.items.length; i++) { - frm.doc.items[i].rate = r.message.items[i].rate; - frm.doc.items[i].amount = r.message.items[i].amount; - } - frappe.run_serially([ - ()=> frm.set_value("net_total", r.message.net_total), - ()=> frm.refresh_field("items") - ]); - }); - }, - make_invoice: function(frm) { - frappe.model.with_doc("Hotel Settings", "Hotel Settings", ()=>{ - frappe.model.with_doctype("Sales Invoice", ()=>{ - let hotel_settings = frappe.get_doc("Hotel Settings", "Hotel Settings"); - let invoice = frappe.model.get_new_doc("Sales Invoice"); - invoice.customer = frm.doc.customer || hotel_settings.default_customer; - if (hotel_settings.default_invoice_naming_series){ - invoice.naming_series = hotel_settings.default_invoice_naming_series; - } - for (let d of frm.doc.items){ - let invoice_item = frappe.model.add_child(invoice, "items") - invoice_item.item_code = d.item; - invoice_item.qty = d.qty; - invoice_item.rate = d.rate; - } - if (hotel_settings.default_taxes_and_charges){ - invoice.taxes_and_charges = hotel_settings.default_taxes_and_charges; - } - frappe.set_route("Form", invoice.doctype, invoice.name); - }); - }); - } -}); - -frappe.ui.form.on('Hotel Room Reservation Item', { - item: function(frm, doctype, name) { - frm.trigger("recalculate_rates"); - }, - qty: function(frm) { - frm.trigger("recalculate_rates"); - } -}); diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json deleted file mode 100644 index fd20efdf8d..0000000000 --- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json +++ /dev/null @@ -1,436 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "HTL-RES-.YYYY.-.#####", - "beta": 1, - "creation": "2017-12-08 13:01:34.829175", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "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": "guest_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Guest Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "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": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "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": "from_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": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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": "to_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": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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": "late_checkin", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Late Checkin", - "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_6", - "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": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Booked\nAdvance Paid\nInvoiced\nPaid\nCompleted\nCancelled", - "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": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "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": "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": "Items", - "length": 0, - "no_copy": 0, - "options": "Hotel Room Reservation Item", - "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": "net_total", - "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": "Net Total", - "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": "amended_from", - "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": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Hotel Room Reservation", - "permlevel": 0, - "print_hide": 1, - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:47.326951", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room Reservation", - "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 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Hotel Reservation User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "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/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py deleted file mode 100644 index 7725955396..0000000000 --- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import json - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.utils import add_days, date_diff, flt - - -class HotelRoomUnavailableError(frappe.ValidationError): pass -class HotelRoomPricingNotSetError(frappe.ValidationError): pass - -class HotelRoomReservation(Document): - def validate(self): - self.total_rooms = {} - self.set_rates() - self.validate_availability() - - def validate_availability(self): - for i in range(date_diff(self.to_date, self.from_date)): - day = add_days(self.from_date, i) - self.rooms_booked = {} - - for d in self.items: - if not d.item in self.rooms_booked: - self.rooms_booked[d.item] = 0 - - room_type = frappe.db.get_value("Hotel Room Package", - d.item, 'hotel_room_type') - rooms_booked = get_rooms_booked(room_type, day, exclude_reservation=self.name) \ - + d.qty + self.rooms_booked.get(d.item) - total_rooms = self.get_total_rooms(d.item) - if total_rooms < rooms_booked: - frappe.throw(_("Hotel Rooms of type {0} are unavailable on {1}").format(d.item, - frappe.format(day, dict(fieldtype="Date"))), exc=HotelRoomUnavailableError) - - self.rooms_booked[d.item] += rooms_booked - - def get_total_rooms(self, item): - if not item in self.total_rooms: - self.total_rooms[item] = frappe.db.sql(""" - select count(*) - from - `tabHotel Room Package` package - inner join - `tabHotel Room` room on package.hotel_room_type = room.hotel_room_type - where - package.item = %s""", item)[0][0] or 0 - - return self.total_rooms[item] - - def set_rates(self): - self.net_total = 0 - for d in self.items: - net_rate = 0.0 - for i in range(date_diff(self.to_date, self.from_date)): - day = add_days(self.from_date, i) - if not d.item: - continue - day_rate = frappe.db.sql(""" - select - item.rate - from - `tabHotel Room Pricing Item` item, - `tabHotel Room Pricing` pricing - where - item.parent = pricing.name - and item.item = %s - and %s between pricing.from_date - and pricing.to_date""", (d.item, day)) - - if day_rate: - net_rate += day_rate[0][0] - else: - frappe.throw( - _("Please set Hotel Room Rate on {}").format( - frappe.format(day, dict(fieldtype="Date"))), exc=HotelRoomPricingNotSetError) - d.rate = net_rate - d.amount = net_rate * flt(d.qty) - self.net_total += d.amount - -@frappe.whitelist() -def get_room_rate(hotel_room_reservation): - """Calculate rate for each day as it may belong to different Hotel Room Pricing Item""" - doc = frappe.get_doc(json.loads(hotel_room_reservation)) - doc.set_rates() - return doc.as_dict() - -def get_rooms_booked(room_type, day, exclude_reservation=None): - exclude_condition = '' - if exclude_reservation: - exclude_condition = 'and reservation.name != {0}'.format(frappe.db.escape(exclude_reservation)) - - return frappe.db.sql(""" - select sum(item.qty) - from - `tabHotel Room Package` room_package, - `tabHotel Room Reservation Item` item, - `tabHotel Room Reservation` reservation - where - item.parent = reservation.name - and room_package.item = item.item - and room_package.hotel_room_type = %s - and reservation.docstatus = 1 - {exclude_condition} - and %s between reservation.from_date - and reservation.to_date""".format(exclude_condition=exclude_condition), - (room_type, day))[0][0] or 0 diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js deleted file mode 100644 index 7bde292a2b..0000000000 --- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js +++ /dev/null @@ -1,9 +0,0 @@ -frappe.views.calendar["Hotel Room Reservation"] = { - field_map: { - "start": "from_date", - "end": "to_date", - "id": "name", - "title": "guest_name", - "status": "status" - } -} diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py deleted file mode 100644 index bb32a27fa7..0000000000 --- a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe - -from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import ( - HotelRoomPricingNotSetError, - HotelRoomUnavailableError, -) - -test_dependencies = ["Hotel Room Package", "Hotel Room Pricing", "Hotel Room"] - -class TestHotelRoomReservation(unittest.TestCase): - def setUp(self): - frappe.db.sql("delete from `tabHotel Room Reservation`") - frappe.db.sql("delete from `tabHotel Room Reservation Item`") - - def test_reservation(self): - reservation = make_reservation( - from_date="2017-01-01", - to_date="2017-01-03", - items=[ - dict(item="Basic Room with Dinner", qty=2) - ] - ) - reservation.insert() - self.assertEqual(reservation.net_total, 48000) - - def test_price_not_set(self): - reservation = make_reservation( - from_date="2016-01-01", - to_date="2016-01-03", - items=[ - dict(item="Basic Room with Dinner", qty=2) - ] - ) - self.assertRaises(HotelRoomPricingNotSetError, reservation.insert) - - def test_room_unavailable(self): - reservation = make_reservation( - from_date="2017-01-01", - to_date="2017-01-03", - items=[ - dict(item="Basic Room with Dinner", qty=2), - ] - ) - reservation.insert() - - reservation = make_reservation( - from_date="2017-01-01", - to_date="2017-01-03", - items=[ - dict(item="Basic Room with Dinner", qty=20), - ] - ) - self.assertRaises(HotelRoomUnavailableError, reservation.insert) - -def make_reservation(**kwargs): - kwargs["doctype"] = "Hotel Room Reservation" - if not "guest_name" in kwargs: - kwargs["guest_name"] = "Test Guest" - doc = frappe.get_doc(kwargs) - return doc diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py b/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json deleted file mode 100644 index 2b7931ebc0..0000000000 --- a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2017-12-08 12:58:21.733330", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "currency", - "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": "Currency", - "length": 0, - "no_copy": 0, - "options": "Currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-12-09 12:04:34.562956", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room Reservation Item", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py deleted file mode 100644 index 41d86ddca6..0000000000 --- a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class HotelRoomReservationItem(Document): - pass diff --git a/erpnext/hotels/doctype/hotel_room_type/__init__.py b/erpnext/hotels/doctype/hotel_room_type/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js deleted file mode 100644 index d73835db94..0000000000 --- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Hotel Room Type', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json deleted file mode 100644 index 3d26413cf8..0000000000 --- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "prompt", - "beta": 1, - "creation": "2017-12-08 12:38:29.485175", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "capacity", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Capacity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "extra_bed_capacity", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Extra Bed Capacity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amenities", - "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": "Amenities", - "length": 0, - "no_copy": 0, - "options": "Hotel Room Amenity", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-12-09 12:10:23.355486", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room Type", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 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 - }, - { - "amend": 0, - "apply_user_permissions": 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": "Hotel Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py deleted file mode 100644 index 7ab529fee9..0000000000 --- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class HotelRoomType(Document): - pass diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py deleted file mode 100644 index 8d1147d0f2..0000000000 --- a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestHotelRoomType(unittest.TestCase): - pass diff --git a/erpnext/hotels/doctype/hotel_settings/__init__.py b/erpnext/hotels/doctype/hotel_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.js b/erpnext/hotels/doctype/hotel_settings/hotel_settings.js deleted file mode 100644 index 0b4a2c36ca..0000000000 --- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Hotel Settings', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.json b/erpnext/hotels/doctype/hotel_settings/hotel_settings.json deleted file mode 100644 index d9f5572549..0000000000 --- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2017-12-08 17:50:24.523107", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_taxes_and_charges", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Taxes and Charges", - "length": 0, - "no_copy": 0, - "options": "Sales Taxes and Charges Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_invoice_naming_series", - "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": "Default Invoice Naming Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2017-12-09 12:11:12.857308", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Hotel Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/hotel_settings.py deleted file mode 100644 index 8376d50969..0000000000 --- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class HotelSettings(Document): - pass diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py deleted file mode 100644 index e76c00ce10..0000000000 --- a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestHotelSettings(unittest.TestCase): - pass diff --git a/erpnext/hotels/report/__init__.py b/erpnext/hotels/report/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/report/hotel_room_occupancy/__init__.py b/erpnext/hotels/report/hotel_room_occupancy/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js deleted file mode 100644 index 81efb2dd12..0000000000 --- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt -/* eslint-disable */ - -frappe.query_reports["Hotel Room Occupancy"] = { - "filters": [ - { - "fieldname":"from_date", - "label": __("From Date"), - "fieldtype": "Date", - "default": frappe.datetime.now_date(), - "reqd":1 - }, - { - "fieldname":"to_date", - "label": __("To Date"), - "fieldtype": "Date", - "default": frappe.datetime.now_date(), - "reqd":1 - } - ] -} diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json deleted file mode 100644 index 782a48bbb9..0000000000 --- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "add_total_row": 1, - "apply_user_permissions": 1, - "creation": "2017-12-09 14:31:26.306705", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2017-12-09 14:31:26.306705", - "modified_by": "Administrator", - "module": "Hotels", - "name": "Hotel Room Occupancy", - "owner": "Administrator", - "ref_doctype": "Hotel Room Reservation", - "report_name": "Hotel Room Occupancy", - "report_type": "Script Report", - "roles": [ - { - "role": "System Manager" - }, - { - "role": "Hotel Reservation User" - } - ] -} \ No newline at end of file diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py deleted file mode 100644 index c43589d2a8..0000000000 --- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe import _ -from frappe.utils import add_days, date_diff - -from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import get_rooms_booked - - -def execute(filters=None): - columns = get_columns(filters) - data = get_data(filters) - return columns, data - -def get_columns(filters): - columns = [ - dict(label=_("Room Type"), fieldname="room_type"), - dict(label=_("Rooms Booked"), fieldtype="Int") - ] - return columns - -def get_data(filters): - out = [] - for room_type in frappe.get_all('Hotel Room Type'): - total_booked = 0 - for i in range(date_diff(filters.to_date, filters.from_date)): - day = add_days(filters.from_date, i) - total_booked += get_rooms_booked(room_type.name, day) - - out.append([room_type.name, total_booked]) - - return out diff --git a/erpnext/hr/doctype/appointment_letter/appointment_letter.json b/erpnext/hr/doctype/appointment_letter/appointment_letter.json index c81b7004f6..012f6b6b49 100644 --- a/erpnext/hr/doctype/appointment_letter/appointment_letter.json +++ b/erpnext/hr/doctype/appointment_letter/appointment_letter.json @@ -86,11 +86,12 @@ } ], "links": [], - "modified": "2020-01-21 17:30:36.334395", + "modified": "2022-01-18 19:27:35.649424", "modified_by": "Administrator", "module": "HR", "name": "Appointment Letter", "name_case": "Title Case", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -118,7 +119,10 @@ "write": 1 } ], + "search_fields": "applicant_name, company", "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "applicant_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json b/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json index c136fb22fa..5e50fe6d8f 100644 --- a/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json +++ b/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json @@ -1,11 +1,12 @@ { "actions": [], - "autoname": "HR-APP-LETTER-TEMP-.#####", + "autoname": "field:template_name", "creation": "2019-12-26 12:20:14.219578", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "template_name", "introduction", "terms", "closing_notes" @@ -29,13 +30,21 @@ "label": "Terms", "options": "Appointment Letter content", "reqd": 1 + }, + { + "fieldname": "template_name", + "fieldtype": "Data", + "label": "Template Name", + "reqd": 1, + "unique": 1 } ], "links": [], - "modified": "2020-01-21 17:00:46.779420", + "modified": "2022-01-18 19:25:14.614616", "modified_by": "Administrator", "module": "HR", "name": "Appointment Letter Template", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { @@ -63,7 +72,10 @@ "write": 1 } ], + "search_fields": "template_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "template_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 7dcfac249f..b1eaaf8b58 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -5,9 +5,9 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cstr, formatdate, get_datetime, getdate, nowdate +from frappe.utils import cint, cstr, formatdate, get_datetime, getdate, nowdate -from erpnext.hr.utils import validate_active_employee +from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee class Attendance(Document): @@ -171,7 +171,7 @@ def get_month_map(): }) @frappe.whitelist() -def get_unmarked_days(employee, month): +def get_unmarked_days(employee, month, exclude_holidays=0): import calendar month_map = get_month_map() @@ -191,6 +191,11 @@ def get_unmarked_days(employee, month): ]) marked_days = [get_datetime(record.attendance_date) for record in records] + if cint(exclude_holidays): + holiday_dates = get_holiday_dates_for_employee(employee, month_start, month_end) + holidays = [get_datetime(record) for record in holiday_dates] + marked_days.extend(holidays) + unmarked_days = [] for date in dates_of_month: diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 6b3c29a76b..3a5c591539 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -28,6 +28,7 @@ frappe.listview_settings['Attendance'] = { onchange: function() { dialog.set_df_property("unmarked_days", "hidden", 1); dialog.set_df_property("status", "hidden", 1); + dialog.set_df_property("exclude_holidays", "hidden", 1); dialog.set_df_property("month", "value", ''); dialog.set_df_property("unmarked_days", "options", []); dialog.no_unmarked_days_left = false; @@ -42,9 +43,14 @@ frappe.listview_settings['Attendance'] = { onchange: function() { if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { dialog.set_df_property("status", "hidden", 0); + dialog.set_df_property("exclude_holidays", "hidden", 0); dialog.set_df_property("unmarked_days", "options", []); dialog.no_unmarked_days_left = false; - me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options => { + me.get_multi_select_options( + dialog.fields_dict.employee.value, + dialog.fields_dict.month.value, + dialog.fields_dict.exclude_holidays.get_value() + ).then(options => { if (options.length > 0) { dialog.set_df_property("unmarked_days", "hidden", 0); dialog.set_df_property("unmarked_days", "options", options); @@ -64,6 +70,31 @@ frappe.listview_settings['Attendance'] = { reqd: 1, }, + { + label: __("Exclude Holidays"), + fieldtype: "Check", + fieldname: "exclude_holidays", + hidden: 1, + onchange: function() { + if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { + dialog.set_df_property("status", "hidden", 0); + dialog.set_df_property("unmarked_days", "options", []); + dialog.no_unmarked_days_left = false; + me.get_multi_select_options( + dialog.fields_dict.employee.value, + dialog.fields_dict.month.value, + dialog.fields_dict.exclude_holidays.get_value() + ).then(options => { + if (options.length > 0) { + dialog.set_df_property("unmarked_days", "hidden", 0); + dialog.set_df_property("unmarked_days", "options", options); + } else { + dialog.no_unmarked_days_left = true; + } + }); + } + } + }, { label: __("Unmarked Attendance for days"), fieldname: "unmarked_days", @@ -105,7 +136,7 @@ frappe.listview_settings['Attendance'] = { }); }, - get_multi_select_options: function(employee, month) { + get_multi_select_options: function(employee, month, exclude_holidays) { return new Promise(resolve => { frappe.call({ method: 'erpnext.hr.doctype.attendance.attendance.get_unmarked_days', @@ -113,6 +144,7 @@ frappe.listview_settings['Attendance'] = { args: { employee: employee, month: month, + exclude_holidays: exclude_holidays } }).then(r => { var options = []; diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py index a1247d9eb1..fb62b0414d 100644 --- a/erpnext/hr/doctype/employee/employee_dashboard.py +++ b/erpnext/hr/doctype/employee/employee_dashboard.py @@ -21,7 +21,7 @@ def get_data(): }, { 'label': _('Lifecycle'), - 'items': ['Employee Transfer', 'Employee Promotion', 'Employee Grievance'] + 'items': ['Employee Onboarding', 'Employee Transfer', 'Employee Promotion', 'Employee Grievance'] }, { 'label': _('Exit'), diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json index 044a5a9886..8474bd09d5 100644 --- a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json +++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json @@ -62,6 +62,7 @@ }, { "default": "0", + "depends_on": "eval:['Employee Onboarding', 'Employee Onboarding Template'].includes(doc.parenttype)", "description": "Applicable in the case of Employee Onboarding", "fieldname": "required_for_employee_creation", "fieldtype": "Check", @@ -93,7 +94,7 @@ ], "istable": 1, "links": [], - "modified": "2021-07-30 15:55:22.470102", + "modified": "2022-01-29 14:05:00.543122", "modified_by": "Administrator", "module": "HR", "name": "Employee Boarding Activity", @@ -102,5 +103,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js index 5d1a024ebb..6fbb54d002 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js @@ -3,12 +3,6 @@ frappe.ui.form.on('Employee Onboarding', { setup: function(frm) { - frm.add_fetch("employee_onboarding_template", "company", "company"); - frm.add_fetch("employee_onboarding_template", "department", "department"); - frm.add_fetch("employee_onboarding_template", "designation", "designation"); - frm.add_fetch("employee_onboarding_template", "employee_grade", "employee_grade"); - - frm.set_query("job_applicant", function () { return { filters:{ @@ -71,5 +65,19 @@ frappe.ui.form.on('Employee Onboarding', { } }); } + }, + + job_applicant: function(frm) { + if (frm.doc.job_applicant) { + frappe.db.get_value('Employee', {'job_applicant': frm.doc.job_applicant}, 'name', (r) => { + if (r.name) { + frm.set_value('employee', r.name); + } else { + frm.set_value('employee', ''); + } + }); + } else { + frm.set_value('employee', ''); + } } }); diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json index fd877a68d8..1d2ea0c669 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json @@ -92,6 +92,7 @@ "options": "Employee Onboarding Template" }, { + "fetch_from": "employee_onboarding_template.company", "fieldname": "company", "fieldtype": "Link", "label": "Company", @@ -99,6 +100,7 @@ "reqd": 1 }, { + "fetch_from": "employee_onboarding_template.department", "fieldname": "department", "fieldtype": "Link", "in_list_view": 1, @@ -106,6 +108,7 @@ "options": "Department" }, { + "fetch_from": "employee_onboarding_template.designation", "fieldname": "designation", "fieldtype": "Link", "in_list_view": 1, @@ -113,6 +116,7 @@ "options": "Designation" }, { + "fetch_from": "employee_onboarding_template.employee_grade", "fieldname": "employee_grade", "fieldtype": "Link", "label": "Employee Grade", @@ -170,10 +174,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-07-30 14:55:04.560683", + "modified": "2022-01-29 12:33:57.120384", "modified_by": "Administrator", "module": "HR", "name": "Employee Onboarding", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -194,6 +199,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py index eba2a03193..a0939a847b 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py @@ -14,10 +14,15 @@ class IncompleteTaskError(frappe.ValidationError): pass class EmployeeOnboarding(EmployeeBoardingController): def validate(self): super(EmployeeOnboarding, self).validate() + self.set_employee() self.validate_duplicate_employee_onboarding() + def set_employee(self): + if not self.employee: + self.employee = frappe.db.get_value('Employee', {'job_applicant': self.job_applicant}, 'name') + def validate_duplicate_employee_onboarding(self): - emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant}) + emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant, "docstatus": ("!=", 2)}) if emp_onboarding and emp_onboarding != self.name: frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant))) diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py index cb1b56048b..2d129c8acf 100644 --- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py +++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py @@ -19,7 +19,7 @@ class TestEmployeeOnboarding(unittest.TestCase): if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}): frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'}) - project = "Employee Onboarding : Test Researcher - test@researcher.com" + project = "Employee Onboarding : test@researcher.com" frappe.db.sql("delete from tabProject where name=%s", project) frappe.db.sql("delete from tabTask where project=%s", project) @@ -27,7 +27,7 @@ class TestEmployeeOnboarding(unittest.TestCase): onboarding = create_employee_onboarding() project_name = frappe.db.get_value('Project', onboarding.project, 'project_name') - self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com') + self.assertEqual(project_name, 'Employee Onboarding : test@researcher.com') # don't allow making employee if onboarding is not complete self.assertRaises(IncompleteTaskError, make_employee, onboarding.name) @@ -64,8 +64,8 @@ class TestEmployeeOnboarding(unittest.TestCase): def get_job_applicant(): - if frappe.db.exists('Job Applicant', 'Test Researcher - test@researcher.com'): - return frappe.get_doc('Job Applicant', 'Test Researcher - test@researcher.com') + if frappe.db.exists('Job Applicant', 'test@researcher.com'): + return frappe.get_doc('Job Applicant', 'test@researcher.com') applicant = frappe.new_doc('Job Applicant') applicant.applicant_name = 'Test Researcher' applicant.email_id = 'test@researcher.com' diff --git a/erpnext/hr/doctype/interview/interview.py b/erpnext/hr/doctype/interview/interview.py index 4bb003ded1..a3b111ccb1 100644 --- a/erpnext/hr/doctype/interview/interview.py +++ b/erpnext/hr/doctype/interview/interview.py @@ -7,7 +7,7 @@ import datetime import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cstr, get_datetime, get_link_to_form +from frappe.utils import cstr, flt, get_datetime, get_link_to_form class DuplicateInterviewRoundError(frappe.ValidationError): @@ -18,6 +18,7 @@ class Interview(Document): self.validate_duplicate_interview() self.validate_designation() self.validate_overlap() + self.set_average_rating() def on_submit(self): if self.status not in ['Cleared', 'Rejected']: @@ -67,6 +68,13 @@ class Interview(Document): overlapping_details = _('Interview overlaps with {0}').format(get_link_to_form('Interview', overlaps[0][0])) frappe.throw(overlapping_details, title=_('Overlap')) + def set_average_rating(self): + total_rating = 0 + for entry in self.interview_details: + if entry.average_rating: + total_rating += entry.average_rating + + self.average_rating = flt(total_rating / len(self.interview_details) if len(self.interview_details) else 0) @frappe.whitelist() def reschedule_interview(self, scheduled_on, from_time, to_time): diff --git a/erpnext/hr/doctype/interview/test_interview.py b/erpnext/hr/doctype/interview/test_interview.py index 1a2257a6d9..fdb11afe82 100644 --- a/erpnext/hr/doctype/interview/test_interview.py +++ b/erpnext/hr/doctype/interview/test_interview.py @@ -12,6 +12,7 @@ from frappe.utils import add_days, getdate, nowtime from erpnext.hr.doctype.designation.test_designation import create_designation from erpnext.hr.doctype.interview.interview import DuplicateInterviewRoundError +from erpnext.hr.doctype.job_applicant.job_applicant import get_interview_details from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant @@ -70,6 +71,20 @@ class TestInterview(unittest.TestCase): email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) self.assertTrue("Subject: Interview Feedback Reminder" in email_queue[0].message) + def test_get_interview_details_for_applicant_dashboard(self): + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name) + + details = get_interview_details(job_applicant.name) + self.assertEqual(details.get('stars'), 5) + self.assertEqual(details.get('interviews').get(interview.name), { + 'name': interview.name, + 'interview_round': interview.interview_round, + 'expected_average_rating': interview.expected_average_rating * 5, + 'average_rating': interview.average_rating * 5, + 'status': 'Pending' + }) + def tearDown(self): frappe.db.rollback() @@ -106,7 +121,8 @@ def create_interview_round(name, skill_set, interviewers=[], designation=None, s interview_round = frappe.new_doc("Interview Round") interview_round.round_name = name interview_round.interview_type = create_interview_type() - interview_round.expected_average_rating = 4 + # average rating = 4 + interview_round.expected_average_rating = 0.8 if designation: interview_round.designation = designation diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.py b/erpnext/hr/doctype/interview_feedback/interview_feedback.py index d046458f19..2ff00c1cac 100644 --- a/erpnext/hr/doctype/interview_feedback/interview_feedback.py +++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.py @@ -57,7 +57,6 @@ class InterviewFeedback(Document): def update_interview_details(self): doc = frappe.get_doc('Interview', self.interview) - total_rating = 0 if self.docstatus == 2: for entry in doc.interview_details: @@ -72,10 +71,6 @@ class InterviewFeedback(Document): entry.comments = self.feedback entry.result = self.result - if entry.average_rating: - total_rating += entry.average_rating - - doc.average_rating = flt(total_rating / len(doc.interview_details) if len(doc.interview_details) else 0) doc.save() doc.notify_update() diff --git a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py index d2ec5b9438..19c464296a 100644 --- a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py +++ b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py @@ -24,7 +24,7 @@ class TestInterviewFeedback(unittest.TestCase): create_skill_set(['Leadership']) interview_feedback = create_interview_feedback(interview.name, interviewer, skill_ratings) - interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 4}) + interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 0.8}) frappe.set_user(interviewer) self.assertRaises(frappe.ValidationError, interview_feedback.save) @@ -50,7 +50,7 @@ class TestInterviewFeedback(unittest.TestCase): avg_rating = flt(total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0) - self.assertEqual(flt(avg_rating, 3), feedback_1.average_rating) + self.assertEqual(flt(avg_rating, 2), flt(feedback_1.average_rating, 2)) avg_on_interview_detail = frappe.db.get_value('Interview Detail', { 'parent': feedback_1.interview, @@ -59,7 +59,7 @@ class TestInterviewFeedback(unittest.TestCase): }, 'average_rating') # 1. average should be reflected in Interview Detail. - self.assertEqual(avg_on_interview_detail, feedback_1.average_rating) + self.assertEqual(flt(avg_on_interview_detail, 2), flt(feedback_1.average_rating, 2)) '''For Second Interviewer Feedback''' interviewer = interview.interview_details[1].interviewer @@ -97,5 +97,5 @@ def get_skills_rating(interview_round): skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields = ["skill"]) for d in skills: - d["rating"] = random.randint(1, 5) + d["rating"] = random.random() return skills diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js index d7b1c6c9df..c1e8257168 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.js +++ b/erpnext/hr/doctype/job_applicant/job_applicant.js @@ -21,9 +21,9 @@ frappe.ui.form.on("Job Applicant", { create_custom_buttons: function(frm) { if (!frm.doc.__islocal && frm.doc.status !== "Rejected" && frm.doc.status !== "Accepted") { - frm.add_custom_button(__("Create Interview"), function() { + frm.add_custom_button(__("Interview"), function() { frm.events.create_dialog(frm); - }); + }, __("Create")); } if (!frm.doc.__islocal) { @@ -40,10 +40,10 @@ frappe.ui.form.on("Job Applicant", { frappe.route_options = { "job_applicant": frm.doc.name, "applicant_name": frm.doc.applicant_name, - "designation": frm.doc.job_opening, + "designation": frm.doc.job_opening || frm.doc.designation, }; frappe.new_doc("Job Offer"); - }); + }, __("Create")); } } }, @@ -55,13 +55,16 @@ frappe.ui.form.on("Job Applicant", { job_applicant: frm.doc.name }, callback: function(r) { - $("div").remove(".form-dashboard-section.custom"); - frm.dashboard.add_section( - frappe.render_template('job_applicant_dashboard', { - data: r.message - }), - __("Interview Summary") - ); + if (r.message) { + $("div").remove(".form-dashboard-section.custom"); + frm.dashboard.add_section( + frappe.render_template("job_applicant_dashboard", { + data: r.message.interviews, + number_of_stars: r.message.stars + }), + __("Interview Summary") + ); + } } }); }, diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json index 200f675221..66b609cf99 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.json +++ b/erpnext/hr/doctype/job_applicant/job_applicant.json @@ -192,10 +192,11 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-09-29 23:06:10.904260", + "modified": "2022-01-12 16:28:53.196881", "modified_by": "Administrator", "module": "HR", "name": "Job Applicant", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -210,10 +211,11 @@ "write": 1 } ], - "search_fields": "applicant_name", + "search_fields": "applicant_name, email_id, job_title, phone_number", "sender_field": "email_id", "sort_field": "modified", "sort_order": "ASC", + "states": [], "subject_field": "notes", "title_field": "applicant_name" } \ No newline at end of file diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index abaa50c84c..ccc21ced2c 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -7,6 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document +from frappe.model.naming import append_number_if_name_exists from frappe.utils import validate_email_address from erpnext.hr.doctype.interview.interview import get_interviewers @@ -21,10 +22,11 @@ class JobApplicant(Document): self.get("__onload").job_offer = job_offer[0].name def autoname(self): - keys = filter(None, (self.applicant_name, self.email_id, self.job_title)) - if not keys: - frappe.throw(_("Name or Email is mandatory"), frappe.NameError) - self.name = " - ".join(keys) + self.name = self.email_id + + # applicant can apply more than once for a different job title or reapply + if frappe.db.exists("Job Applicant", self.name): + self.name = append_number_if_name_exists("Job Applicant", self.name) def validate(self): if self.email_id: @@ -79,8 +81,13 @@ def get_interview_details(job_applicant): fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"] ) interview_detail_map = {} + meta = frappe.get_meta("Interview") + number_of_stars = meta.get_options("expected_average_rating") or 5 for detail in interview_details: + detail.expected_average_rating = detail.expected_average_rating * number_of_stars if detail.expected_average_rating else 0 + detail.average_rating = detail.average_rating * number_of_stars if detail.average_rating else 0 + interview_detail_map[detail.name] = detail - return interview_detail_map + return {"interviews": interview_detail_map, "stars": number_of_stars} diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html index c286787a55..734b2fe5e8 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html +++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html @@ -17,24 +17,33 @@ {%= key %} {%= value["interview_round"] %} {%= value["status"] %} - - {% for (i = 0; i < value["expected_average_rating"]; i++) { %} - - {% } %} - {% for (i = 0; i < (5-value["expected_average_rating"]); i++) { %} - - {% } %} - - - {% if(value["average_rating"]){ %} - {% for (i = 0; i < value["average_rating"]; i++) { %} - - {% } %} - {% for (i = 0; i < (5-value["average_rating"]); i++) { %} - - {% } %} - {% } %} - + {% let right_class = ''; %} + {% let left_class = ''; %} + + {% $.each([value["expected_average_rating"], value["average_rating"]], (_, val) => { %} + +
+ {% for (let i = 1; i <= number_of_stars; i++) { %} + {% if (i <= val) { %} + {% right_class = 'star-click'; %} + {% } else { %} + {% right_class = ''; %} + {% } %} + + {% if ((i <= val) || ((i - 0.5) == val)) { %} + {% left_class = 'star-click'; %} + {% } else { %} + {% left_class = ''; %} + {% } %} + + + + + + {% } %} +
+ + {% }); %} {% } %} diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.py b/erpnext/hr/doctype/job_applicant/test_job_applicant.py index 36dcf6b074..bf1622028d 100644 --- a/erpnext/hr/doctype/job_applicant/test_job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.py @@ -9,7 +9,26 @@ from erpnext.hr.doctype.designation.test_designation import create_designation class TestJobApplicant(unittest.TestCase): - pass + def test_job_applicant_naming(self): + applicant = frappe.get_doc({ + "doctype": "Job Applicant", + "status": "Open", + "applicant_name": "_Test Applicant", + "email_id": "job_applicant_naming@example.com" + }).insert() + self.assertEqual(applicant.name, 'job_applicant_naming@example.com') + + applicant = frappe.get_doc({ + "doctype": "Job Applicant", + "status": "Open", + "applicant_name": "_Test Applicant", + "email_id": "job_applicant_naming@example.com" + }).insert() + self.assertEqual(applicant.name, 'job_applicant_naming@example.com-1') + + def tearDown(self): + frappe.db.rollback() + def create_job_applicant(**args): args = frappe._dict(args) diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py index 39f471929b..072fc73271 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.py +++ b/erpnext/hr/doctype/job_offer/job_offer.py @@ -78,6 +78,7 @@ def make_employee(source_name, target_doc=None): "doctype": "Employee", "field_map": { "applicant_name": "employee_name", + "offer_date": "scheduled_confirmation_date" }} }, target_doc, set_missing_values) return doc diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 52ee463db0..9ecbe014b9 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -237,10 +237,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-10-01 15:28:26.335104", + "modified": "2022-01-18 19:15:53.262536", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -278,5 +279,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "timeline_field": "employee" -} + "states": [], + "timeline_field": "employee", + "title_field": "employee_name" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 1dc5b31461..70250f5bcf 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -22,6 +22,7 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry from erpnext.hr.utils import ( + get_holiday_dates_for_employee, get_leave_period, set_employee_name, share_doc_with_approver, @@ -159,33 +160,57 @@ class LeaveApplication(Document): .format(formatdate(future_allocation[0].from_date), future_allocation[0].name)) def update_attendance(self): - if self.status == "Approved": - for dt in daterange(getdate(self.from_date), getdate(self.to_date)): - date = dt.strftime("%Y-%m-%d") - status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave" - attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee, - attendance_date = date, docstatus = ('!=', 2))) + if self.status != "Approved": + return + holiday_dates = [] + if not frappe.db.get_value("Leave Type", self.leave_type, "include_holiday"): + holiday_dates = get_holiday_dates_for_employee(self.employee, self.from_date, self.to_date) + + for dt in daterange(getdate(self.from_date), getdate(self.to_date)): + date = dt.strftime("%Y-%m-%d") + attendance_name = frappe.db.exists("Attendance", dict(employee = self.employee, + attendance_date = date, docstatus = ('!=', 2))) + + # don't mark attendance for holidays + # if leave type does not include holidays within leaves as leaves + if date in holiday_dates: if attendance_name: - # update existing attendance, change absent to on leave - doc = frappe.get_doc('Attendance', attendance_name) - if doc.status != status: - doc.db_set('status', status) - doc.db_set('leave_type', self.leave_type) - doc.db_set('leave_application', self.name) - else: - # make new attendance and submit it - doc = frappe.new_doc("Attendance") - doc.employee = self.employee - doc.employee_name = self.employee_name - doc.attendance_date = date - doc.company = self.company - doc.leave_type = self.leave_type - doc.leave_application = self.name - doc.status = status - doc.flags.ignore_validate = True - doc.insert(ignore_permissions=True) - doc.submit() + # cancel and delete existing attendance for holidays + attendance = frappe.get_doc("Attendance", attendance_name) + attendance.flags.ignore_permissions = True + if attendance.docstatus == 1: + attendance.cancel() + frappe.delete_doc("Attendance", attendance_name, force=1) + continue + + self.create_or_update_attendance(attendance_name, date) + + def create_or_update_attendance(self, attendance_name, date): + status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave" + + if attendance_name: + # update existing attendance, change absent to on leave + doc = frappe.get_doc('Attendance', attendance_name) + if doc.status != status: + doc.db_set({ + 'status': status, + 'leave_type': self.leave_type, + 'leave_application': self.name + }) + else: + # make new attendance and submit it + doc = frappe.new_doc("Attendance") + doc.employee = self.employee + doc.employee_name = self.employee_name + doc.attendance_date = date + doc.company = self.company + doc.leave_type = self.leave_type + doc.leave_application = self.name + doc.status = status + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True) + doc.submit() def cancel_attendance(self): if self.docstatus == 2: diff --git a/erpnext/hr/doctype/leave_application/leave_application_email_template.html b/erpnext/hr/doctype/leave_application/leave_application_email_template.html index 14ca41bebc..dae9084f79 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_email_template.html +++ b/erpnext/hr/doctype/leave_application/leave_application_email_template.html @@ -23,3 +23,8 @@ {{status}} + + {% set doc_link = frappe.utils.get_url_to_form('Leave Application', name) %} + +

+ {{ _('Open Now') }} \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index f73d3e52da..75e99f8991 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -5,7 +5,16 @@ import unittest import frappe from frappe.permissions import clear_user_permissions_for_doctype -from frappe.utils import add_days, add_months, getdate, nowdate +from frappe.utils import ( + add_days, + add_months, + get_first_day, + get_last_day, + get_year_ending, + get_year_start, + getdate, + nowdate, +) from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation @@ -19,6 +28,10 @@ from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import ( create_assignment_for_multiple_employees, ) from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type +from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( + make_holiday_list, + make_leave_application, +) test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"] @@ -61,13 +74,15 @@ class TestLeaveApplication(unittest.TestCase): for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]: frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec + frappe.set_user("Administrator") + @classmethod def setUpClass(cls): set_leave_approver() frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'") def tearDown(self): - frappe.set_user("Administrator") + frappe.db.rollback() def _clear_roles(self): frappe.db.sql("""delete from `tabHas Role` where parent in @@ -106,6 +121,72 @@ class TestLeaveApplication(unittest.TestCase): for d in ('2018-01-01', '2018-01-02', '2018-01-03'): self.assertTrue(getdate(d) in dates) + def test_attendance_for_include_holidays(self): + # Case 1: leave type with 'Include holidays within leaves as leaves' enabled + frappe.delete_doc_if_exists("Leave Type", "Test Include Holidays", force=1) + leave_type = frappe.get_doc(dict( + leave_type_name="Test Include Holidays", + doctype="Leave Type", + include_holiday=True + )).insert() + + date = getdate() + make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date)) + + holiday_list = make_holiday_list() + frappe.db.set_value("Company", "_Test Company", "default_holiday_list", holiday_list) + first_sunday = get_first_sunday(holiday_list) + + leave_application = make_leave_application("_T-Employee-00001", first_sunday, add_days(first_sunday, 3), leave_type.name) + leave_application.reload() + self.assertEqual(leave_application.total_leave_days, 4) + self.assertEqual(frappe.db.count('Attendance', {'leave_application': leave_application.name}), 4) + + leave_application.cancel() + + def test_attendance_update_for_exclude_holidays(self): + # Case 2: leave type with 'Include holidays within leaves as leaves' disabled + frappe.delete_doc_if_exists("Leave Type", "Test Do Not Include Holidays", force=1) + leave_type = frappe.get_doc(dict( + leave_type_name="Test Do Not Include Holidays", + doctype="Leave Type", + include_holiday=False + )).insert() + + date = getdate() + make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date)) + + holiday_list = make_holiday_list() + frappe.db.set_value("Company", "_Test Company", "default_holiday_list", holiday_list) + first_sunday = get_first_sunday(holiday_list) + + # already marked attendance on a holiday should be deleted in this case + config = { + "doctype": "Attendance", + "employee": "_T-Employee-00001", + "status": "Present" + } + attendance_on_holiday = frappe.get_doc(config) + attendance_on_holiday.attendance_date = first_sunday + attendance_on_holiday.save() + + # already marked attendance on a non-holiday should be updated + attendance = frappe.get_doc(config) + attendance.attendance_date = add_days(first_sunday, 3) + attendance.save() + + leave_application = make_leave_application("_T-Employee-00001", first_sunday, add_days(first_sunday, 3), leave_type.name) + leave_application.reload() + # holiday should be excluded while marking attendance + self.assertEqual(leave_application.total_leave_days, 3) + self.assertEqual(frappe.db.count("Attendance", {"leave_application": leave_application.name}), 3) + + # attendance on holiday deleted + self.assertFalse(frappe.db.exists("Attendance", attendance_on_holiday.name)) + + # attendance on non-holiday updated + self.assertEqual(frappe.db.get_value("Attendance", attendance.name, "status"), "On Leave") + def test_block_list(self): self._clear_roles() @@ -241,7 +322,13 @@ class TestLeaveApplication(unittest.TestCase): leave_period = get_leave_period() today = nowdate() holiday_list = 'Test Holiday List for Optional Holiday' - optional_leave_date = add_days(today, 7) + employee = get_employee() + + default_holiday_list = make_holiday_list() + frappe.db.set_value("Company", "_Test Company", "default_holiday_list", default_holiday_list) + first_sunday = get_first_sunday(default_holiday_list) + + optional_leave_date = add_days(first_sunday, 1) if not frappe.db.exists('Holiday List', holiday_list): frappe.get_doc(dict( @@ -253,7 +340,6 @@ class TestLeaveApplication(unittest.TestCase): dict(holiday_date = optional_leave_date, description = 'Test') ] )).insert() - employee = get_employee() frappe.db.set_value('Leave Period', leave_period.name, 'optional_holiday_list', holiday_list) leave_type = 'Test Optional Type' @@ -266,7 +352,7 @@ class TestLeaveApplication(unittest.TestCase): allocate_leaves(employee, leave_period, leave_type, 10) - date = add_days(today, 6) + date = add_days(first_sunday, 2) leave_application = frappe.get_doc(dict( doctype = 'Leave Application', @@ -443,6 +529,7 @@ class TestLeaveApplication(unittest.TestCase): leave_policy = frappe.get_doc({ "doctype": "Leave Policy", + "title": "Test Leave Policy", "leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}] }).insert() @@ -636,13 +723,13 @@ def create_carry_forwarded_allocation(employee, leave_type): carry_forward=1) leave_allocation.submit() -def make_allocation_record(employee=None, leave_type=None): +def make_allocation_record(employee=None, leave_type=None, from_date=None, to_date=None): allocation = frappe.get_doc({ "doctype": "Leave Allocation", "employee": employee or "_T-Employee-00001", "leave_type": leave_type or "_Test Leave Type", - "from_date": "2013-01-01", - "to_date": "2019-12-31", + "from_date": from_date or "2013-01-01", + "to_date": to_date or "2019-12-31", "new_leaves_allocated": 30 }) @@ -691,3 +778,16 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el }).insert() allocate_leave.submit() + + +def get_first_sunday(holiday_list): + month_start_date = get_first_day(nowdate()) + month_end_date = get_last_day(nowdate()) + first_sunday = frappe.db.sql(""" + select holiday_date from `tabHoliday` + where parent = %s + and holiday_date between %s and %s + order by holiday_date + """, (holiday_list, month_start_date, month_end_date))[0][0] + + return first_sunday \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.json b/erpnext/hr/doctype/leave_encashment/leave_encashment.json index 1f6c03f7b6..cc4e53eb90 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json @@ -154,10 +154,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:32:55.492327", + "modified": "2022-01-18 19:16:52.414356", "modified_by": "Administrator", "module": "HR", "name": "Leave Encashment", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -218,7 +219,10 @@ "write": 1 } ], + "search_fields": "employee,employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_period/leave_period.json b/erpnext/hr/doctype/leave_period/leave_period.json index 9e895c34fb..84ce1147e9 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.json +++ b/erpnext/hr/doctype/leave_period/leave_period.json @@ -1,294 +1,108 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "HR-LPR-.YYYY.-.#####", - "beta": 0, "creation": "2018-04-13 15:20:52.864288", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "from_date", + "to_date", + "is_active", + "column_break_3", + "company", + "optional_holiday_list" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "from_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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": "to_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 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, + "default": "0", "fieldname": "is_active", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Active", - "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 Active" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "company", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Company", - "length": 0, - "no_copy": 0, "options": "Company", - "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": "optional_holiday_list", "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": "Holiday List for Optional Leave", - "length": 0, - "no_copy": 0, - "options": "Holiday List", - "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": "Holiday List" } ], - "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-05-30 16:15:43.305502", + "links": [], + "modified": "2022-01-13 13:28:12.951025", "modified_by": "Administrator", "module": "HR", "name": "Leave Period", - "name_case": "", + "naming_rule": "Expression (old style)", "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 }, { - "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": "HR Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "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": "HR User", - "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, + "search_fields": "from_date, to_date, company", "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "states": [], + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy/leave_policy.json b/erpnext/hr/doctype/leave_policy/leave_policy.json index 373095d075..6ac8f20ea2 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy.json +++ b/erpnext/hr/doctype/leave_policy/leave_policy.json @@ -1,131 +1,55 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, + "actions": [], "autoname": "HR-LPOL-.YYYY.-.#####", - "beta": 0, "creation": "2018-04-13 16:06:19.507624", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "title", + "leave_allocations_section", + "leave_policy_details", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, "allow_in_quick_entry": 1, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_allocations_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Leave Allocations", - "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": "Leave Allocations" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_policy_details", "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": "Leave Policy Details", - "length": 0, - "no_copy": 0, "options": "Leave Policy Detail", - "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": "amended_from", "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": "Amended From", - "length": 0, "no_copy": 1, "options": "Leave Policy", - "permlevel": 0, "print_hide": 1, - "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_on_submit": 1, + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-29 08:42:53.363088", + "links": [], + "modified": "2022-01-19 13:07:40.556500", "modified_by": "Administrator", "module": "HR", "name": "Leave Policy", - "name_case": "", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -135,14 +59,10 @@ "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": 1, "write": 1 @@ -154,14 +74,10 @@ "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR Manager", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 @@ -173,26 +89,19 @@ "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR User", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, + "search_fields": "title", "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "states": [], + "title_field": "title", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py index 3dbbef857e..a4b8af759e 100644 --- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py +++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py @@ -24,6 +24,7 @@ def create_leave_policy(**args): args = frappe._dict(args) return frappe.get_doc({ "doctype": "Leave Policy", + "title": "Test Leave Policy", "leave_policy_details": [{ "leave_type": args.leave_type or "_Test Leave Type", "annual_allocation": args.annual_allocation or 10 diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json index 3373350e73..27f0540b24 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json @@ -113,10 +113,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-01 17:54:01.014509", + "modified": "2022-01-13 13:37:11.218882", "modified_by": "Administrator", "module": "HR", "name": "Leave Policy Assignment", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -164,5 +165,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py index dca7e4895e..355370f3a4 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py @@ -56,9 +56,7 @@ class LeavePolicyAssignment(Document): leave_policy_detail.leave_type, leave_policy_detail.annual_allocation, leave_type_details, date_of_joining ) - - leave_allocations[leave_policy_detail.leave_type] = {"name": leave_allocation, "leaves": new_leaves_allocated} - + leave_allocations[leave_policy_detail.leave_type] = {"name": leave_allocation, "leaves": new_leaves_allocated} self.db_set("leaves_allocated", 1) return leave_allocations @@ -130,6 +128,8 @@ class LeavePolicyAssignment(Document): monthly_earned_leave = get_monthly_earned_leave(new_leaves_allocated, leave_type_details.get(leave_type).earned_leave_frequency, leave_type_details.get(leave_type).rounding) new_leaves_allocated = monthly_earned_leave * months_passed + else: + new_leaves_allocated = 0 return new_leaves_allocated diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js index 8b954c46a1..6b75817cba 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js @@ -48,7 +48,16 @@ frappe.listview_settings['Leave Policy Assignment'] = { if (cur_dialog.fields_dict.leave_period.value) { me.set_effective_date(); } - } + }, + get_query() { + let filters = {"is_active": 1}; + if (cur_dialog.fields_dict.company.value) + filters["company"] = cur_dialog.fields_dict.company.value; + + return { + filters: filters + }; + }, }, { fieldtype: "Column Break" diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py index b1861ad4d8..3b7f8ec822 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py @@ -4,6 +4,7 @@ import unittest import frappe +from frappe.utils import add_months, get_first_day, getdate from erpnext.hr.doctype.leave_application.test_leave_application import ( get_employee, @@ -17,9 +18,8 @@ from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import ( test_dependencies = ["Employee"] class TestLeavePolicyAssignment(unittest.TestCase): - def setUp(self): - for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]: + for doctype in ["Leave Period", "Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]: frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec def test_grant_leaves(self): @@ -54,8 +54,8 @@ class TestLeavePolicyAssignment(unittest.TestCase): self.assertEqual(leave_alloc_doc.new_leaves_allocated, 10) self.assertEqual(leave_alloc_doc.leave_type, "_Test Leave Type") - self.assertEqual(leave_alloc_doc.from_date, leave_period.from_date) - self.assertEqual(leave_alloc_doc.to_date, leave_period.to_date) + self.assertEqual(getdate(leave_alloc_doc.from_date), getdate(leave_period.from_date)) + self.assertEqual(getdate(leave_alloc_doc.to_date), getdate(leave_period.to_date)) self.assertEqual(leave_alloc_doc.leave_policy, leave_policy.name) self.assertEqual(leave_alloc_doc.leave_policy_assignment, leave_policy_assignments[0]) @@ -101,6 +101,56 @@ class TestLeavePolicyAssignment(unittest.TestCase): # User are now allowed to grant leave self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 0) + def test_earned_leave_allocation(self): + leave_period = create_leave_period("Test Earned Leave Period") + employee = get_employee() + leave_type = create_earned_leave_type("Test Earned Leave") + + leave_policy = frappe.get_doc({ + "doctype": "Leave Policy", + "title": "Test Leave Policy", + "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 6}] + }).insert() + + data = { + "assignment_based_on": "Leave Period", + "leave_policy": leave_policy.name, + "leave_period": leave_period.name + } + leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data)) + + # leaves allocated should be 0 since it is an earned leave and allocation happens via scheduler based on set frequency + leaves_allocated = frappe.db.get_value("Leave Allocation", { + "leave_policy_assignment": leave_policy_assignments[0] + }, "total_leaves_allocated") + self.assertEqual(leaves_allocated, 0) + def tearDown(self): - for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]: - frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec + frappe.db.rollback() + + +def create_earned_leave_type(leave_type): + frappe.delete_doc_if_exists("Leave Type", leave_type, force=1) + + return frappe.get_doc(dict( + leave_type_name=leave_type, + doctype="Leave Type", + is_earned_leave=1, + earned_leave_frequency="Monthly", + rounding=0.5, + max_leaves_allowed=6 + )).insert() + + +def create_leave_period(name): + frappe.delete_doc_if_exists("Leave Period", name, force=1) + start_date = get_first_day(getdate()) + + return frappe.get_doc(dict( + name=name, + doctype="Leave Period", + from_date=start_date, + to_date=add_months(start_date, 12), + company="_Test Company", + is_active=1 + )).insert() \ No newline at end of file diff --git a/erpnext/hr/doctype/shift_type/shift_type.js b/erpnext/hr/doctype/shift_type/shift_type.js index ba53312bce..7138e3bcf3 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.js +++ b/erpnext/hr/doctype/shift_type/shift_type.js @@ -4,15 +4,32 @@ frappe.ui.form.on('Shift Type', { refresh: function(frm) { frm.add_custom_button( - 'Mark Attendance', - () => frm.call({ - doc: frm.doc, - method: 'process_auto_attendance', - freeze: true, - callback: () => { - frappe.msgprint(__("Attendance has been marked as per employee check-ins")); + __('Mark Attendance'), + () => { + if (!frm.doc.enable_auto_attendance) { + frm.scroll_to_field('enable_auto_attendance'); + frappe.throw(__('Please Enable Auto Attendance and complete the setup first.')); } - }) + + if (!frm.doc.process_attendance_after) { + frm.scroll_to_field('process_attendance_after'); + frappe.throw(__('Please set {0}.', [__('Process Attendance After').bold()])); + } + + if (!frm.doc.last_sync_of_checkin) { + frm.scroll_to_field('last_sync_of_checkin'); + frappe.throw(__('Please set {0}.', [__('Last Sync of Checkin').bold()])); + } + + frm.call({ + doc: frm.doc, + method: 'process_auto_attendance', + freeze: true, + callback: () => { + frappe.msgprint(__('Attendance has been marked as per employee check-ins')); + } + }); + } ); } }); diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.json b/erpnext/hr/doctype/training_feedback/training_feedback.json index cd967d514f..ebf5a506f0 100644 --- a/erpnext/hr/doctype/training_feedback/training_feedback.json +++ b/erpnext/hr/doctype/training_feedback/training_feedback.json @@ -1,443 +1,144 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "HR-TRF-.YYYY.-.#####", - "beta": 0, - "creation": "2016-08-08 06:35:34.158568", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "autoname": "HR-TRF-.YYYY.-.#####", + "creation": "2016-08-08 06:35:34.158568", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "department", + "course", + "column_break_3", + "training_event", + "event_name", + "trainer_name", + "section_break_6", + "feedback", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "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 - }, + "fieldname": "employee", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Read Only", - "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": "Employee Name", - "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 - }, + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Read Only", + "in_global_search": 1, + "label": "Employee Name" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "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": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "training_event.course", - "fieldname": "course", - "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": "Course", - "length": 0, - "no_copy": 0, - "options": "Course", - "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 - }, + "fetch_from": "training_event.course", + "fieldname": "course", + "fieldtype": "Link", + "label": "Course", + "options": "Course", + "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_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "training_event", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Training Event", - "length": 0, - "no_copy": 0, - "options": "Training Event", - "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 - }, + "fieldname": "training_event", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Training Event", + "options": "Training Event", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "training_event.event_name", - "fieldname": "event_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Event Name", - "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 - }, + "fetch_from": "training_event.event_name", + "fieldname": "event_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Event Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "training_event.trainer_name", - "fieldname": "trainer_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Trainer Name", - "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 - }, + "fetch_from": "training_event.trainer_name", + "fieldname": "trainer_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Trainer Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "feedback", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Feedback", - "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 - }, + "fieldname": "feedback", + "fieldtype": "Text", + "label": "Feedback", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "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": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Training Feedback", - "permlevel": 0, - "print_hide": 1, - "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 + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Training Feedback", + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-30 11:28:13.849860", - "modified_by": "Administrator", - "module": "HR", - "name": "Training Feedback", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2022-01-18 19:32:20.805277", + "modified_by": "Administrator", + "module": "HR", + "name": "Training Feedback", + "naming_rule": "Expression (old style)", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Employee", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "employee_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "employee_name, training_event, event_name", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "employee_name" } \ No newline at end of file diff --git a/erpnext/hr/doctype/training_result/training_result.json b/erpnext/hr/doctype/training_result/training_result.json index dd7abd7753..f28669e3c2 100644 --- a/erpnext/hr/doctype/training_result/training_result.json +++ b/erpnext/hr/doctype/training_result/training_result.json @@ -1,226 +1,83 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 1, - "autoname": "HR-TRR-.YYYY.-.#####", - "beta": 0, - "creation": "2016-11-04 02:13:48.407576", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_rename": 1, + "autoname": "HR-TRR-.YYYY.-.#####", + "creation": "2016-11-04 02:13:48.407576", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "training_event", + "section_break_3", + "employees", + "amended_from", + "employee_emails" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "training_event", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Training Event", - "length": 0, - "no_copy": 0, - "options": "Training Event", - "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, + "fieldname": "training_event", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Training Event", + "options": "Training Event", + "reqd": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employees", - "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": "Employees", - "length": 0, - "no_copy": 0, - "options": "Training Result Employee", - "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 - }, + "fieldname": "employees", + "fieldtype": "Table", + "label": "Employees", + "options": "Training Result Employee" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "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": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Training Result", - "permlevel": 0, - "print_hide": 1, - "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 - }, + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Training Result", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee_emails", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee Emails", - "length": 0, - "no_copy": 0, - "options": "Email", - "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 + "fieldname": "employee_emails", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Employee Emails", + "options": "Email" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:47.614563", - "modified_by": "Administrator", - "module": "HR", - "name": "Training Result", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2022-01-18 19:31:44.900034", + "modified_by": "Administrator", + "module": "HR", + "name": "Training Result", + "naming_rule": "Expression (old style)", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "training_event", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "training_event", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "training_event" } \ No newline at end of file diff --git a/erpnext/hr/doctype/travel_request/travel_request.json b/erpnext/hr/doctype/travel_request/travel_request.json index 441907c02d..7908e1a8ea 100644 --- a/erpnext/hr/doctype/travel_request/travel_request.json +++ b/erpnext/hr/doctype/travel_request/travel_request.json @@ -216,10 +216,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2019-12-12 18:42:26.451359", + "modified": "2022-01-18 19:19:33.678664", "modified_by": "Administrator", "module": "HR", "name": "Travel Request", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -235,7 +236,10 @@ "write": 1 } ], + "search_fields": "employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index 85e641c856..30cec1b4a8 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -5,7 +5,7 @@ "label": "Outgoing Salary" } ], - "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Human Resource\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Outgoing Salary\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Employee\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leave Application\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Attendance\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Applicant\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Monthly Attendance Sheet\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Lifecycle\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Exit\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Shift Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Leaves\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Attendance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Expense Claims\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loans\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Recruitment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Performance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Fleet Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Training\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Human Resource\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Outgoing Salary\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Employee\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leave Application\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Attendance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Applicant\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Monthly Attendance Sheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Lifecycle\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Exit\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Shift Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Leaves\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Attendance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Expense Claims\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loans\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Recruitment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Performance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Fleet Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Training\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", "creation": "2020-03-02 15:48:58.322521", "docstatus": 0, "doctype": "Workspace", @@ -1642,7 +1642,7 @@ "type": "Link" } ], - "modified": "2021-12-05 22:05:13.004462", + "modified": "2022-01-13 17:38:45.489128", "modified_by": "Administrator", "module": "HR", "name": "HR", @@ -1651,7 +1651,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 14, + "sequence_id": 14.0, "shortcuts": [ { "color": "Green", diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 6479853246..93ef217042 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -13,8 +13,10 @@ "column_break_3", "company", "posting_date", - "is_term_loan", "rate_of_interest", + "payroll_payable_account", + "is_term_loan", + "repay_from_salary", "payment_details_section", "due_date", "pending_principal_amount", @@ -243,15 +245,31 @@ "label": "Total Penalty Paid", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "depends_on": "eval:doc.repay_from_salary", + "fieldname": "payroll_payable_account", + "fieldtype": "Link", + "label": "Payroll Payable Account", + "mandatory_depends_on": "eval:doc.repay_from_salary", + "options": "Account" + }, + { + "default": "0", + "fetch_from": "against_loan.repay_from_salary", + "fieldname": "repay_from_salary", + "fieldtype": "Check", + "label": "Repay From Salary" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:10:00.935364", + "modified": "2022-01-06 01:51:06.707782", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -287,5 +305,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 2abb3957b2..7e997e87c3 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -320,74 +320,79 @@ class LoanRepayment(AccountsController): else: remarks = _("Repayment against Loan: ") + self.against_loan - if not loan_details.repay_from_salary: - if self.total_penalty_paid: - gle_map.append( - self.get_gl_dict({ - "account": loan_details.loan_account, - "against": loan_details.payment_account, - "debit": self.total_penalty_paid, - "debit_in_account_currency": self.total_penalty_paid, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("Penalty against loan:") + self.against_loan, - "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, - "posting_date": getdate(self.posting_date) - }) - ) - - gle_map.append( - self.get_gl_dict({ - "account": loan_details.penalty_income_account, - "against": loan_details.payment_account, - "credit": self.total_penalty_paid, - "credit_in_account_currency": self.total_penalty_paid, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("Penalty against loan:") + self.against_loan, - "cost_center": self.cost_center, - "posting_date": getdate(self.posting_date) - }) - ) - - gle_map.append( - self.get_gl_dict({ - "account": loan_details.payment_account, - "against": loan_details.loan_account + ", " + loan_details.interest_income_account - + ", " + loan_details.penalty_income_account, - "debit": self.amount_paid, - "debit_in_account_currency": self.amount_paid, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": remarks, - "cost_center": self.cost_center, - "posting_date": getdate(self.posting_date) - }) - ) + if self.repay_from_salary: + payment_account = self.payroll_payable_account + else: + payment_account = loan_details.payment_account + if self.total_penalty_paid: gle_map.append( self.get_gl_dict({ "account": loan_details.loan_account, - "party_type": loan_details.applicant_type, - "party": loan_details.applicant, "against": loan_details.payment_account, - "credit": self.amount_paid, - "credit_in_account_currency": self.amount_paid, + "debit": self.total_penalty_paid, + "debit_in_account_currency": self.total_penalty_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": remarks, + "remarks": _("Penalty against loan:") + self.against_loan, + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": getdate(self.posting_date) + }) + ) + + gle_map.append( + self.get_gl_dict({ + "account": loan_details.penalty_income_account, + "against": payment_account, + "credit": self.total_penalty_paid, + "credit_in_account_currency": self.total_penalty_paid, + "against_voucher_type": "Loan", + "against_voucher": self.against_loan, + "remarks": _("Penalty against loan:") + self.against_loan, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date) }) ) - if gle_map: - make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) + gle_map.append( + self.get_gl_dict({ + "account": payment_account, + "against": loan_details.loan_account + ", " + loan_details.interest_income_account + + ", " + loan_details.penalty_income_account, + "debit": self.amount_paid, + "debit_in_account_currency": self.amount_paid, + "against_voucher_type": "Loan", + "against_voucher": self.against_loan, + "remarks": remarks, + "cost_center": self.cost_center, + "posting_date": getdate(self.posting_date) + }) + ) + + gle_map.append( + self.get_gl_dict({ + "account": loan_details.loan_account, + "party_type": loan_details.applicant_type, + "party": loan_details.applicant, + "against": payment_account, + "credit": self.amount_paid, + "credit_in_account_currency": self.amount_paid, + "against_voucher_type": "Loan", + "against_voucher": self.against_loan, + "remarks": remarks, + "cost_center": self.cost_center, + "posting_date": getdate(self.posting_date) + }) + ) + + if gle_map: + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) def create_repayment_entry(loan, applicant, company, posting_date, loan_type, - payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None): + payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None, + payroll_payable_account=None): lr = frappe.get_doc({ "doctype": "Loan Repayment", @@ -400,7 +405,8 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type, "interest_payable": interest_payable, "payable_principal_amount": payable_principal_amount, "amount_paid": amount_paid, - "loan_type": loan_type + "loan_type": loan_type, + "payroll_payable_account": payroll_payable_account }).insert() return lr diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json index 3d07081215..b7b20d945d 100644 --- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json +++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json @@ -70,7 +70,6 @@ { "fieldname": "loan_repayment_entry", "fieldtype": "Link", - "hidden": 1, "label": "Loan Repayment Entry", "no_copy": 1, "options": "Loan Repayment", @@ -88,7 +87,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-03-14 20:47:11.725818", + "modified": "2022-01-31 14:50:14.823213", "modified_by": "Administrator", "module": "Loan Management", "name": "Salary Slip Loan", @@ -97,5 +96,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json index 7deee0d461..b08a85e213 100644 --- a/erpnext/loan_management/workspace/loan_management/loan_management.json +++ b/erpnext/loan_management/workspace/loan_management/loan_management.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan Application\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Processes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Disbursement and Repayment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Security\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan Application\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loan\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loan Processes\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Disbursement and Repayment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loan Security\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", "creation": "2020-03-12 16:35:55.299820", "docstatus": 0, "doctype": "Workspace", @@ -238,7 +238,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:18:13.350905", + "modified": "2022-01-13 17:39:16.790152", "modified_by": "Administrator", "module": "Loan Management", "name": "Loans", @@ -247,7 +247,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 16, + "sequence_id": 16.0, "shortcuts": [ { "color": "Green", diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 2ffae1a4f2..07d928c221 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - import frappe from frappe import _, throw from frappe.utils import add_days, cint, cstr, date_diff, formatdate, getdate @@ -306,13 +305,18 @@ class MaintenanceSchedule(TransactionBase): return schedule.name @frappe.whitelist() -def update_serial_nos(s_id): - serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') +def get_serial_nos_from_schedule(item_code, schedule=None): + serial_nos = [] + if schedule: + serial_nos = frappe.db.get_value('Maintenance Schedule Item', { + 'parent': schedule, + 'item_code': item_code + }, 'serial_no') + if serial_nos: serial_nos = get_serial_nos(serial_nos) - return serial_nos - else: - return False + + return serial_nos @frappe.whitelist() def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None): @@ -320,12 +324,9 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No def update_status_and_detail(source, target, parent): target.maintenance_type = "Scheduled" - target.maintenance_schedule = source.name target.maintenance_schedule_detail = s_id - def update_sales_and_serial(source, target, parent): - sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') - target.service_person = sales_person + def update_serial(source, target, parent): serial_nos = get_serial_nos(target.serial_no) if len(serial_nos) == 1: target.serial_no = serial_nos[0] @@ -346,7 +347,10 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No "Maintenance Schedule Item": { "doctype": "Maintenance Visit Purpose", "condition": lambda doc: doc.item_name == item_name, - "postprocess": update_sales_and_serial + "field_map": { + "sales_person": "service_person" + }, + "postprocess": update_serial } }, target_doc) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 501712613a..6e727e53ef 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -4,11 +4,15 @@ import unittest import frappe +from frappe.utils import format_date from frappe.utils.data import add_days, formatdate, today from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import ( + get_serial_nos_from_schedule, make_maintenance_visit, ) +from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item # test_records = frappe.get_test_records('Maintenance Schedule') @@ -79,6 +83,49 @@ class TestMaintenanceSchedule(unittest.TestCase): #checks if visit status is back updated in schedule self.assertTrue(ms.schedules[1].completion_status, "Partially Completed") + self.assertEqual(format_date(visit.mntc_date), format_date(ms.schedules[1].actual_date)) + + #checks if visit status is updated on cancel + visit.cancel() + ms.reload() + self.assertTrue(ms.schedules[1].completion_status, "Pending") + self.assertEqual(ms.schedules[1].actual_date, None) + + def test_serial_no_filters(self): + # Without serial no. set in schedule -> returns None + item_code = "_Test Serial Item" + make_serial_item_with_serial(item_code) + ms = make_maintenance_schedule(item_code=item_code) + ms.submit() + + s_item = ms.schedules[0] + mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) + mvi = mv.purposes[0] + serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name) + self.assertEqual(serial_nos, None) + + # With serial no. set in schedule -> returns serial nos. + make_serial_item_with_serial(item_code) + ms = make_maintenance_schedule(item_code=item_code, serial_no="TEST001, TEST002") + ms.submit() + + s_item = ms.schedules[0] + mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) + mvi = mv.purposes[0] + serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name) + self.assertEqual(serial_nos, ["TEST001", "TEST002"]) + + frappe.db.rollback() + +def make_serial_item_with_serial(item_code): + serial_item_doc = create_item(item_code, is_stock_item=1) + if not serial_item_doc.has_serial_no or not serial_item_doc.serial_no_series: + serial_item_doc.has_serial_no = 1 + serial_item_doc.serial_no_series = "TEST.###" + serial_item_doc.save(ignore_permissions=True) + active_serials = frappe.db.get_all('Serial No', {"status": "Active", "item_code": item_code}) + if len(active_serials) < 2: + make_serialized_item(item_code=item_code) def get_events(ms): return frappe.get_all("Event Participants", filters={ @@ -87,17 +134,18 @@ def get_events(ms): "parenttype": "Event" }) -def make_maintenance_schedule(): +def make_maintenance_schedule(**args): ms = frappe.new_doc("Maintenance Schedule") ms.company = "_Test Company" ms.customer = "_Test Customer" ms.transaction_date = today() ms.append("items", { - "item_code": "_Test Item", + "item_code": args.get("item_code") or "_Test Item", "start_date": today(), "periodicity": "Weekly", "no_of_visits": 4, + "serial_no": args.get("serial_no"), "sales_person": "Sales Team", }) ms.insert(ignore_permissions=True) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index d2197a6877..72686e7403 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -2,52 +2,54 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.maintenance"); -var serial_nos = []; frappe.ui.form.on('Maintenance Visit', { - refresh: function (frm) { - //filters for serial_no based on item_code - frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) { - let item = locals[cdt][cdn]; - if (serial_nos) { - return { - filters: { - 'item_code': item.item_code, - 'name': ["in", serial_nos] - } - }; - } else { - return { - filters: { - 'item_code': item.item_code - } - }; - } - }); - }, setup: function (frm) { frm.set_query('contact_person', erpnext.queries.contact_query); frm.set_query('customer_address', erpnext.queries.address_query); frm.set_query('customer', erpnext.queries.customer); }, - onload: function (frm, cdt, cdn) { - let item = locals[cdt][cdn]; + onload: function (frm) { + // filters for serial no based on item code if (frm.doc.maintenance_type === "Scheduled") { - const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail; + let item_code = frm.doc.purposes[0].item_code; frappe.call({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.get_serial_nos_from_schedule", args: { - s_id: schedule_id - }, - callback: function (r) { - serial_nos = r.message; + schedule: frm.doc.maintenance_schedule, + item_code: item_code } + }).then((r) => { + let serial_nos = r.message; + frm.set_query('serial_no', 'purposes', () => { + if (serial_nos.length > 0) { + return { + filters: { + 'item_code': item_code, + 'name': ["in", serial_nos] + } + }; + } + return { + filters: { + 'item_code': item_code + } + }; + }); + }); + } else { + frm.set_query('serial_no', 'purposes', (frm, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code + } + }; }); } if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { - frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes"); frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, @@ -60,7 +62,6 @@ frappe.ui.form.on('Maintenance Visit', { contact_person: function (frm) { erpnext.utils.get_contact_details(frm); } - }) // TODO commonify this code diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json index ec32239518..4a6aa0a34b 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -179,8 +179,7 @@ "label": "Purposes", "oldfieldname": "maintenance_visit_details", "oldfieldtype": "Table", - "options": "Maintenance Visit Purpose", - "reqd": 1 + "options": "Maintenance Visit Purpose" }, { "fieldname": "more_info", @@ -294,10 +293,11 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-27 16:06:17.352572", + "modified": "2021-12-17 03:10:27.608112", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 5a87b162af..6fe2466be2 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.utils import get_datetime +from frappe.utils import format_date, get_datetime from erpnext.utilities.transaction_base import TransactionBase @@ -18,25 +18,34 @@ class MaintenanceVisit(TransactionBase): if d.serial_no and not frappe.db.exists("Serial No", d.serial_no): frappe.throw(_("Serial No {0} does not exist").format(d.serial_no)) + def validate_purpose_table(self): + if not self.purposes: + frappe.throw(_("Add Items in the Purpose Table"), title="Purposes Required") + def validate_maintenance_date(self): if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail: item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference') if item_ref: start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date']) if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date): - frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date)) + frappe.throw(_("Date must be between {0} and {1}") + .format(format_date(start_date), format_date(end_date))) + def validate(self): self.validate_serial_no() self.validate_maintenance_date() + self.validate_purpose_table() - def update_completion_status(self): + def update_status_and_actual_date(self, cancel=False): + status = "Pending" + actual_date = None + if not cancel: + status = self.completion_status + actual_date = self.mntc_date if self.maintenance_schedule_detail: - frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', self.completion_status) - - def update_actual_date(self): - if self.maintenance_schedule_detail: - frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', self.mntc_date) + frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', status) + frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', actual_date) def update_customer_issue(self, flag): if not self.maintenance_schedule: @@ -97,12 +106,12 @@ class MaintenanceVisit(TransactionBase): def on_submit(self): self.update_customer_issue(1) frappe.db.set(self, 'status', 'Submitted') - self.update_completion_status() - self.update_actual_date() + self.update_status_and_actual_date() def on_cancel(self): self.check_if_last_visit() frappe.db.set(self, 'status', 'Cancelled') + self.update_status_and_actual_date(cancel=True) def on_update(self): pass diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 6d35d65bea..fc3b971bcb 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -331,7 +331,7 @@ frappe.ui.form.on("BOM", { }); }); - if (has_template_rm) { + if (has_template_rm && has_template_rm.length) { dialog.fields_dict.items.grid.refresh(); } }, @@ -467,7 +467,8 @@ var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) { "uom": d.uom, "stock_uom": d.stock_uom, "conversion_factor": d.conversion_factor, - "sourced_by_supplier": d.sourced_by_supplier + "sourced_by_supplier": d.sourced_by_supplier, + "do_not_explode": d.do_not_explode }, callback: function(r) { d = locals[cdt][cdn]; @@ -640,6 +641,13 @@ frappe.ui.form.on("BOM Operation", "workstation", function(frm, cdt, cdn) { }); }); +frappe.ui.form.on("BOM Item", { + do_not_explode: function(frm, cdt, cdn) { + get_bom_material_detail(frm.doc, cdt, cdn, false); + } +}) + + frappe.ui.form.on("BOM Item", "qty", function(frm, cdt, cdn) { var d = locals[cdt][cdn]; d.stock_qty = d.qty * d.conversion_factor; diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 218ac64d8d..0b44196940 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -37,7 +37,6 @@ "inspection_required", "quality_inspection_template", "column_break_31", - "bom_level", "section_break_33", "items", "scrap_section", @@ -522,13 +521,6 @@ "fieldname": "column_break_31", "fieldtype": "Column Break" }, - { - "default": "0", - "fieldname": "bom_level", - "fieldtype": "Int", - "label": "BOM Level", - "read_only": 1 - }, { "fieldname": "section_break_33", "fieldtype": "Section Break", @@ -540,7 +532,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-11-18 13:04:16.271975", + "modified": "2022-01-30 21:27:54.727298", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", @@ -577,5 +569,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index f82d9a0d55..d640f3fda7 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -149,12 +149,12 @@ class BOM(WebsiteGenerator): self.set_bom_material_details() self.set_bom_scrap_items_detail() self.validate_materials() + self.validate_transfer_against() self.set_routing_operations() self.validate_operations() self.calculate_cost() self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False) - self.set_bom_level() self.validate_scrap_items() def get_context(self, context): @@ -203,6 +203,10 @@ class BOM(WebsiteGenerator): for item in self.get("items"): self.validate_bom_currency(item) + item.bom_no = '' + if not item.do_not_explode: + item.bom_no = item.bom_no + ret = self.get_bom_material_detail({ "company": self.company, "item_code": item.item_code, @@ -214,8 +218,10 @@ class BOM(WebsiteGenerator): "uom": item.uom, "stock_uom": item.stock_uom, "conversion_factor": item.conversion_factor, - "sourced_by_supplier": item.sourced_by_supplier + "sourced_by_supplier": item.sourced_by_supplier, + "do_not_explode": item.do_not_explode }) + for r in ret: if not item.get(r): item.set(r, ret[r]) @@ -267,6 +273,9 @@ class BOM(WebsiteGenerator): 'sourced_by_supplier' : args.get('sourced_by_supplier', 0) } + if args.get('do_not_explode'): + ret_item['bom_no'] = '' + return ret_item def validate_bom_currency(self, item): @@ -530,16 +539,6 @@ class BOM(WebsiteGenerator): row.hour_rate = (hour_rate / flt(self.conversion_rate) if self.conversion_rate and hour_rate else hour_rate) - if self.routing: - time_in_mins = flt(frappe.db.get_value("BOM Operation", { - "workstation": row.workstation, - "operation": row.operation, - "parent": self.routing - }, ["time_in_mins"])) - - if time_in_mins: - row.time_in_mins = time_in_mins - if row.hour_rate and row.time_in_mins: row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate) row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0 @@ -691,6 +690,12 @@ class BOM(WebsiteGenerator): if act_pbom and act_pbom[0][0]: frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs")) + def validate_transfer_against(self): + if not self.with_operations: + self.transfer_material_against = "Work Order" + if not self.transfer_material_against and not self.is_new(): + frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value")) + def set_routing_operations(self): if self.routing and self.with_operations and not self.operations: self.get_routing() @@ -710,20 +715,6 @@ class BOM(WebsiteGenerator): """Get a complete tree representation preserving order of child items.""" return BOMTree(self.name) - def set_bom_level(self, update=False): - levels = [] - - self.bom_level = 0 - for row in self.items: - if row.bom_no: - levels.append(frappe.get_cached_value("BOM", row.bom_no, "bom_level") or 0) - - if levels: - self.bom_level = max(levels) + 1 - - if update: - self.db_set("bom_level", self.bom_level) - def validate_scrap_items(self): for item in self.scrap_items: msg = "" diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 178d92c26c..53437c8012 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -385,6 +385,53 @@ class TestBOM(ERPNextTestCase): self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results") self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results") + def test_exclude_exploded_items_from_bom(self): + bom_no = get_default_bom() + new_bom = frappe.copy_doc(frappe.get_doc('BOM', bom_no)) + for row in new_bom.items: + if row.item_code == '_Test Item Home Desktop Manufactured': + self.assertTrue(row.bom_no) + row.do_not_explode = True + + new_bom.docstatus = 0 + new_bom.save() + new_bom.load_from_db() + + for row in new_bom.items: + if row.item_code == '_Test Item Home Desktop Manufactured' and row.do_not_explode: + self.assertFalse(row.bom_no) + + new_bom.delete() + + def test_valid_transfer_defaults(self): + bom_with_op = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1}) + bom = frappe.copy_doc(frappe.get_doc("BOM", bom_with_op), ignore_no_copy=False) + + # test defaults + bom.docstatus = 0 + bom.transfer_material_against = None + bom.insert() + self.assertEqual(bom.transfer_material_against, "Work Order") + + bom.reload() + bom.transfer_material_against = None + with self.assertRaises(frappe.ValidationError): + bom.save() + bom.reload() + + # test saner default + bom.transfer_material_against = "Job Card" + bom.with_operations = 0 + bom.save() + self.assertEqual(bom.transfer_material_against, "Work Order") + + # test no value on existing doc + bom.transfer_material_against = None + bom.with_operations = 0 + bom.save() + self.assertEqual(bom.transfer_material_against, "Work Order") + bom.delete() + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index 4c9877f52b..3406215cbb 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -10,6 +10,7 @@ "item_name", "operation", "column_break_3", + "do_not_explode", "bom_no", "source_warehouse", "allow_alternative_item", @@ -73,6 +74,7 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:!doc.do_not_explode", "fieldname": "bom_no", "fieldtype": "Link", "in_filter": 1, @@ -284,18 +286,25 @@ "fieldname": "sourced_by_supplier", "fieldtype": "Check", "label": "Sourced by Supplier" + }, + { + "default": "0", + "fieldname": "do_not_explode", + "fieldtype": "Check", + "label": "Do Not Explode" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-08 14:19:37.563300", + "modified": "2022-01-24 16:57:57.020232", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 7cec7f515a..4290ca3e4c 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -559,9 +559,11 @@ class ProductionPlan(Document): get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) - def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): - bom_data = sorted(bom_data, key = lambda i: i.bom_level) + self.sub_assembly_items.sort(key= lambda d: d.bom_level, reverse=True) + for idx, row in enumerate(self.sub_assembly_items, start=1): + row.idx = idx + def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): for data in bom_data: data.qty = data.stock_qty data.production_plan_item = row.name @@ -947,11 +949,8 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): locations = get_available_item_locations(item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True) - if not locations: - new_mr_items.append(item) - return - required_qty = item.get("quantity") + # get available material by transferring to production warehouse for d in locations: if required_qty <=0: return @@ -962,14 +961,34 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): new_dict.update({ "quantity": quantity, "material_request_type": "Material Transfer", + "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM "from_warehouse": d.get("warehouse") }) required_qty -= quantity new_mr_items.append(new_dict) + # raise purchase request for remaining qty if required_qty: + stock_uom, purchase_uom = frappe.db.get_value( + 'Item', + item['item_code'], + ['stock_uom', 'purchase_uom'] + ) + + if purchase_uom != stock_uom and purchase_uom == item['uom']: + conversion_factor = get_uom_conversion_factor(item['item_code'], item['uom']) + if not (conversion_factor or frappe.flags.show_qty_in_stock_uom): + frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}") + .format(purchase_uom, stock_uom, item['item_code'])) + + required_qty = required_qty / conversion_factor + + if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): + required_qty = ceil(required_qty) + item["quantity"] = required_qty + new_mr_items.append(item) @frappe.whitelist() @@ -987,9 +1006,6 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0): for d in data: if d.expandable: parent_item_code = frappe.get_cached_value("BOM", bom_no, "item") - bom_level = (frappe.get_cached_value("BOM", d.value, "bom_level") - if d.value else 0) - stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty) bom_data.append(frappe._dict({ 'parent_item_code': parent_item_code, @@ -1000,7 +1016,7 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0): 'uom': d.stock_uom, 'bom_no': d.value, 'is_sub_contracted_item': d.is_sub_contracted_item, - 'bom_level': bom_level, + 'bom_level': indent, 'indent': indent, 'stock_qty': stock_qty })) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2febc1e23c..21a126b2a7 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -347,6 +347,45 @@ class TestProductionPlan(ERPNextTestCase): frappe.db.rollback() + def test_subassmebly_sorting(self): + """ Test subassembly sorting in case of multiple items with nested BOMs""" + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + + prefix = "_TestLevel_" + boms = { + "Assembly": { + "SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},}, + "SubAssembly2": {"ChildPart3": {}}, + "SubAssembly3": {"SubSubAssy1": {"ChildPart4": {}}}, + "ChildPart5": {}, + "ChildPart6": {}, + "SubAssembly4": {"SubSubAssy2": {"ChildPart7": {}}}, + }, + "MegaDeepAssy": { + "SecretSubassy": {"SecretPart": {"VerySecret" : { "SuperSecret": {"Classified": {}}}},}, + # ^ assert that this is + # first item in subassy table + } + } + create_nested_bom(boms, prefix=prefix) + + items = [prefix + item_code for item_code in boms.keys()] + plan = create_production_plan(item_code=items[0], do_not_save=True) + plan.append("po_items", { + 'use_multi_level_bom': 1, + 'item_code': items[1], + 'bom_no': frappe.db.get_value('Item', items[1], 'default_bom'), + 'planned_qty': 1, + 'planned_start_date': now_datetime() + }) + plan.get_sub_assembly_items() + + bom_level_order = [d.bom_level for d in plan.sub_assembly_items] + self.assertEqual(bom_level_order, sorted(bom_level_order, reverse=True)) + # lowest most level of subassembly should be first + self.assertIn("SuperSecret", plan.sub_assembly_items[0].production_item) + + def create_production_plan(**args): args = frappe._dict(args) diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json index 657ee35a85..45ea26c3a8 100644 --- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json @@ -102,7 +102,6 @@ }, { "columns": 1, - "fetch_from": "bom_no.bom_level", "fieldname": "bom_level", "fieldtype": "Int", "in_list_view": 1, @@ -189,7 +188,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-28 20:10:56.296410", + "modified": "2022-01-30 21:31:10.527559", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Sub Assembly Item", @@ -198,5 +197,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index e90b0a7d6d..8bd60ea4ac 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -46,6 +46,7 @@ class TestRouting(ERPNextTestCase): wo_doc.delete() def test_update_bom_operation_time(self): + """Update cost shouldn't update routing times.""" operations = [ { "operation": "Test Operation A", @@ -85,8 +86,8 @@ class TestRouting(ERPNextTestCase): routing_doc.save() bom_doc.update_cost() bom_doc.reload() - self.assertEqual(bom_doc.operations[0].time_in_mins, 90) - self.assertEqual(bom_doc.operations[1].time_in_mins, 42.2) + self.assertEqual(bom_doc.operations[0].time_in_mins, 30) + self.assertEqual(bom_doc.operations[1].time_in_mins, 20) def setup_operations(rows): diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 9926b15894..a399edda70 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2,7 +2,7 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.utils import add_months, cint, flt, now, today +from frappe.utils import add_days, add_months, cint, flt, now, today from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom @@ -12,6 +12,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import ( OverProductionError, StockOverProductionError, close_work_order, + make_job_card, make_stock_entry, stop_unstop, ) @@ -804,6 +805,34 @@ class TestWorkOrder(ERPNextTestCase): if row.is_scrap_item: self.assertEqual(row.qty, 1) + # Partial Job Card 1 with qty 10 + wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1) + job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + update_job_card(job_card, 10) + + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + for row in stock_entry.items: + if row.is_scrap_item: + self.assertEqual(row.qty, 2) + + # Partial Job Card 2 with qty 10 + operations = [] + wo_order.load_from_db() + for row in wo_order.operations: + n_dict = row.as_dict() + n_dict['qty'] = 10 + n_dict['pending_qty'] = 10 + operations.append(n_dict) + + make_job_card(wo_order.name, operations) + job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name, 'docstatus': 0}, 'name') + update_job_card(job_card, 10) + + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + for row in stock_entry.items: + if row.is_scrap_item: + self.assertEqual(row.qty, 2) + def test_close_work_order(self): items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO', 'Test RM Item 2 for Closed WO'] @@ -882,8 +911,57 @@ class TestWorkOrder(ERPNextTestCase): self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins) + def test_partial_manufacture_entries(self): + cancel_stock_entry = [] -def update_job_card(job_card): + frappe.db.set_value("Manufacturing Settings", None, + "backflush_raw_materials_based_on", "Material Transferred for Manufacture") + + wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100) + ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item", + target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0) + ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", + target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0) + + cancel_stock_entry.extend([ste1.name, ste2.name]) + + sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100)) + for row in sm.get('items'): + if row.get('item_code') == '_Test Item': + row.qty = 110 + + sm.submit() + cancel_stock_entry.append(sm.name) + + s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90)) + for row in s.get('items'): + if row.get('item_code') == '_Test Item': + self.assertEqual(row.get('qty'), 100) + s.submit() + cancel_stock_entry.append(s.name) + + s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5)) + for row in s1.get('items'): + if row.get('item_code') == '_Test Item': + self.assertEqual(row.get('qty'), 5) + s1.submit() + cancel_stock_entry.append(s1.name) + + s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5)) + for row in s2.get('items'): + if row.get('item_code') == '_Test Item': + self.assertEqual(row.get('qty'), 5) + + cancel_stock_entry.reverse() + for ste in cancel_stock_entry: + doc = frappe.get_doc("Stock Entry", ste) + doc.cancel() + + frappe.db.set_value("Manufacturing Settings", None, + "backflush_raw_materials_based_on", "BOM") + +def update_job_card(job_card, jc_qty=None): + employee = frappe.db.get_value('Employee', {'status': 'Active'}, 'name') job_card_doc = frappe.get_doc('Job Card', job_card) job_card_doc.set('scrap_items', [ { @@ -896,8 +974,12 @@ def update_job_card(job_card): }, ]) + if jc_qty: + job_card_doc.for_quantity = jc_qty + job_card_doc.append('time_logs', { 'from_time': now(), + 'employee': employee, 'time_in_mins': 60, 'completed_qty': job_card_doc.for_quantity }) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 5ffbb0374e..6433a99283 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -131,16 +131,14 @@ frappe.ui.form.on("Work Order", { erpnext.work_order.set_custom_buttons(frm); frm.set_intro(""); - if (frm.doc.docstatus === 0 && !frm.doc.__islocal) { + if (frm.doc.docstatus === 0 && !frm.is_new()) { frm.set_intro(__("Submit this Work Order for further processing.")); + } else { + frm.trigger("show_progress_for_items"); + frm.trigger("show_progress_for_operations"); } if (frm.doc.status != "Closed") { - if (frm.doc.docstatus===1) { - frm.trigger('show_progress_for_items'); - frm.trigger('show_progress_for_operations'); - } - if (frm.doc.docstatus === 1 && frm.doc.operations && frm.doc.operations.length) { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 12cd58f418..9452a63d70 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -333,12 +333,13 @@ "options": "fa fa-wrench" }, { - "default": "Work Order", "depends_on": "operations", + "fetch_from": "bom_no.transfer_material_against", + "fetch_if_empty": 1, "fieldname": "transfer_material_against", "fieldtype": "Select", "label": "Transfer Material Against", - "options": "Work Order\nJob Card" + "options": "\nWork Order\nJob Card" }, { "fieldname": "operations", @@ -574,7 +575,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-11-08 17:36:07.016300", + "modified": "2022-01-24 21:18:12.160114", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", @@ -607,6 +608,7 @@ ], "sort_field": "modified", "sort_order": "ASC", + "states": [], "title_field": "production_item", "track_changes": 1, "track_seen": 1 diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 170454c823..03e0910345 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -31,6 +31,7 @@ from erpnext.stock.doctype.batch.batch import make_batch from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life from erpnext.stock.doctype.serial_no.serial_no import ( auto_make_serial_nos, + clean_serial_no_string, get_auto_serial_nos, get_serial_nos, ) @@ -65,6 +66,7 @@ class WorkOrder(Document): self.validate_warehouse_belongs_to_company() self.calculate_operating_cost() self.validate_qty() + self.validate_transfer_against() self.validate_operation_time() self.status = self.get_status() @@ -72,6 +74,7 @@ class WorkOrder(Document): self.set_required_items(reset_only_qty = len(self.get("required_items"))) + def validate_sales_order(self): if self.sales_order: self.check_sales_order_on_hold_or_close() @@ -356,6 +359,7 @@ class WorkOrder(Document): frappe.delete_doc("Batch", row.name) def make_serial_nos(self, args): + self.serial_no = clean_serial_no_string(self.serial_no) serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series") if serial_no_series: self.serial_no = get_auto_serial_nos(serial_no_series, self.qty) @@ -625,6 +629,16 @@ class WorkOrder(Document): if not self.qty > 0: frappe.throw(_("Quantity to Manufacture must be greater than 0.")) + def validate_transfer_against(self): + if not self.docstatus == 1: + # let user configure operations until they're ready to submit + return + if not self.operations: + self.transfer_material_against = "Work Order" + if not self.transfer_material_against: + frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value")) + + def validate_operation_time(self): for d in self.operations: if not d.time_in_mins > 0: diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py index 25de2e0379..19a80ab407 100644 --- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py +++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py @@ -26,8 +26,7 @@ def get_exploded_items(bom, data, indent=0, qty=1): 'item_code': item.item_code, 'item_name': item.item_name, 'indent': indent, - 'bom_level': (frappe.get_cached_value("BOM", item.bom_no, "bom_level") - if item.bom_no else ""), + 'bom_level': indent, 'bom': item.bom_no, 'qty': item.qty * qty, 'uom': item.uom, @@ -73,7 +72,7 @@ def get_columns(): }, { "label": "BOM Level", - "fieldtype": "Data", + "fieldtype": "Int", "fieldname": "bom_level", "width": 100 }, diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js index 7468e34020..0eb22a22f7 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js @@ -4,6 +4,39 @@ frappe.query_reports["BOM Operations Time"] = { "filters": [ - + { + "fieldname": "item_code", + "label": __("Item Code"), + "fieldtype": "Link", + "width": "100", + "options": "Item", + "get_query": () =>{ + return { + filters: { "disabled": 0, "is_stock_item": 1 } + } + } + }, + { + "fieldname": "bom_id", + "label": __("BOM ID"), + "fieldtype": "MultiSelectList", + "width": "100", + "options": "BOM", + "get_data": function(txt) { + return frappe.db.get_link_options("BOM", txt); + }, + "get_query": () =>{ + return { + filters: { "docstatus": 1, "is_active": 1, "with_operations": 1 } + } + } + }, + { + "fieldname": "workstation", + "label": __("Workstation"), + "fieldtype": "Link", + "width": "100", + "options": "Workstation" + }, ] }; diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json index 665c5b9f79..8162017ca8 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json @@ -1,14 +1,16 @@ { - "add_total_row": 0, + "add_total_row": 1, + "columns": [], "creation": "2020-03-03 01:41:20.862521", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", "letter_head": "", - "modified": "2020-03-03 01:41:20.862521", + "modified": "2022-01-20 14:21:47.771591", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operations Time", diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py index e7a818abd5..eda9eb9d70 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py @@ -12,19 +12,15 @@ def execute(filters=None): return columns, data def get_data(filters): - data = [] + bom_wise_data = {} + bom_data, report_data = [], [] - bom_data = [] - for d in frappe.db.sql(""" - SELECT - bom.name, bom.item, bom.item_name, bom.uom, - bomps.operation, bomps.workstation, bomps.time_in_mins - FROM `tabBOM` bom, `tabBOM Operation` bomps - WHERE - bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent - """, as_dict=1): + bom_operation_data = get_filtered_data(filters) + + for d in bom_operation_data: row = get_args() if d.name not in bom_data: + bom_wise_data[d.name] = [] bom_data.append(d.name) row.update(d) else: @@ -34,14 +30,49 @@ def get_data(filters): "time_in_mins": d.time_in_mins }) - data.append(row) + # maintain BOM wise data for grouping such as: + # {"BOM A": [{Row1}, {Row2}], "BOM B": ...} + bom_wise_data[d.name].append(row) used_as_subassembly_items = get_bom_count(bom_data) - for d in data: - d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0) + for d in bom_wise_data: + for row in bom_wise_data[d]: + row.used_as_subassembly_items = used_as_subassembly_items.get(row.name, 0) + report_data.append(row) - return data + return report_data + +def get_filtered_data(filters): + bom = frappe.qb.DocType("BOM") + bom_ops = frappe.qb.DocType("BOM Operation") + + bom_ops_query = ( + frappe.qb.from_(bom) + .join(bom_ops).on(bom.name == bom_ops.parent) + .select( + bom.name, bom.item, bom.item_name, bom.uom, + bom_ops.operation, bom_ops.workstation, bom_ops.time_in_mins + ).where( + (bom.docstatus == 1) + & (bom.is_active == 1) + ) + ) + + if filters.get("item_code"): + bom_ops_query = bom_ops_query.where(bom.item == filters.get("item_code")) + + if filters.get("bom_id"): + bom_ops_query = bom_ops_query.where(bom.name.isin(filters.get("bom_id"))) + + if filters.get("workstation"): + bom_ops_query = bom_ops_query.where( + bom_ops.workstation == filters.get("workstation") + ) + + bom_operation_data = bom_ops_query.run(as_dict=True) + + return bom_operation_data def get_bom_count(bom_data): data = frappe.get_all("BOM Item", @@ -68,13 +99,13 @@ def get_columns(filters): "options": "BOM", "fieldname": "name", "fieldtype": "Link", - "width": 140 + "width": 220 }, { - "label": _("BOM Item Code"), + "label": _("Item Code"), "options": "Item", "fieldname": "item", "fieldtype": "Link", - "width": 140 + "width": 150 }, { "label": _("Item Name"), "fieldname": "item_name", @@ -85,13 +116,13 @@ def get_columns(filters): "options": "UOM", "fieldname": "uom", "fieldtype": "Link", - "width": 140 + "width": 100 }, { "label": _("Operation"), "options": "Operation", "fieldname": "operation", "fieldtype": "Link", - "width": 120 + "width": 140 }, { "label": _("Workstation"), "options": "Workstation", @@ -101,11 +132,11 @@ def get_columns(filters): }, { "label": _("Time (In Mins)"), "fieldname": "time_in_mins", - "fieldtype": "Int", - "width": 140 + "fieldtype": "Float", + "width": 120 }, { "label": _("Sub-assembly BOM Count"), "fieldname": "used_as_subassembly_items", "fieldtype": "Int", - "width": 180 + "width": 200 }] diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js index 97e7e0a7d2..72eed5e0d7 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js @@ -17,14 +17,12 @@ frappe.query_reports["Cost of Poor Quality Report"] = { fieldname:"from_date", fieldtype: "Datetime", default: frappe.datetime.convert_to_system_tz(frappe.datetime.add_months(frappe.datetime.now_datetime(), -1)), - reqd: 1 }, { label: __("To Date"), fieldname:"to_date", fieldtype: "Datetime", default: frappe.datetime.now_datetime(), - reqd: 1, }, { label: __("Job Card"), diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index 77418235b0..88b21170e8 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -3,46 +3,65 @@ import frappe from frappe import _ -from frappe.utils import flt def execute(filters=None): - columns, data = [], [] + return get_columns(filters), get_data(filters) - columns = get_columns(filters) - data = get_data(filters) - - return columns, data def get_data(report_filters): data = [] operations = frappe.get_all("Operation", filters = {"is_corrective_operation": 1}) if operations: - operations = [d.name for d in operations] - fields = ["production_item as item_code", "item_name", "work_order", "operation", - "workstation", "total_time_in_mins", "name", "hour_rate", "serial_no", "batch_no"] + if report_filters.get('operation'): + operations = [report_filters.get('operation')] + else: + operations = [d.name for d in operations] - filters = get_filters(report_filters, operations) + job_card = frappe.qb.DocType("Job Card") - job_cards = frappe.get_all("Job Card", fields = fields, - filters = filters) + operating_cost = ((job_card.hour_rate) * (job_card.total_time_in_mins) / 60.0).as_('operating_cost') + item_code = (job_card.production_item).as_('item_code') - for row in job_cards: - row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0) - data.append(row) + query = (frappe.qb + .from_(job_card) + .select(job_card.name, job_card.work_order, item_code, job_card.item_name, + job_card.operation, job_card.serial_no, job_card.batch_no, + job_card.workstation, job_card.total_time_in_mins, job_card.hour_rate, + operating_cost) + .where( + (job_card.docstatus == 1) + & (job_card.is_corrective_job_card == 1)) + .groupby(job_card.name) + ) + query = append_filters(query, report_filters, operations, job_card) + data = query.run(as_dict=True) return data -def get_filters(report_filters, operations): - filters = {"docstatus": 1, "operation": ("in", operations), "is_corrective_job_card": 1} - for field in ["name", "work_order", "operation", "workstation", "company", "serial_no", "batch_no", "production_item"]: - if report_filters.get(field): - if field != 'serial_no': - filters[field] = report_filters.get(field) - else: - filters[field] = ('like', '% {} %'.format(report_filters.get(field))) +def append_filters(query, report_filters, operations, job_card): + """Append optional filters to query builder. """ - return filters + for field in ("name", "work_order", "operation", "workstation", + "company", "serial_no", "batch_no", "production_item"): + if report_filters.get(field): + if field == 'serial_no': + query = query.where(job_card[field].like('%{}%'.format(report_filters.get(field)))) + elif field == 'operation': + query = query.where(job_card[field].isin(operations)) + else: + query = query.where(job_card[field] == report_filters.get(field)) + + if report_filters.get('from_date') or report_filters.get('to_date'): + job_card_time_log = frappe.qb.DocType("Job Card Time Log") + + query = query.join(job_card_time_log).on(job_card.name == job_card_time_log.parent) + if report_filters.get('from_date'): + query = query.where(job_card_time_log.from_time >= report_filters.get('from_date')) + if report_filters.get('to_date'): + query = query.where(job_card_time_log.to_time <= report_filters.get('to_date')) + + return query def get_columns(filters): return [ diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py index 55b1a3f2f9..aaa231466f 100644 --- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py @@ -48,7 +48,7 @@ def get_production_plan_item_details(filters, data, order_details): "qty": row.planned_qty, "document_type": "Work Order", "document_name": work_order or "", - "bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"), + "bom_level": 0, "produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0), "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0)) }) diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py index 1de472659e..9f51ded6c7 100644 --- a/erpnext/manufacturing/report/test_reports.py +++ b/erpnext/manufacturing/report/test_reports.py @@ -18,7 +18,7 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("BOM Operations Time", {}), ("BOM Stock Calculated", {"bom": frappe.get_last_doc("BOM").name, "qty_to_make": 2}), ("BOM Stock Report", {"bom": frappe.get_last_doc("BOM").name, "qty_to_produce": 2}), - ("Cost of Poor Quality Report", {}), + ("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}), ("Downtime Analysis", {}), ( "Exponential Smoothing Forecasting", diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json index 65b4d02639..05ca2a8452 100644 --- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", "creation": "2020-03-02 17:11:37.032604", "docstatus": 0, "doctype": "Workspace", @@ -402,7 +402,7 @@ "type": "Link" } ], - "modified": "2021-11-22 17:55:03.524496", + "modified": "2022-01-13 17:40:09.474747", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", @@ -411,7 +411,7 @@ "public": 1, "restrict_to_domain": "Manufacturing", "roles": [], - "sequence_id": 17, + "sequence_id": 17.0, "shortcuts": [ { "color": "Green", diff --git a/erpnext/modules.txt b/erpnext/modules.txt index 15a24a746f..e62e2bcfab 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -15,11 +15,8 @@ Portal Maintenance Education Regional -Restaurant -Agriculture ERPNext Integrations Non Profit -Hotels Quality Management Communication Loan Management diff --git a/erpnext/non_profit/workspace/non_profit/non_profit.json b/erpnext/non_profit/workspace/non_profit/non_profit.json index ba2f919d01..fc90475fb3 100644 --- a/erpnext/non_profit/workspace/non_profit/non_profit.json +++ b/erpnext/non_profit/workspace/non_profit/non_profit.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Member\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Profit Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Membership\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter Member\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Grant Application\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Membership\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Volunteer\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Donation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tax Exemption Certification (India)\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Member\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Non Profit Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Membership\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chapter\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chapter Member\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loan Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Grant Application\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Membership\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Volunteer\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Chapter\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Donation\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tax Exemption Certification (India)\",\"col\":4}}]", "creation": "2020-03-02 17:23:47.811421", "docstatus": 0, "doctype": "Workspace", @@ -231,7 +231,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.146207", + "modified": "2022-01-13 17:40:50.220877", "modified_by": "Administrator", "module": "Non Profit", "name": "Non Profit", @@ -240,7 +240,7 @@ "public": 1, "restrict_to_domain": "Non Profit", "roles": [], - "sequence_id": 18, + "sequence_id": 18.0, "shortcuts": [ { "label": "Member", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index deeeeb7a1c..5a8f8ef6f8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -1,4 +1,6 @@ +[pre_model_sync] erpnext.patches.v12_0.update_is_cancelled_field +erpnext.patches.v13_0.add_bin_unique_constraint erpnext.patches.v11_0.rename_production_order_to_work_order erpnext.patches.v11_0.refactor_naming_series erpnext.patches.v11_0.refactor_autoname_naming @@ -165,7 +167,6 @@ erpnext.patches.v12_0.set_updated_purpose_in_pick_list erpnext.patches.v12_0.set_default_payroll_based_on erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign -erpnext.patches.v13_0.validate_options_for_data_field erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123 erpnext.patches.v12_0.fix_quotation_expired_status erpnext.patches.v12_0.rename_pos_closing_doctype @@ -203,6 +204,7 @@ execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation") erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 erpnext.patches.v12_0.create_itc_reversal_custom_fields +execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings") erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020 erpnext.patches.v12_0.add_taxjar_integration_field @@ -223,11 +225,11 @@ erpnext.patches.v13_0.set_youtube_video_id erpnext.patches.v13_0.set_app_name erpnext.patches.v13_0.print_uom_after_quantity_patch erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account +erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02 erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.update_reason_for_resignation_in_employee execute:frappe.delete_doc("Report", "Quoted Item Comparison") erpnext.patches.v13_0.update_member_email_address -erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log erpnext.patches.v13_0.add_po_to_global_search @@ -243,6 +245,8 @@ erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes erpnext.patches.v13_0.update_vehicle_no_reqd_condition +erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17 +erpnext.patches.v12_0.add_einvoice_summary_report_permissions erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae @@ -253,9 +257,10 @@ erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing -erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 +erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 #17-01-2022 erpnext.patches.v13_0.update_shipment_status erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting +erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed @@ -267,12 +272,11 @@ erpnext.patches.v13_0.set_training_event_attendance erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold erpnext.patches.v13_0.update_response_by_variance -erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.update_job_card_details -erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_amt_in_work_order_required_items +erpnext.patches.v12_0.show_einvoice_irn_cancelled_field erpnext.patches.v13_0.delete_orphaned_tables erpnext.patches.v13_0.update_export_type_for_gst #2021-08-16 erpnext.patches.v13_0.update_tds_check_field #3 @@ -280,16 +284,14 @@ erpnext.patches.v13_0.add_custom_field_for_south_africa #2 erpnext.patches.v13_0.update_recipient_email_digest erpnext.patches.v13_0.shopify_deprecation_warning erpnext.patches.v13_0.remove_bad_selling_defaults +erpnext.patches.v13_0.trim_whitespace_from_serial_nos # 16-01-2022 erpnext.patches.v13_0.migrate_stripe_api erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries -erpnext.patches.v13_0.einvoicing_deprecation_warning execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings") execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category") -erpnext.patches.v14_0.delete_einvoicing_doctypes erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021 erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021 -erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v13_0.fix_invoice_statuses erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category @@ -305,19 +307,38 @@ erpnext.patches.v13_0.add_default_interview_notification_templates erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts erpnext.patches.v13_0.update_job_card_status +erpnext.patches.v13_0.enable_uoms erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.healthcare_deprecation_warning erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v14_0.delete_healthcare_doctypes erpnext.patches.v13_0.update_category_in_ltds_certificate erpnext.patches.v13_0.create_pan_field_for_india #2 -erpnext.patches.v14_0.delete_hub_doctypes -erpnext.patches.v13_0.create_ksa_vat_custom_fields -erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents +erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit +erpnext.patches.v13_0.create_ksa_vat_custom_fields # 07-01-2022 erpnext.patches.v14_0.migrate_crm_settings erpnext.patches.v13_0.rename_ksa_qr_field +erpnext.patches.v13_0.wipe_serial_no_field_for_0_qty erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021 -erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template erpnext.patches.v13_0.update_tax_category_for_rcm execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings') -erpnext.patches.v14_0.set_payroll_cost_centers \ No newline at end of file +erpnext.patches.v14_0.set_payroll_cost_centers +erpnext.patches.v13_0.agriculture_deprecation_warning +erpnext.patches.v13_0.hospitality_deprecation_warning +erpnext.patches.v13_0.update_exchange_rate_settings +erpnext.patches.v13_0.update_asset_quantity_field +erpnext.patches.v13_0.delete_bank_reconciliation_detail + +[post_model_sync] +erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents +erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template +erpnext.patches.v14_0.delete_shopify_doctypes +erpnext.patches.v14_0.delete_hub_doctypes +erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022 +erpnext.patches.v14_0.delete_agriculture_doctypes +erpnext.patches.v14_0.rearrange_company_fields +erpnext.patches.v14_0.update_leave_notification_template +erpnext.patches.v14_0.restore_einvoice_fields +erpnext.patches.v13_0.update_sane_transfer_against +erpnext.patches.v12_0.add_company_link_to_einvoice_settings +erpnext.patches.v14_0.migrate_cost_center_allocations diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py new file mode 100644 index 0000000000..e498b673fb --- /dev/null +++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals + +import frappe + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company or not frappe.db.count('E Invoice User'): + return + + frappe.reload_doc("regional", "doctype", "e_invoice_user") + for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']): + company_name = frappe.db.sql(""" + select dl.link_name from `tabAddress` a, `tabDynamic Link` dl + where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company' + """, (creds.get('gstin'))) + if company_name and len(company_name) > 0: + frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0]) diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py new file mode 100644 index 0000000000..aeff9ca841 --- /dev/null +++ b/erpnext/patches/v12_0/add_einvoice_status_field.py @@ -0,0 +1,72 @@ +from __future__ import unicode_literals + +import json + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + # move hidden einvoice fields to a different section + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', + print_hide=1, hidden=1), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', + no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + no_copy=1, print_hide=1), + + dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image', + options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON', + hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1) + ] + } + create_custom_fields(custom_fields, update=True) + + if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'): + frappe.db.sql(''' + UPDATE `tabSales Invoice` SET einvoice_status = 'Pending' + WHERE + posting_date >= '2021-04-01' + AND ifnull(irn, '') = '' + AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '') + AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export') + ''') + + # set appropriate statuses + frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated' + WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''') + + frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled' + WHERE ifnull(irn_cancelled, 0) = 1''') + + # set correct acknowledgement in e-invoices + einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice']) + + if einvoices: + for inv in einvoices: + signed_einvoice = inv.get('signed_einvoice') + if signed_einvoice: + signed_einvoice = json.loads(signed_einvoice) + frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False) + frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False) diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py new file mode 100644 index 0000000000..e837786138 --- /dev/null +++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals + +import frappe + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + if frappe.db.exists('Report', 'E-Invoice Summary') and \ + not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')): + frappe.get_doc(dict( + doctype='Custom Role', + report='E-Invoice Summary', + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager') + ] + )).insert() diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py new file mode 100644 index 0000000000..247140d21d --- /dev/null +++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill') + ] + } + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py new file mode 100644 index 0000000000..c17666add1 --- /dev/null +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -0,0 +1,59 @@ +from __future__ import unicode_literals + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +from erpnext.regional.india.setup import add_permissions, add_print_formats + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + frappe.reload_doc("custom", "doctype", "custom_field") + frappe.reload_doc("regional", "doctype", "e_invoice_settings") + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, + depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1) + ] + } + create_custom_fields(custom_fields, update=True) + add_permissions() + add_print_formats() + + einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)' + t = { + 'mode_of_transport': [{'default': None}], + 'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}], + 'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}], + 'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}], + 'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}], + 'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}], + 'ewaybill': [ + {'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'}, + {'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'} + ] + } + + for field, conditions in t.items(): + for c in conditions: + [(prop, value)] = c.items() + frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value) diff --git a/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py new file mode 100644 index 0000000000..3f90a03020 --- /dev/null +++ b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py @@ -0,0 +1,14 @@ +from __future__ import unicode_literals + +import frappe + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'}) + if irn_cancelled_field: + frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn') + frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0) diff --git a/erpnext/patches/v12_0/update_is_cancelled_field.py b/erpnext/patches/v12_0/update_is_cancelled_field.py index df7875079b..0401034874 100644 --- a/erpnext/patches/v12_0/update_is_cancelled_field.py +++ b/erpnext/patches/v12_0/update_is_cancelled_field.py @@ -2,14 +2,28 @@ import frappe def execute(): - try: - frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')") - frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')") + #handle type casting for is_cancelled field + module_doctypes = ( + ('stock', 'Stock Ledger Entry'), + ('stock', 'Serial No'), + ('accounts', 'GL Entry') + ) - frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 1 where is_cancelled = 'Yes'") - frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 1 where is_cancelled = 'Yes'") + for module, doctype in module_doctypes: + if (not frappe.db.has_column(doctype, "is_cancelled") + or frappe.db.get_column_type(doctype, "is_cancelled").lower() == "int(1)" + ): + continue - frappe.reload_doc("stock", "doctype", "stock_ledger_entry") - frappe.reload_doc("stock", "doctype", "serial_no") - except Exception: - pass + frappe.db.sql(""" + UPDATE `tab{doctype}` + SET is_cancelled = 0 + where is_cancelled in ('', NULL, 'No')""" + .format(doctype=doctype)) + frappe.db.sql(""" + UPDATE `tab{doctype}` + SET is_cancelled = 1 + where is_cancelled = 'Yes'""" + .format(doctype=doctype)) + + frappe.reload_doc(module, "doctype", frappe.scrub(doctype)) diff --git a/erpnext/patches/v13_0/add_bin_unique_constraint.py b/erpnext/patches/v13_0/add_bin_unique_constraint.py new file mode 100644 index 0000000000..57fbaae9d8 --- /dev/null +++ b/erpnext/patches/v13_0/add_bin_unique_constraint.py @@ -0,0 +1,63 @@ +import frappe + +from erpnext.stock.stock_balance import ( + get_balance_qty_from_sle, + get_indented_qty, + get_ordered_qty, + get_planned_qty, + get_reserved_qty, +) +from erpnext.stock.utils import get_bin + + +def execute(): + delete_broken_bins() + delete_and_patch_duplicate_bins() + +def delete_broken_bins(): + # delete useless bins + frappe.db.sql("delete from `tabBin` where item_code is null or warehouse is null") + +def delete_and_patch_duplicate_bins(): + + duplicate_bins = frappe.db.sql(""" + SELECT + item_code, warehouse, count(*) as bin_count + FROM + tabBin + GROUP BY + item_code, warehouse + HAVING + bin_count > 1 + """, as_dict=1) + + for duplicate_bin in duplicate_bins: + item_code = duplicate_bin.item_code + warehouse = duplicate_bin.warehouse + existing_bins = frappe.get_list("Bin", + filters={ + "item_code": item_code, + "warehouse": warehouse + }, + fields=["name"], + order_by="creation",) + + # keep last one + existing_bins.pop() + + for broken_bin in existing_bins: + frappe.delete_doc("Bin", broken_bin.name) + + qty_dict = { + "reserved_qty": get_reserved_qty(item_code, warehouse), + "indented_qty": get_indented_qty(item_code, warehouse), + "ordered_qty": get_ordered_qty(item_code, warehouse), + "planned_qty": get_planned_qty(item_code, warehouse), + "actual_qty": get_balance_qty_from_sle(item_code, warehouse) + } + + bin = get_bin(item_code, warehouse) + bin.update(qty_dict) + bin.update_reserved_qty_for_production() + bin.update_reserved_qty_for_sub_contracting() + bin.db_update() diff --git a/erpnext/patches/v13_0/agriculture_deprecation_warning.py b/erpnext/patches/v13_0/agriculture_deprecation_warning.py new file mode 100644 index 0000000000..512444ef65 --- /dev/null +++ b/erpnext/patches/v13_0/agriculture_deprecation_warning.py @@ -0,0 +1,10 @@ +import click + + +def execute(): + + click.secho( + "Agriculture Domain is moved to a separate app and will be removed from ERPNext in version-14.\n" + "Please install the app to continue using the Agriculture domain: https://github.com/frappe/agriculture", + fg="yellow", + ) diff --git a/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py b/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py new file mode 100644 index 0000000000..75953b0e30 --- /dev/null +++ b/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py @@ -0,0 +1,13 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + + +import frappe + + +def execute(): + + if frappe.db.exists('DocType', 'Bank Reconciliation Detail') and \ + frappe.db.exists('DocType', 'Bank Clearance Detail'): + + frappe.delete_doc("DocType", 'Bank Reconciliation Detail', force=1) diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py index c597fe8645..e6eba0a608 100644 --- a/erpnext/patches/v13_0/delete_old_sales_reports.py +++ b/erpnext/patches/v13_0/delete_old_sales_reports.py @@ -12,6 +12,7 @@ def execute(): for report in reports_to_delete: if frappe.db.exists("Report", report): + delete_links_from_desktop_icons(report) delete_auto_email_reports(report) check_and_delete_linked_reports(report) @@ -22,3 +23,9 @@ def delete_auto_email_reports(report): auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"]) for auto_email_report in auto_email_reports: frappe.delete_doc("Auto Email Report", auto_email_report[0]) + +def delete_links_from_desktop_icons(report): + """ Check for one or multiple Desktop Icons and delete """ + desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"]) + for desktop_icon in desktop_icons: + frappe.delete_doc("Desktop Icon", desktop_icon[0]) \ No newline at end of file diff --git a/erpnext/patches/v13_0/einvoicing_deprecation_warning.py b/erpnext/patches/v13_0/einvoicing_deprecation_warning.py deleted file mode 100644 index e123a55f5a..0000000000 --- a/erpnext/patches/v13_0/einvoicing_deprecation_warning.py +++ /dev/null @@ -1,9 +0,0 @@ -import click - - -def execute(): - click.secho( - "Indian E-Invoicing integration is moved to a separate app and will be removed from ERPNext in version-14.\n" - "Please install the app to continue using the integration: https://github.com/frappe/erpnext_gst_compliance", - fg="yellow", - ) diff --git a/erpnext/patches/v13_0/enable_uoms.py b/erpnext/patches/v13_0/enable_uoms.py new file mode 100644 index 0000000000..4d3f637630 --- /dev/null +++ b/erpnext/patches/v13_0/enable_uoms.py @@ -0,0 +1,13 @@ +import frappe + + +def execute(): + frappe.reload_doc('setup', 'doctype', 'uom') + + uom = frappe.qb.DocType("UOM") + + (frappe.qb + .update(uom) + .set(uom.enabled, 1) + .where(uom.creation >= "2021-10-18") # date when this field was released + ).run() diff --git a/erpnext/patches/v13_0/hospitality_deprecation_warning.py b/erpnext/patches/v13_0/hospitality_deprecation_warning.py new file mode 100644 index 0000000000..2708b2ccd3 --- /dev/null +++ b/erpnext/patches/v13_0/hospitality_deprecation_warning.py @@ -0,0 +1,10 @@ +import click + + +def execute(): + + click.secho( + "Hospitality domain is moved to a separate app and will be removed from ERPNext in version-14.\n" + "When upgrading to ERPNext version-14, please install the app to continue using the Hospitality domain: https://github.com/frappe/hospitality", + fg="yellow", + ) diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py index a7bdf93332..ff241a3fd9 100644 --- a/erpnext/patches/v13_0/make_non_standard_user_type.py +++ b/erpnext/patches/v13_0/make_non_standard_user_type.py @@ -10,8 +10,15 @@ from erpnext.setup.install import add_non_standard_user_types def execute(): doctype_dict = { 'projects': ['Timesheet'], - 'payroll': ['Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission'], - 'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request'] + 'payroll': [ + 'Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission', + 'Employee Benefit Application', 'Employee Benefit Claim' + ], + 'hr': [ + 'Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request', + 'Holiday List', 'Employee Advance', 'Training Program', 'Training Feedback', + 'Shift Request', 'Employee Grievance', 'Employee Referral', 'Travel Request' + ] } for module, doctypes in doctype_dict.items(): diff --git a/erpnext/patches/v13_0/remove_bad_selling_defaults.py b/erpnext/patches/v13_0/remove_bad_selling_defaults.py index 5487a6c60c..02625396dd 100644 --- a/erpnext/patches/v13_0/remove_bad_selling_defaults.py +++ b/erpnext/patches/v13_0/remove_bad_selling_defaults.py @@ -3,6 +3,7 @@ from frappe import _ def execute(): + frappe.reload_doctype('Selling Settings') selling_settings = frappe.get_single("Selling Settings") if selling_settings.customer_group in (_("All Customer Groups"), "All Customer Groups"): diff --git a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py index 7a2a253967..2d35ea3458 100644 --- a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py +++ b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py @@ -5,6 +5,9 @@ from erpnext.regional.india.setup import make_custom_fields def execute(): if frappe.get_all('Company', filters = {'country': 'India'}): + frappe.reload_doc('accounts', 'doctype', 'POS Invoice') + frappe.reload_doc('accounts', 'doctype', 'POS Invoice Item') + make_custom_fields() if not frappe.db.exists('Party Type', 'Donor'): diff --git a/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py new file mode 100644 index 0000000000..4ec22e9d0e --- /dev/null +++ b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py @@ -0,0 +1,67 @@ +import frappe + +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + +def execute(): + broken_sles = frappe.db.sql(""" + select name, serial_no + from `tabStock Ledger Entry` + where + is_cancelled = 0 + and ( serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s + or serial_no = %s ) + """, + ( + " %", # leading whitespace + "% ", # trailing whitespace + "%\n %", # leading whitespace on newline + "% \n%", # trailing whitespace on newline + "\n", # just new line + ), + as_dict=True, + ) + + frappe.db.MAX_WRITES_PER_TRANSACTION += len(broken_sles) + + if not broken_sles: + return + + broken_serial_nos = set() + + # patch SLEs + for sle in broken_sles: + serial_no_list = get_serial_nos(sle.serial_no) + correct_sr_no = "\n".join(serial_no_list) + + if correct_sr_no == sle.serial_no: + continue + + frappe.db.set_value("Stock Ledger Entry", sle.name, "serial_no", correct_sr_no, update_modified=False) + broken_serial_nos.update(serial_no_list) + + if not broken_serial_nos: + return + + # Patch serial No documents if they don't have purchase info + # Purchase info is used for fetching incoming rate + broken_sr_no_records = frappe.get_list("Serial No", + filters={ + "status":"Active", + "name": ("in", broken_serial_nos), + "purchase_document_type": ("is", "not set") + }, + pluck="name", + ) + + frappe.db.MAX_WRITES_PER_TRANSACTION += len(broken_sr_no_records) + + patch_savepoint = "serial_no_patch" + for serial_no in broken_sr_no_records: + try: + frappe.db.savepoint(patch_savepoint) + sn = frappe.get_doc("Serial No", serial_no) + sn.update_serial_no_reference() + sn.db_update() + except Exception: + frappe.db.rollback(save_point=patch_savepoint) diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py index 55fd465b20..60466eb86a 100644 --- a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py +++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py @@ -37,4 +37,4 @@ def execute(): jc.production_item = wo.production_item, jc.item_name = wo.item_name WHERE jc.work_order = wo.name and IFNULL(jc.production_item, "") = "" - """) + """) \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_asset_quantity_field.py b/erpnext/patches/v13_0/update_asset_quantity_field.py new file mode 100644 index 0000000000..47884d1968 --- /dev/null +++ b/erpnext/patches/v13_0/update_asset_quantity_field.py @@ -0,0 +1,8 @@ +import frappe + + +def execute(): + if frappe.db.count('Asset'): + frappe.reload_doc("assets", "doctype", "Asset") + asset = frappe.qb.DocType('Asset') + frappe.qb.update(asset).set(asset.asset_quantity, 1).run() \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_exchange_rate_settings.py b/erpnext/patches/v13_0/update_exchange_rate_settings.py new file mode 100644 index 0000000000..b7ec232bba --- /dev/null +++ b/erpnext/patches/v13_0/update_exchange_rate_settings.py @@ -0,0 +1,10 @@ +import frappe + +from erpnext.setup.install import setup_currency_exchange + + +def execute(): + frappe.reload_doc("accounts", "doctype", "currency_exchange_settings") + frappe.reload_doc("accounts", "doctype", "currency_exchange_settings_result") + frappe.reload_doc("accounts", "doctype", "currency_exchange_settings_details") + setup_currency_exchange() \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_level_in_bom.py b/erpnext/patches/v13_0/update_level_in_bom.py deleted file mode 100644 index 499412ee27..0000000000 --- a/erpnext/patches/v13_0/update_level_in_bom.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2020, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - - -import frappe - - -def execute(): - for document in ["bom", "bom_item", "bom_explosion_item"]: - frappe.reload_doc('manufacturing', 'doctype', document) - - frappe.db.sql(" update `tabBOM` set bom_level = 0 where docstatus = 1") - - bom_list = frappe.db.sql_list("""select name from `tabBOM` bom - where docstatus=1 and is_active=1 and not exists(select bom_no from `tabBOM Item` - where parent=bom.name and ifnull(bom_no, '')!='')""") - - count = 0 - while(count < len(bom_list)): - for parent_bom in get_parent_boms(bom_list[count]): - bom_doc = frappe.get_cached_doc("BOM", parent_bom) - bom_doc.set_bom_level(update=True) - bom_list.append(parent_bom) - count += 1 - -def get_parent_boms(bom_no): - return frappe.db.sql_list(""" - select distinct bom_item.parent from `tabBOM Item` bom_item - where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM' - and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1) - """, bom_no) diff --git a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py new file mode 100644 index 0000000000..7a8c1c6135 --- /dev/null +++ b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py @@ -0,0 +1,25 @@ + +import frappe + + +def execute(): + frappe.reload_doctype('Maintenance Visit') + frappe.reload_doctype('Maintenance Visit Purpose') + + # Updates the Maintenance Schedule link to fetch serial nos + from frappe.query_builder.functions import Coalesce + mvp = frappe.qb.DocType('Maintenance Visit Purpose') + mv = frappe.qb.DocType('Maintenance Visit') + + frappe.qb.update( + mv + ).join( + mvp + ).on(mvp.parent == mv.name).set( + mv.maintenance_schedule, + Coalesce(mvp.prevdoc_docname, '') + ).where( + (mv.maintenance_type == "Scheduled") + & (mvp.prevdoc_docname.notnull()) + & (mv.docstatus < 2) + ).run(as_dict=1) diff --git a/erpnext/patches/v13_0/update_sane_transfer_against.py b/erpnext/patches/v13_0/update_sane_transfer_against.py new file mode 100644 index 0000000000..a163d38584 --- /dev/null +++ b/erpnext/patches/v13_0/update_sane_transfer_against.py @@ -0,0 +1,11 @@ +import frappe + + +def execute(): + bom = frappe.qb.DocType("BOM") + + (frappe.qb + .update(bom) + .set(bom.transfer_material_against, "Work Order") + .where(bom.with_operations == 0) + ).run() diff --git a/erpnext/patches/v13_0/validate_options_for_data_field.py b/erpnext/patches/v13_0/validate_options_for_data_field.py deleted file mode 100644 index ad777b8586..0000000000 --- a/erpnext/patches/v13_0/validate_options_for_data_field.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2021, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - - -import frappe -from frappe.model import data_field_options - - -def execute(): - - for field in frappe.get_all('Custom Field', - fields = ['name'], - filters = { - 'fieldtype': 'Data', - 'options': ['!=', None] - }): - - if field not in data_field_options: - frappe.db.sql(""" - UPDATE - `tabCustom Field` - SET - options=NULL - WHERE - name=%s - """, (field)) diff --git a/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py b/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py new file mode 100644 index 0000000000..e43a8bad8e --- /dev/null +++ b/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py @@ -0,0 +1,18 @@ +import frappe + + +def execute(): + + doctype = "Stock Reconciliation Item" + + if not frappe.db.has_column(doctype, "current_serial_no"): + # nothing to fix if column doesn't exist + return + + sr_item = frappe.qb.DocType(doctype) + + (frappe.qb + .update(sr_item) + .set(sr_item.current_serial_no, None) + .where(sr_item.current_qty == 0) + ).run() diff --git a/erpnext/patches/v14_0/__init__.py b/erpnext/patches/v14_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py index 120182a80e..2a8b6ef746 100644 --- a/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py +++ b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py @@ -5,9 +5,6 @@ from frappe import _ def execute(): - frappe.reload_doc("email", "doctype", "email_template") - frappe.reload_doc("hr", "doctype", "hr_settings") - template = frappe.db.exists("Email Template", _("Exit Questionnaire Notification")) if not template: base_path = frappe.get_app_path("erpnext", "hr", "doctype") diff --git a/erpnext/patches/v14_0/delete_agriculture_doctypes.py b/erpnext/patches/v14_0/delete_agriculture_doctypes.py new file mode 100644 index 0000000000..d7fe832f9a --- /dev/null +++ b/erpnext/patches/v14_0/delete_agriculture_doctypes.py @@ -0,0 +1,19 @@ +import frappe + + +def execute(): + frappe.delete_doc("Module Def", "Agriculture", ignore_missing=True, force=True) + + frappe.delete_doc("Workspace", "Agriculture", ignore_missing=True, force=True) + + reports = frappe.get_all("Report", {"module": "agriculture", "is_standard": "Yes"}, pluck='name') + for report in reports: + frappe.delete_doc("Report", report, ignore_missing=True, force=True) + + dashboards = frappe.get_all("Dashboard", {"module": "agriculture", "is_standard": 1}, pluck='name') + for dashboard in dashboards: + frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True) + + doctypes = frappe.get_all("DocType", {"module": "agriculture", "custom": 0}, pluck='name') + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, ignore_missing=True) diff --git a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py b/erpnext/patches/v14_0/delete_einvoicing_doctypes.py deleted file mode 100644 index a3a8149be3..0000000000 --- a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py +++ /dev/null @@ -1,10 +0,0 @@ -import frappe - - -def execute(): - frappe.delete_doc('DocType', 'E Invoice Settings', ignore_missing=True) - frappe.delete_doc('DocType', 'E Invoice User', ignore_missing=True) - frappe.delete_doc('Report', 'E-Invoice Summary', ignore_missing=True) - frappe.delete_doc('Print Format', 'GST E-Invoice', ignore_missing=True) - frappe.delete_doc('Custom Field', 'Sales Invoice-eway_bill_cancelled', ignore_missing=True) - frappe.delete_doc('Custom Field', 'Sales Invoice-irn_cancelled', ignore_missing=True) diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py index 28fc01beab..3a4f8f537d 100644 --- a/erpnext/patches/v14_0/delete_healthcare_doctypes.py +++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py @@ -47,3 +47,18 @@ def execute(): frappe.delete_doc("DocType", doctype, ignore_missing=True) frappe.delete_doc("Module Def", "Healthcare", ignore_missing=True, force=True) + + custom_fields = { + 'Sales Invoice': ['patient', 'patient_name', 'ref_practitioner'], + 'Sales Invoice Item': ['reference_dt', 'reference_dn'], + 'Stock Entry': ['inpatient_medication_entry'], + 'Stock Entry Detail': ['patient', 'inpatient_medication_entry_child'], + } + for doc, fields in custom_fields.items(): + filters = { + 'dt': doc, + 'fieldname': ['in', fields] + } + records = frappe.get_all('Custom Field', filters=filters, pluck='name') + for record in records: + frappe.delete_doc('Custom Field', record, ignore_missing=True, force=True) diff --git a/erpnext/patches/v14_0/delete_hospitality_doctypes.py b/erpnext/patches/v14_0/delete_hospitality_doctypes.py new file mode 100644 index 0000000000..d0216f80d3 --- /dev/null +++ b/erpnext/patches/v14_0/delete_hospitality_doctypes.py @@ -0,0 +1,32 @@ +import frappe + + +def execute(): + modules = ['Hotels', 'Restaurant'] + + for module in modules: + frappe.delete_doc("Module Def", module, ignore_missing=True, force=True) + + frappe.delete_doc("Workspace", module, ignore_missing=True, force=True) + + reports = frappe.get_all("Report", {"module": module, "is_standard": "Yes"}, pluck='name') + for report in reports: + frappe.delete_doc("Report", report, ignore_missing=True, force=True) + + dashboards = frappe.get_all("Dashboard", {"module": module, "is_standard": 1}, pluck='name') + for dashboard in dashboards: + frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True) + + doctypes = frappe.get_all("DocType", {"module": module, "custom": 0}, pluck='name') + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, ignore_missing=True) + + custom_fields = [ + {"dt": "Sales Invoice", "fieldname": "restaurant"}, + {"dt": "Sales Invoice", "fieldname": "restaurant_table"}, + {"dt": "Price List", "fieldname": "restaurant_menu"}, + ] + + for field in custom_fields: + custom_field = frappe.db.get_value("Custom Field", field) + frappe.delete_doc("Custom Field", custom_field, ignore_missing=True) diff --git a/erpnext/patches/v14_0/migrate_cost_center_allocations.py b/erpnext/patches/v14_0/migrate_cost_center_allocations.py new file mode 100644 index 0000000000..3d217d89e2 --- /dev/null +++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py @@ -0,0 +1,48 @@ +import frappe +from frappe.utils import today + + +def execute(): + for dt in ("cost_center_allocation", "cost_center_allocation_percentage"): + frappe.reload_doc('accounts', 'doctype', dt) + + cc_allocations = get_existing_cost_center_allocations() + if cc_allocations: + create_new_cost_center_allocation_records(cc_allocations) + + frappe.delete_doc('DocType', 'Distributed Cost Center', ignore_missing=True) + +def create_new_cost_center_allocation_records(cc_allocations): + for main_cc, allocations in cc_allocations.items(): + cca = frappe.new_doc("Cost Center Allocation") + cca.main_cost_center = main_cc + cca.valid_from = today() + + for child_cc, percentage in allocations.items(): + cca.append("allocation_percentages", ({ + "cost_center": child_cc, + "percentage": percentage + })) + cca.save() + cca.submit() + +def get_existing_cost_center_allocations(): + if not frappe.get_meta("Cost Center").has_field("enable_distributed_cost_center"): + return + + par = frappe.qb.DocType("Cost Center") + child = frappe.qb.DocType("Distributed Cost Center") + + records = ( + frappe.qb.from_(par) + .inner_join(child).on(par.name == child.parent) + .select(par.name, child.cost_center, child.percentage_allocation) + .where(par.enable_distributed_cost_center == 1) + ).run(as_dict=True) + + cc_allocations = frappe._dict() + for d in records: + cc_allocations.setdefault(d.name, frappe._dict())\ + .setdefault(d.cost_center, d.percentage_allocation) + + return cc_allocations \ No newline at end of file diff --git a/erpnext/patches/v14_0/migrate_crm_settings.py b/erpnext/patches/v14_0/migrate_crm_settings.py index 30d3ea0cb1..0c7785367c 100644 --- a/erpnext/patches/v14_0/migrate_crm_settings.py +++ b/erpnext/patches/v14_0/migrate_crm_settings.py @@ -9,8 +9,9 @@ def execute(): ], as_dict=True) frappe.reload_doc('crm', 'doctype', 'crm_settings') - frappe.db.set_value('CRM Settings', 'CRM Settings', { - 'campaign_naming_by': settings.campaign_naming_by, - 'close_opportunity_after_days': settings.close_opportunity_after_days, - 'default_valid_till': settings.default_valid_till - }) + if settings: + frappe.db.set_value('CRM Settings', 'CRM Settings', { + 'campaign_naming_by': settings.campaign_naming_by, + 'close_opportunity_after_days': settings.close_opportunity_after_days, + 'default_valid_till': settings.default_valid_till + }) diff --git a/erpnext/patches/v14_0/rearrange_company_fields.py b/erpnext/patches/v14_0/rearrange_company_fields.py new file mode 100644 index 0000000000..fd7eb7f750 --- /dev/null +++ b/erpnext/patches/v14_0/rearrange_company_fields.py @@ -0,0 +1,28 @@ +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + custom_fields = { + 'Company': [ + dict(fieldname='hra_section', label='HRA Settings', + fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1), + dict(fieldname='basic_component', label='Basic Component', + fieldtype='Link', options='Salary Component', insert_after='hra_section'), + dict(fieldname='hra_component', label='HRA Component', + fieldtype='Link', options='Salary Component', insert_after='basic_component'), + dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'), + dict(fieldname='arrear_component', label='Arrear Component', + fieldtype='Link', options='Salary Component', insert_after='hra_column_break'), + dict(fieldname='non_profit_section', label='Non Profit Settings', + fieldtype='Section Break', insert_after='arrear_component', collapsible=1), + dict(fieldname='company_80g_number', label='80G Number', + fieldtype='Data', insert_after='non_profit_section'), + dict(fieldname='with_effect_from', label='80G With Effect From', + fieldtype='Date', insert_after='company_80g_number'), + dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'), + dict(fieldname='pan_details', label='PAN Number', + fieldtype='Data', insert_after='non_profit_column_break') + ] + } + + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/patches/v14_0/restore_einvoice_fields.py b/erpnext/patches/v14_0/restore_einvoice_fields.py new file mode 100644 index 0000000000..c4431fb9db --- /dev/null +++ b/erpnext/patches/v14_0/restore_einvoice_fields.py @@ -0,0 +1,24 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +from erpnext.regional.india.setup import add_permissions, add_print_formats + + +def execute(): + # restores back the 2 custom fields that was deleted while removing e-invoicing from v14 + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + ] + } + create_custom_fields(custom_fields, update=True) + add_permissions() + add_print_formats() diff --git a/erpnext/patches/v14_0/update_leave_notification_template.py b/erpnext/patches/v14_0/update_leave_notification_template.py new file mode 100644 index 0000000000..e744054a2f --- /dev/null +++ b/erpnext/patches/v14_0/update_leave_notification_template.py @@ -0,0 +1,17 @@ +import os + +import frappe +from frappe import _ + + +def execute(): + base_path = frappe.get_app_path("erpnext", "hr", "doctype") + response = frappe.read_file(os.path.join(base_path, "leave_application/leave_application_email_template.html")) + + template = frappe.db.exists("Email Template", _("Leave Approval Notification")) + if template: + frappe.db.set_value("Email Template", template, "response", response) + + template = frappe.db.exists("Email Template", _("Leave Status Notification")) + if template: + frappe.db.set_value("Email Template", template, "response", response) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json index d9efe458dc..9c897a7c69 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.json +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json @@ -204,10 +204,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-05-26 11:10:00.812698", + "modified": "2022-01-19 12:56:51.765353", "modified_by": "Administrator", "module": "Payroll", "name": "Additional Salary", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -239,8 +240,10 @@ "write": 1 } ], + "search_fields": "employee_name", "sort_field": "modified", "sort_order": "DESC", - "title_field": "employee", + "states": [], + "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json index 83326975b0..2e4b64e9da 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json @@ -147,10 +147,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:35:08.940087", + "modified": "2022-01-19 12:58:31.664468", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Benefit Application", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -212,8 +213,10 @@ } ], "quick_entry": 1, + "search_fields": "employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json index b3bac01818..5deb0a5eca 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json @@ -144,10 +144,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:37:21.024625", + "modified": "2022-01-19 12:59:15.699118", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Benefit Claim", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -208,8 +209,10 @@ "write": 1 } ], + "search_fields": "employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json index 0d10b2c19a..64fb8c5c98 100644 --- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json +++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json @@ -94,10 +94,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:38:20.332316", + "modified": "2022-01-19 12:52:19.850710", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Incentive", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -136,8 +137,10 @@ "write": 1 } ], + "search_fields": "employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/employee_other_income/employee_other_income.json b/erpnext/payroll/doctype/employee_other_income/employee_other_income.json index 14f63e4fdd..04ce9f79a1 100644 --- a/erpnext/payroll/doctype/employee_other_income/employee_other_income.json +++ b/erpnext/payroll/doctype/employee_other_income/employee_other_income.json @@ -76,10 +76,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-06-22 22:55:17.604688", + "modified": "2022-01-19 12:58:43.255900", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Other Income", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -129,7 +130,10 @@ } ], "quick_entry": 1, + "search_fields": "employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json index b247d266ae..5ef373e887 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json @@ -119,10 +119,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:39:59.237361", + "modified": "2022-01-19 12:58:54.707871", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Tax Exemption Declaration", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -186,7 +187,10 @@ "write": 1 } ], + "search_fields": "employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json index 77b107ef4a..bb90051e5d 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json @@ -142,10 +142,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:41:13.723339", + "modified": "2022-01-19 12:58:24.244546", "modified_by": "Administrator", "module": "Payroll", "name": "Employee Tax Exemption Proof Submission", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -209,7 +210,10 @@ "write": 1 } ], + "search_fields": "employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json index 48a9ce4759..197089567d 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.json +++ b/erpnext/payroll/doctype/gratuity/gratuity.json @@ -167,10 +167,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-07-02 15:05:57.396398", + "modified": "2022-01-19 12:54:37.306145", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -198,6 +199,9 @@ "write": 1 } ], + "search_fields": "employee_name", "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [], + "title_field": "employee_name" } \ No newline at end of file diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 5bb32cf909..db88c0643c 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -61,6 +61,8 @@ class PayrollEntry(Document): def on_cancel(self): frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip` where payroll_entry=%s """, (self.name))) + self.db_set("salary_slips_created", 0) + self.db_set("salary_slips_submitted", 0) def get_emp_list(self): """ @@ -398,23 +400,24 @@ class PayrollEntry(Document): currencies = [] multi_currency = 0 company_currency = erpnext.get_company_currency(self.company) + accounting_dimensions = get_accounting_dimensions() or [] exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies) - accounts.append({ + accounts.append(self.update_accounting_dimensions({ "account": self.payment_account, "bank_account": self.bank_account, "credit_in_account_currency": flt(amount, precision), "exchange_rate": flt(exchange_rate), - }) + }, accounting_dimensions)) exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies) - accounts.append({ + accounts.append(self.update_accounting_dimensions({ "account": payroll_payable_account, "debit_in_account_currency": flt(amount, precision), "exchange_rate": flt(exchange_rate), "reference_type": self.doctype, "reference_name": self.name - }) + }, accounting_dimensions)) if len(currencies) > 1: multi_currency = 1 diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json index 7ea6210c7a..f8d8bb46de 100644 --- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json +++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json @@ -105,10 +105,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:43:28.363644", + "modified": "2022-01-19 12:57:37.898953", "modified_by": "Administrator", "module": "Payroll", "name": "Retention Bonus", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -163,7 +164,10 @@ "share": 1 } ], + "search_fields": "employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 4e40e13be0..fe8e22cedf 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -637,7 +637,7 @@ "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-12-23 11:47:47.098248", + "modified": "2022-01-19 12:45:54.999345", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", @@ -673,9 +673,11 @@ "role": "Employee" } ], + "search_fields": "employee_name", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "employee", "title_field": "employee_name" -} +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index b035292c0b..f33443d0d7 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -932,8 +932,11 @@ class SalarySlip(TransactionBase): def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount): future_recurring_additional_amount = 0 to_date = frappe.db.get_value("Additional Salary", additional_salary, 'to_date') + # future month count excluding current - future_recurring_period = (getdate(to_date).month - getdate(self.start_date).month) + from_date, to_date = getdate(self.start_date), getdate(to_date) + future_recurring_period = ((to_date.year - from_date.year) * 12) + (to_date.month - from_date.month) + if future_recurring_period > 0: future_recurring_additional_amount = monthly_additional_amount * future_recurring_period # Used earning.additional_amount to consider the amount for the full month return future_recurring_additional_amount @@ -1032,7 +1035,8 @@ class SalarySlip(TransactionBase): data.update({"annual_taxable_earning": annual_taxable_earning}) tax_amount = 0 for slab in tax_slab.slabs: - if slab.condition and not self.eval_tax_slab_condition(slab.condition, data): + cond = cstr(slab.condition).strip() + if cond and not self.eval_tax_slab_condition(cond, data): continue if not slab.to_amount and annual_taxable_earning >= slab.from_amount: tax_amount += (annual_taxable_earning - slab.from_amount + 1) * slab.percent_deduction *.01 @@ -1138,15 +1142,17 @@ class SalarySlip(TransactionBase): }) def make_loan_repayment_entry(self): + payroll_payable_account = get_payroll_payable_account(self.company, self.payroll_entry) for loan in self.loans: - repayment_entry = create_repayment_entry(loan.loan, self.employee, - self.company, self.posting_date, loan.loan_type, "Regular Payment", loan.interest_amount, - loan.principal_amount, loan.total_payment) + if loan.total_payment: + repayment_entry = create_repayment_entry(loan.loan, self.employee, + self.company, self.posting_date, loan.loan_type, "Regular Payment", loan.interest_amount, + loan.principal_amount, loan.total_payment, payroll_payable_account=payroll_payable_account) - repayment_entry.save() - repayment_entry.submit() + repayment_entry.save() + repayment_entry.submit() - frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name) + frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name) def cancel_loan_repayment_entry(self): for loan in self.loans: @@ -1380,3 +1386,11 @@ def get_salary_component_data(component): ], as_dict=1, ) + +def get_payroll_payable_account(company, payroll_entry): + if payroll_entry: + payroll_payable_account = frappe.db.get_value('Payroll Entry', payroll_entry, 'payroll_payable_account') + else: + payroll_payable_account = frappe.db.get_value('Company', company, 'default_payroll_payable_account') + + return payroll_payable_account \ No newline at end of file diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 6e8fae0c1a..bcf981b74d 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -380,7 +380,7 @@ class TestSalarySlip(unittest.TestCase): make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR', payroll_period=payroll_period) - frappe.db.sql("delete from tabLoan") + frappe.db.sql("delete from tabLoan where applicant = 'test_loan_repayment_salary_slip@salary.com'") loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1)) loan.repay_from_salary = 1 loan.submit() @@ -994,6 +994,8 @@ def make_leave_application(employee, from_date, to_date, leave_type, company=Non )) leave_application.submit() + return leave_application + def setup_test(): make_earning_salary_component(setup=True, company_list=["_Test Company"]) make_deduction_salary_component(setup=True, company_list=["_Test Company"]) diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json index 197ab5f25b..613246e732 100644 --- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json +++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json @@ -162,7 +162,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-12-23 17:28:09.794444", + "modified": "2022-01-19 12:43:54.439073", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Structure Assignment", @@ -209,6 +209,7 @@ "write": 1 } ], + "search_fields": "employee_name, salary_structure", "sort_field": "modified", "sort_order": "DESC", "states": [], diff --git a/erpnext/payroll/workspace/payroll/payroll.json b/erpnext/payroll/workspace/payroll/payroll.json index 7246dae5bc..762bea02c7 100644 --- a/erpnext/payroll/workspace/payroll/payroll.json +++ b/erpnext/payroll/workspace/payroll/payroll.json @@ -5,7 +5,7 @@ "label": "Outgoing Salary" } ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Payroll\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Salary\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Structure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payroll Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Slip\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Income Tax Slab\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payroll\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Compensations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Payroll\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Outgoing Salary\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Salary Structure\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payroll Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Salary Slip\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Income Tax Slab\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Salary Register\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Payroll\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Taxation\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Compensations\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", "creation": "2020-05-27 19:54:23.405607", "docstatus": 0, "doctype": "Workspace", @@ -312,7 +312,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.335325", + "modified": "2022-01-13 17:41:19.098813", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll", @@ -321,7 +321,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 19, + "sequence_id": 19.0, "shortcuts": [ { "label": "Salary Structure", diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 2570df7026..1cda0a08c4 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -235,13 +235,13 @@ { "fieldname": "actual_start_date", "fieldtype": "Data", - "label": "Actual Start Date", + "label": "Actual Start Date (via Time Sheet)", "read_only": 1 }, { "fieldname": "actual_time", "fieldtype": "Float", - "label": "Actual Time (in Hours)", + "label": "Actual Time (in Hours via Time Sheet)", "read_only": 1 }, { @@ -251,7 +251,7 @@ { "fieldname": "actual_end_date", "fieldtype": "Date", - "label": "Actual End Date", + "label": "Actual End Date (via Time Sheet)", "oldfieldname": "act_completion_date", "oldfieldtype": "Date", "read_only": 1 @@ -458,10 +458,11 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 4, - "modified": "2021-04-28 16:36:11.654632", + "modified": "2022-01-29 13:58:27.712714", "modified_by": "Administrator", "module": "Projects", "name": "Project", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -499,6 +500,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "customer", "title_field": "project_name", "track_seen": 1 diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index ef4740d9ee..81822085a5 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -249,7 +249,7 @@ { "fieldname": "actual_time", "fieldtype": "Float", - "label": "Actual Time (in hours)", + "label": "Actual Time (in Hours via Time Sheet)", "read_only": 1 }, { @@ -397,10 +397,11 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2021-04-16 12:46:51.556741", + "modified": "2022-01-29 13:58:47.005241", "modified_by": "Administrator", "module": "Projects", "name": "Task", + "naming_rule": "Expression (old style)", "nsm_parent_field": "parent_task", "owner": "Administrator", "permissions": [ @@ -421,6 +422,7 @@ "show_preview_popup": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "project", "title_field": "subject", "track_seen": 1 diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 9b1ea043be..8fa0538f36 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -102,7 +102,7 @@ class Task(NestedSet): frappe.throw(_("Completed On cannot be greater than Today")) def update_depends_on(self): - depends_on_tasks = self.depends_on_tasks or "" + depends_on_tasks = "" for d in self.depends_on: if d.task and d.task not in depends_on_tasks: depends_on_tasks += d.task + "," diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index a0ac7c1497..5f5b519ba4 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -78,11 +78,11 @@ class TestTask(unittest.TestCase): return frappe.db.get_value("ToDo", filters={"reference_type": task.doctype, "reference_name": task.name, "description": "Close this task"}, - fieldname=("owner", "status"), as_dict=True) + fieldname=("allocated_to", "status"), as_dict=True) assign() todo = get_owner_and_status() - self.assertEqual(todo.owner, "test@example.com") + self.assertEqual(todo.allocated_to, "test@example.com") self.assertEqual(todo.status, "Open") # assignment should be @@ -90,7 +90,7 @@ class TestTask(unittest.TestCase): task.status = "Completed" task.save() todo = get_owner_and_status() - self.assertEqual(todo.owner, "test@example.com") + self.assertEqual(todo.allocated_to, "test@example.com") self.assertEqual(todo.status, "Closed") def test_overdue(self): diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 148d8ba29c..989bcd1670 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -5,7 +5,7 @@ import datetime import unittest import frappe -from frappe.utils import add_months, now_datetime, nowdate +from frappe.utils import add_months, add_to_date, now_datetime, nowdate from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.hr.doctype.employee.test_employee import make_employee @@ -151,6 +151,27 @@ class TestTimesheet(unittest.TestCase): settings.ignore_employee_time_overlap = initial_setting settings.save() + def test_to_time(self): + emp = make_employee("test_employee_6@salary.com") + from_time = now_datetime() + + timesheet = frappe.new_doc("Timesheet") + timesheet.employee = emp + timesheet.append( + 'time_logs', + { + "billable": 1, + "activity_type": "_Test Activity Type", + "from_time": from_time, + "hours": 2, + "company": "_Test Company" + } + ) + timesheet.save() + + to_time = timesheet.time_logs[0].to_time + self.assertEqual(to_time, add_to_date(from_time, hours=2, as_datetime=True)) + def make_salary_structure_for_timesheet(employee, company=None): salary_structure_name = "Timesheet Salary Structure Test" diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index e92785e06c..dd0b5f90f4 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -7,7 +7,7 @@ import json import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import flt, getdate, time_diff_in_hours +from frappe.utils import add_to_date, flt, getdate, time_diff_in_hours from erpnext.controllers.queries import get_match_cond from erpnext.hr.utils import validate_active_employee @@ -136,10 +136,19 @@ class Timesheet(Document): def validate_time_logs(self): for data in self.get('time_logs'): + self.set_to_time(data) self.validate_overlap(data) self.set_project(data) self.validate_project(data) + def set_to_time(self, data): + if not (data.from_time and data.hours): + return + + _to_time = add_to_date(data.from_time, hours=data.hours, as_datetime=True) + if data.to_time != _to_time: + data.to_time = _to_time + def validate_overlap(self, data): settings = frappe.get_single('Projects Settings') self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap) diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index 1df2b08983..c5a047d5cb 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -5,7 +5,7 @@ "label": "Open Projects" } ], - "content": "[{\"type\": \"chart\", \"data\": {\"chart_name\": \"Open Projects\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Task\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Timesheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project Billing Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Projects\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Time Tracking\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", + "content": "[{\"type\":\"chart\",\"data\":{\"chart_name\":\"Open Projects\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Task\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Timesheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project Billing Summary\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Projects\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Time Tracking\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", "creation": "2020-03-02 15:46:04.874669", "docstatus": 0, "doctype": "Workspace", @@ -194,7 +194,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.540147", + "modified": "2022-01-13 17:41:55.163878", "modified_by": "Administrator", "module": "Projects", "name": "Projects", @@ -203,7 +203,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 20, + "sequence_id": 20.0, "shortcuts": [ { "color": "Blue", diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 7c1c8c7e46..ae0e2a3f6f 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -114,6 +114,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if ((!item.qty) && me.frm.doc.is_return) { item.amount = flt(item.rate * -1, precision("amount", item)); + } else if ((!item.qty) && me.frm.doc.is_debit_note) { + item.amount = flt(item.rate, precision("amount", item)); } else { item.amount = flt(item.rate * item.qty, precision("amount", item)); } @@ -710,14 +712,15 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) { - var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; + let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; + let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total; if(this.frm.doc.party_account_currency == this.frm.doc.currency) { var total_amount_to_pay = flt((grand_total - this.frm.doc.total_advance - this.frm.doc.write_off_amount), precision("grand_total")); } else { var total_amount_to_pay = flt( - (flt(grand_total*this.frm.doc.conversion_rate, precision("grand_total")) + (flt(base_grand_total, precision("base_grand_total")) - this.frm.doc.total_advance - this.frm.doc.base_write_off_amount), precision("base_grand_total") ); @@ -748,14 +751,15 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } set_total_amount_to_default_mop() { - var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; + let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; + let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total; if(this.frm.doc.party_account_currency == this.frm.doc.currency) { var total_amount_to_pay = flt((grand_total - this.frm.doc.total_advance - this.frm.doc.write_off_amount), precision("grand_total")); } else { var total_amount_to_pay = flt( - (flt(grand_total*this.frm.doc.conversion_rate, precision("grand_total")) + (flt(base_grand_total, precision("base_grand_total")) - this.frm.doc.total_advance - this.frm.doc.base_write_off_amount), precision("base_grand_total") ); diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index 38e1eb5156..e746ce9ae0 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -27,7 +27,6 @@ erpnext.setup.slides_settings = [ { "label": __("Manufacturing"), "value": "Manufacturing" }, { "label": __("Retail"), "value": "Retail" }, { "label": __("Services"), "value": "Services" }, - { "label": __("Agriculture (beta)"), "value": "Agriculture" }, { "label": __("Healthcare (beta)"), "value": "Healthcare" }, { "label": __("Non Profit (beta)"), "value": "Non Profit" } ], reqd: 1 diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 597d77c6e9..08270bdea1 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -592,6 +592,6 @@ function check_can_calculate_pending_qty(me) { && doc.fg_completed_qty && erpnext.stock.bom && erpnext.stock.bom.name === doc.bom_no; - const itemChecks = !!item; + const itemChecks = !!item && !item.allow_alternative_item; return docChecks && itemChecks; } diff --git a/erpnext/quality_management/workspace/quality/quality.json b/erpnext/quality_management/workspace/quality/quality.json index ae28470182..3effd59d8e 100644 --- a/erpnext/quality_management/workspace/quality/quality.json +++ b/erpnext/quality_management/workspace/quality/quality.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Goal\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Procedure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Inspection\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Review\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Action\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Conformance\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goal and Procedure\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Feedback\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Meeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Review and Action\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Goal\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Procedure\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Inspection\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Review\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Action\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Non Conformance\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Goal and Procedure\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Feedback\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Meeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Review and Action\",\"col\":4}}]", "creation": "2020-03-02 15:49:28.632014", "docstatus": 0, "doctype": "Workspace", @@ -142,7 +142,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.699913", + "modified": "2022-01-13 17:42:20.105187", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality", @@ -151,7 +151,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 21, + "sequence_id": 21.0, "shortcuts": [ { "color": "Grey", diff --git a/erpnext/agriculture/doctype/disease/__init__.py b/erpnext/regional/doctype/e_invoice_request_log/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/disease/__init__.py rename to erpnext/regional/doctype/e_invoice_request_log/__init__.py diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js new file mode 100644 index 0000000000..7b7ba964e5 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('E Invoice Request Log', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json new file mode 100644 index 0000000000..3034370fea --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json @@ -0,0 +1,102 @@ +{ + "actions": [], + "autoname": "EINV-REQ-.#####", + "creation": "2020-12-08 12:54:08.175992", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user", + "url", + "headers", + "response", + "column_break_7", + "timestamp", + "reference_invoice", + "data" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User" + }, + { + "fieldname": "reference_invoice", + "fieldtype": "Data", + "label": "Reference Invoice" + }, + { + "fieldname": "headers", + "fieldtype": "Code", + "label": "Headers", + "options": "JSON" + }, + { + "fieldname": "data", + "fieldtype": "Code", + "label": "Data", + "options": "JSON" + }, + { + "default": "Now", + "fieldname": "timestamp", + "fieldtype": "Datetime", + "label": "Timestamp" + }, + { + "fieldname": "response", + "fieldtype": "Code", + "label": "Response", + "options": "JSON" + }, + { + "fieldname": "url", + "fieldtype": "Data", + "label": "URL" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-01-13 12:06:57.253111", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice Request Log", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py new file mode 100644 index 0000000000..c89552d782 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class EInvoiceRequestLog(Document): + pass diff --git a/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py new file mode 100644 index 0000000000..091cc88e45 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + + +class TestEInvoiceRequestLog(unittest.TestCase): + pass diff --git a/erpnext/agriculture/doctype/fertilizer/__init__.py b/erpnext/regional/doctype/e_invoice_settings/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/fertilizer/__init__.py rename to erpnext/regional/doctype/e_invoice_settings/__init__.py diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js new file mode 100644 index 0000000000..54e488610d --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -0,0 +1,11 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('E Invoice Settings', { + refresh(frm) { + const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing'; + frm.dashboard.set_headline( + __("Read {0} for more information on E Invoicing features.", [`documentation`]) + ); + } +}); diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json new file mode 100644 index 0000000000..16b2963301 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -0,0 +1,97 @@ +{ + "actions": [], + "creation": "2020-09-24 16:23:16.235722", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enable", + "section_break_2", + "sandbox_mode", + "applicable_from", + "credentials", + "advanced_settings_section", + "client_id", + "column_break_8", + "client_secret", + "auth_token", + "token_expiry" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "enable", + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "auth_token", + "fieldtype": "Data", + "hidden": 1, + "read_only": 1 + }, + { + "fieldname": "token_expiry", + "fieldtype": "Datetime", + "hidden": 1, + "read_only": 1 + }, + { + "fieldname": "credentials", + "fieldtype": "Table", + "label": "Credentials", + "mandatory_depends_on": "enable", + "options": "E Invoice User" + }, + { + "default": "0", + "fieldname": "sandbox_mode", + "fieldtype": "Check", + "label": "Sandbox Mode" + }, + { + "fieldname": "applicable_from", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Applicable From", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "advanced_settings_section", + "fieldtype": "Section Break", + "label": "Advanced Settings" + }, + { + "fieldname": "client_id", + "fieldtype": "Data", + "label": "Client ID" + }, + { + "fieldname": "client_secret", + "fieldtype": "Password", + "label": "Client Secret" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2021-11-16 19:50:28.029517", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice Settings", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py new file mode 100644 index 0000000000..342d583cc0 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class EInvoiceSettings(Document): + def validate(self): + if self.enable and not self.credentials: + frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.')) diff --git a/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py new file mode 100644 index 0000000000..10770deb0e --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + + +class TestEInvoiceSettings(unittest.TestCase): + pass diff --git a/erpnext/agriculture/doctype/fertilizer_content/__init__.py b/erpnext/regional/doctype/e_invoice_user/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/fertilizer_content/__init__.py rename to erpnext/regional/doctype/e_invoice_user/__init__.py diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json new file mode 100644 index 0000000000..a65b1ca7ca --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "creation": "2020-12-22 15:02:46.229474", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "gstin", + "username", + "password" + ], + "fields": [ + { + "fieldname": "gstin", + "fieldtype": "Data", + "in_list_view": 1, + "label": "GSTIN", + "reqd": 1 + }, + { + "fieldname": "username", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Username", + "reqd": 1 + }, + { + "fieldname": "password", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Password", + "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-03-22 12:16:56.365616", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice User", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py similarity index 77% rename from erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py rename to erpnext/regional/doctype/e_invoice_user/e_invoice_user.py index dcf0e3b99d..4e0e89c09a 100644 --- a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py @@ -1,10 +1,10 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - # import frappe from frappe.model.document import Document -class DistributedCostCenter(Document): +class EInvoiceUser(Document): pass diff --git a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js index 07a93010b5..66531412fa 100644 --- a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js +++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js @@ -2,7 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on('UAE VAT Settings', { - // refresh: function(frm) { - - // } + onload: function(frm) { + frm.set_query('account', 'uae_vat_accounts', function() { + return { + filters: { + 'company': frm.doc.company + } + }; + }); + } }); diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index 2d1e02eadb..ec271a1029 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -1,11 +1,11 @@ import datetime import zipfile from csv import QUOTE_NONNUMERIC +from io import BytesIO import frappe import pandas as pd from frappe import _ -from six import BytesIO from .datev_constants import DataCategory diff --git a/erpnext/agriculture/doctype/linked_location/__init__.py b/erpnext/regional/india/e_invoice/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/linked_location/__init__.py rename to erpnext/regional/india/e_invoice/__init__.py diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json new file mode 100644 index 0000000000..78e56518df --- /dev/null +++ b/erpnext/regional/india/e_invoice/einv_item_template.json @@ -0,0 +1,31 @@ +{{ + "SlNo": "{item.sr_no}", + "PrdDesc": "{item.description}", + "IsServc": "{item.is_service_item}", + "HsnCd": "{item.gst_hsn_code}", + "Barcde": "{item.barcode}", + "Unit": "{item.uom}", + "Qty": "{item.qty}", + "FreeQty": "{item.free_qty}", + "UnitPrice": "{item.unit_rate}", + "TotAmt": "{item.gross_amount}", + "Discount": "{item.discount_amount}", + "AssAmt": "{item.taxable_value}", + "PrdSlNo": "{item.serial_no}", + "GstRt": "{item.tax_rate}", + "IgstAmt": "{item.igst_amount}", + "CgstAmt": "{item.cgst_amount}", + "SgstAmt": "{item.sgst_amount}", + "CesRt": "{item.cess_rate}", + "CesAmt": "{item.cess_amount}", + "CesNonAdvlAmt": "{item.cess_nadv_amount}", + "StateCesRt": "{item.state_cess_rate}", + "StateCesAmt": "{item.state_cess_amount}", + "StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}", + "OthChrg": "{item.other_charges}", + "TotItemVal": "{item.total_value}", + "BchDtls": {{ + "Nm": "{item.batch_no}", + "ExpDt": "{item.batch_expiry_date}" + }} +}} \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json new file mode 100644 index 0000000000..c2a28f2049 --- /dev/null +++ b/erpnext/regional/india/e_invoice/einv_template.json @@ -0,0 +1,110 @@ +{{ + "Version": "1.1", + "TranDtls": {{ + "TaxSch": "{transaction_details.tax_scheme}", + "SupTyp": "{transaction_details.supply_type}", + "RegRev": "{transaction_details.reverse_charge}", + "EcmGstin": "{transaction_details.ecom_gstin}", + "IgstOnIntra": "{transaction_details.igst_on_intra}" + }}, + "DocDtls": {{ + "Typ": "{doc_details.invoice_type}", + "No": "{doc_details.invoice_name}", + "Dt": "{doc_details.invoice_date}" + }}, + "SellerDtls": {{ + "Gstin": "{seller_details.gstin}", + "LglNm": "{seller_details.legal_name}", + "TrdNm": "{seller_details.trade_name}", + "Loc": "{seller_details.location}", + "Pin": "{seller_details.pincode}", + "Stcd": "{seller_details.state_code}", + "Addr1": "{seller_details.address_line1}", + "Addr2": "{seller_details.address_line2}", + "Ph": "{seller_details.phone}", + "Em": "{seller_details.email}" + }}, + "BuyerDtls": {{ + "Gstin": "{buyer_details.gstin}", + "LglNm": "{buyer_details.legal_name}", + "TrdNm": "{buyer_details.trade_name}", + "Addr1": "{buyer_details.address_line1}", + "Addr2": "{buyer_details.address_line2}", + "Loc": "{buyer_details.location}", + "Pin": "{buyer_details.pincode}", + "Stcd": "{buyer_details.state_code}", + "Ph": "{buyer_details.phone}", + "Em": "{buyer_details.email}", + "Pos": "{buyer_details.place_of_supply}" + }}, + "DispDtls": {{ + "Nm": "{dispatch_details.legal_name}", + "Addr1": "{dispatch_details.address_line1}", + "Addr2": "{dispatch_details.address_line2}", + "Loc": "{dispatch_details.location}", + "Pin": "{dispatch_details.pincode}", + "Stcd": "{dispatch_details.state_code}" + }}, + "ShipDtls": {{ + "Gstin": "{shipping_details.gstin}", + "LglNm": "{shipping_details.legal_name}", + "TrdNm": "{shipping_details.trader_name}", + "Addr1": "{shipping_details.address_line1}", + "Addr2": "{shipping_details.address_line2}", + "Loc": "{shipping_details.location}", + "Pin": "{shipping_details.pincode}", + "Stcd": "{shipping_details.state_code}" + }}, + "ItemList": [ + {item_list} + ], + "ValDtls": {{ + "AssVal": "{invoice_value_details.base_total}", + "CgstVal": "{invoice_value_details.total_cgst_amt}", + "SgstVal": "{invoice_value_details.total_sgst_amt}", + "IgstVal": "{invoice_value_details.total_igst_amt}", + "CesVal": "{invoice_value_details.total_cess_amt}", + "Discount": "{invoice_value_details.invoice_discount_amt}", + "RndOffAmt": "{invoice_value_details.round_off}", + "OthChrg": "{invoice_value_details.total_other_charges}", + "TotInvVal": "{invoice_value_details.base_grand_total}", + "TotInvValFc": "{invoice_value_details.grand_total}" + }}, + "PayDtls": {{ + "Nm": "{payment_details.payee_name}", + "AccDet": "{payment_details.account_no}", + "Mode": "{payment_details.mode_of_payment}", + "FinInsBr": "{payment_details.ifsc_code}", + "PayTerm": "{payment_details.terms}", + "PaidAmt": "{payment_details.paid_amount}", + "PaymtDue": "{payment_details.outstanding_amount}" + }}, + "RefDtls": {{ + "DocPerdDtls": {{ + "InvStDt": "{period_details.start_date}", + "InvEndDt": "{period_details.end_date}" + }}, + "PrecDocDtls": [{{ + "InvNo": "{prev_doc_details.invoice_name}", + "InvDt": "{prev_doc_details.invoice_date}" + }}] + }}, + "ExpDtls": {{ + "ShipBNo": "{export_details.bill_no}", + "ShipBDt": "{export_details.bill_date}", + "Port": "{export_details.port}", + "ForCur": "{export_details.foreign_curr_code}", + "CntCode": "{export_details.country_code}", + "ExpDuty": "{export_details.export_duty}" + }}, + "EwbDtls": {{ + "TransId": "{eway_bill_details.gstin}", + "TransName": "{eway_bill_details.name}", + "TransMode": "{eway_bill_details.mode_of_transport}", + "Distance": "{eway_bill_details.distance}", + "TransDocNo": "{eway_bill_details.document_name}", + "TransDocDt": "{eway_bill_details.document_date}", + "VehNo": "{eway_bill_details.vehicle_no}", + "VehType": "{eway_bill_details.vehicle_type}" + }} +}} \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json new file mode 100644 index 0000000000..f4a3542a60 --- /dev/null +++ b/erpnext/regional/india/e_invoice/einv_validation.json @@ -0,0 +1,957 @@ +{ + "Version": { + "type": "string", + "minLength": 1, + "maxLength": 6, + "description": "Version of the schema" + }, + "Irn": { + "type": "string", + "minLength": 64, + "maxLength": 64, + "description": "Invoice Reference Number" + }, + "TranDtls": { + "type": "object", + "properties": { + "TaxSch": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "enum": ["GST"], + "description": "GST- Goods and Services Tax Scheme" + }, + "SupTyp": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"], + "description": "Type of Supply: B2B-Business to Business, SEZWP - SEZ with payment, SEZWOP - SEZ without payment, EXPWP - Export with Payment, EXPWOP - Export without payment,DEXP - Deemed Export" + }, + "RegRev": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"], + "description": "Y- whether the tax liability is payable under reverse charge" + }, + "EcmGstin": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "pattern": "([0-9]{2}[0-9A-Z]{13})", + "description": "E-Commerce GSTIN", + "validationMsg": "E-Commerce GSTIN is invalid" + }, + "IgstOnIntra": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"], + "description": "Y- indicates the supply is intra state but chargeable to IGST" + } + }, + "required": ["TaxSch", "SupTyp"] + }, + "DocDtls": { + "type": "object", + "properties": { + "Typ": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "enum": ["INV", "CRN", "DBN"], + "description": "Document Type" + }, + "No": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$", + "description": "Document Number", + "validationMsg": "Document Number should not be starting with 0, / and -" + }, + "Dt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Document Date" + } + }, + "required": ["Typ", "No", "Dt"] + }, + "SellerDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "pattern": "([0-9]{2}[0-9A-Z]{13})", + "description": "Supplier GSTIN", + "validationMsg": "Company GSTIN is invalid" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Legal Name" + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Tradename" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 50, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "Supplier State Code" + }, + "Ph": { + "type": "string", + "minLength": 6, + "maxLength": 12, + "description": "Phone" + }, + "Em": { + "type": "string", + "minLength": 6, + "maxLength": 100, + "description": "Email-Id" + } + }, + "required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "BuyerDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "minLength": 3, + "maxLength": 15, + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", + "description": "Buyer GSTIN", + "validationMsg": "Customer GSTIN is invalid" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Legal Name" + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Trade Name" + }, + "Pos": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "Place of Supply State code" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "Buyer State Code" + }, + "Ph": { + "type": "string", + "minLength": 6, + "maxLength": 12, + "description": "Phone" + }, + "Em": { + "type": "string", + "minLength": 6, + "maxLength": 100, + "description": "Email-Id" + } + }, + "required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"] + }, + "DispDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Dispatch Address Name" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "State Code" + } + }, + "required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "ShipDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "maxLength": 15, + "minLength": 3, + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", + "description": "Shipping Address GSTIN", + "validationMsg": "Shipping Address GSTIN is invalid" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Legal Name" + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Trade Name" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "State Code" + } + }, + "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "ItemList": { + "type": "Array", + "properties": { + "SlNo": { + "type": "string", + "minLength": 1, + "maxLength": 6, + "description": "Serial No. of Item" + }, + "PrdDesc": { + "type": "string", + "minLength": 3, + "maxLength": 300, + "description": "Item Name" + }, + "IsServc": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"], + "description": "Is Service Item" + }, + "HsnCd": { + "type": "string", + "minLength": 4, + "maxLength": 8, + "description": "HSN Code" + }, + "Barcde": { + "type": "string", + "minLength": 3, + "maxLength": 30, + "description": "Barcode" + }, + "Qty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999, + "description": "Quantity" + }, + "FreeQty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999, + "description": "Free Quantity" + }, + "Unit": { + "type": "string", + "minLength": 3, + "maxLength": 8, + "description": "UOM" + }, + "UnitPrice": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.999, + "description": "Rate" + }, + "TotAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Gross Amount" + }, + "Discount": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Discount" + }, + "PreTaxVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Pre tax value" + }, + "AssAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Taxable Value" + }, + "GstRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999, + "description": "GST Rate" + }, + "IgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "IGST Amount" + }, + "CgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "CGST Amount" + }, + "SgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "SGST Amount" + }, + "CesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999, + "description": "Cess Rate" + }, + "CesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Cess Amount (Advalorem)" + }, + "CesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Cess Amount (Non-Advalorem)" + }, + "StateCesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999, + "description": "State CESS Rate" + }, + "StateCesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "State CESS Amount" + }, + "StateCesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "State CESS Amount (Non Advalorem)" + }, + "OthChrg": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Other Charges" + }, + "TotItemVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Total Item Value" + }, + "OrdLineRef": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "description": "Order line reference" + }, + "OrgCntry": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Origin Country" + }, + "PrdSlNo": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Serial number" + }, + "BchDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 3, + "maxLength": 20, + "description": "Batch number" + }, + "ExpDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Batch Expiry Date" + }, + "WrDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Warranty Date" + } + }, + "required": ["Nm"] + }, + "AttribDtls": { + "type": "Array", + "Attribute": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Attribute name of the item" + }, + "Val": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Attribute value of the item" + } + } + } + } + }, + "required": [ + "SlNo", + "IsServc", + "HsnCd", + "UnitPrice", + "TotAmt", + "AssAmt", + "GstRt", + "TotItemVal" + ] + }, + "ValDtls": { + "type": "object", + "properties": { + "AssVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total Assessable value of all items" + }, + "CgstVal": { + "type": "number", + "maximum": 99999999999999.99, + "minimum": 0, + "description": "Total CGST value of all items" + }, + "SgstVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total SGST value of all items" + }, + "IgstVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total IGST value of all items" + }, + "CesVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total CESS value of all items" + }, + "StCesVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total State CESS value of all items" + }, + "Discount": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Invoice Discount" + }, + "OthChrg": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Other Charges" + }, + "RndOffAmt": { + "type": "number", + "minimum": -99.99, + "maximum": 99.99, + "description": "Rounded off Amount" + }, + "TotInvVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Final Invoice Value " + }, + "TotInvValFc": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Final Invoice value in Foreign Currency" + } + }, + "required": ["AssVal", "TotInvVal"] + }, + "PayDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Payee Name" + }, + "AccDet": { + "type": "string", + "minLength": 1, + "maxLength": 18, + "description": "Bank Account Number of Payee" + }, + "Mode": { + "type": "string", + "minLength": 1, + "maxLength": 18, + "description": "Mode of Payment" + }, + "FinInsBr": { + "type": "string", + "minLength": 1, + "maxLength": 11, + "description": "Branch or IFSC code" + }, + "PayTerm": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Terms of Payment" + }, + "PayInstr": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Payment Instruction" + }, + "CrTrn": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Credit Transfer" + }, + "DirDr": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Direct Debit" + }, + "CrDay": { + "type": "number", + "minimum": 0, + "maximum": 9999, + "description": "Credit Days" + }, + "PaidAmt": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Advance Amount" + }, + "PaymtDue": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Outstanding Amount" + } + } + }, + "RefDtls": { + "type": "object", + "properties": { + "InvRm": { + "type": "string", + "maxLength": 100, + "minLength": 3, + "pattern": "^[0-9A-Za-z/-]{3,100}$", + "description": "Remarks/Note" + }, + "DocPerdDtls": { + "type": "object", + "properties": { + "InvStDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Invoice Period Start Date" + }, + "InvEndDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Invoice Period End Date" + } + }, + "required": ["InvStDt ", "InvEndDt "] + }, + "PrecDocDtls": { + "type": "object", + "properties": { + "InvNo": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$", + "description": "Reference of Original Invoice" + }, + "InvDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Date of Orginal Invoice" + }, + "OthRefNo": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Other Reference" + } + } + }, + "required": ["InvNo", "InvDt"], + "ContrDtls": { + "type": "object", + "properties": { + "RecAdvRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Receipt Advice No." + }, + "RecAdvDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Date of receipt advice" + }, + "TendRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Lot/Batch Reference No." + }, + "ContrRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Contract Reference Number" + }, + "ExtRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Any other reference" + }, + "ProjRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Project Reference Number" + }, + "PORefr": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^([0-9A-Za-z/-]){1,16}$", + "description": "PO Reference Number" + }, + "PORefDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "PO Reference date" + } + } + } + } + }, + "AddlDocDtls": { + "type": "Array", + "properties": { + "Url": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Supporting document URL" + }, + "Docs": { + "type": "string", + "minLength": 3, + "maxLength": 1000, + "description": "Supporting document in Base64 Format" + }, + "Info": { + "type": "string", + "minLength": 3, + "maxLength": 1000, + "description": "Any additional information" + } + } + }, + + "ExpDtls": { + "type": "object", + "properties": { + "ShipBNo": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Shipping Bill No." + }, + "ShipBDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Shipping Bill Date" + }, + "Port": { + "type": "string", + "minLength": 2, + "maxLength": 10, + "pattern": "^[0-9A-Za-z]{2,10}$", + "description": "Port Code. Refer the master" + }, + "RefClm": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "description": "Claiming Refund. Y/N" + }, + "ForCur": { + "type": "string", + "minLength": 3, + "maxLength": 16, + "description": "Additional Currency Code. Refer the master" + }, + "CntCode": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Country Code. Refer the master" + }, + "ExpDuty": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Export Duty" + } + } + }, + "EwbDtls": { + "type": "object", + "properties": { + "TransId": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "description": "Transporter GSTIN" + }, + "TransName": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Transporter Name" + }, + "TransMode": { + "type": "string", + "maxLength": 1, + "minLength": 1, + "enum": ["1", "2", "3", "4"], + "description": "Mode of Transport" + }, + "Distance": { + "type": "number", + "minimum": 1, + "maximum": 9999, + "description": "Distance" + }, + "TransDocNo": { + "type": "string", + "minLength": 1, + "maxLength": 15, + "pattern": "^([0-9A-Z/-]){1,15}$", + "description": "Tranport Document Number", + "validationMsg": "Transport Receipt No is invalid" + }, + "TransDocDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Transport Document Date" + }, + "VehNo": { + "type": "string", + "minLength": 4, + "maxLength": 20, + "description": "Vehicle Number" + }, + "VehType": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["O", "R"], + "description": "Vehicle Type" + } + }, + "required": ["Distance"] + }, + "required": [ + "Version", + "TranDtls", + "DocDtls", + "SellerDtls", + "BuyerDtls", + "ItemList", + "ValDtls" + ] +} diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js new file mode 100644 index 0000000000..348f0c6fee --- /dev/null +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -0,0 +1,292 @@ +erpnext.setup_einvoice_actions = (doctype) => { + frappe.ui.form.on(doctype, { + async refresh(frm) { + if (frm.doc.docstatus == 2) return; + + const res = await frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility', + args: { doc: frm.doc } + }); + const invoice_eligible = res.message; + + if (!invoice_eligible) return; + + const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; + + const add_custom_button = (label, action) => { + if (!frm.custom_buttons[label]) { + frm.add_custom_button(label, action, __('E Invoicing')); + } + }; + + if (!irn && !__unsaved) { + const action = () => { + if (frm.doc.__unsaved) { + frappe.throw(__('Please save the document to generate IRN.')); + } + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.get_einvoice', + args: { doctype, docname: name }, + freeze: true, + callback: (res) => { + const einvoice = res.message; + show_einvoice_preview(frm, einvoice); + } + }); + }; + + add_custom_button(__("Generate IRN"), action); + } + + if (irn && !irn_cancelled && !ewaybill) { + const fields = [ + { + "label": "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + const action = () => { + const d = new frappe.ui.Dialog({ + title: __("Cancel IRN"), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_irn', + args: { + doctype, + docname: name, + irn: irn, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + add_custom_button(__("Cancel IRN"), action); + } + + if (irn && !irn_cancelled && !ewaybill) { + const action = () => { + const d = new frappe.ui.Dialog({ + title: __('Generate E-Way Bill'), + size: "large", + fields: get_ewaybill_fields(frm), + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill', + args: { + doctype, + docname: name, + irn, + ...data + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + + add_custom_button(__("Generate E-Way Bill"), action); + } + + if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { + const action = () => { + let message = __('Cancellation of e-way bill is currently not supported.') + ' '; + message += '

'; + message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.'); + + const dialog = frappe.msgprint({ + title: __('Update E-Way Bill Cancelled Status?'), + message: message, + indicator: 'orange', + primary_action: { + action: function() { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', + args: { doctype, docname: name }, + freeze: true, + callback: () => frm.reload_doc() || dialog.hide() + }); + } + }, + primary_action_label: __('Yes') + }); + }; + add_custom_button(__("Cancel E-Way Bill"), action); + } + } + }); +}; + +const get_ewaybill_fields = (frm) => { + return [ + { + 'fieldname': 'transporter', + 'label': 'Transporter', + 'fieldtype': 'Link', + 'options': 'Supplier', + 'default': frm.doc.transporter + }, + { + 'fieldname': 'gst_transporter_id', + 'label': 'GST Transporter ID', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.gst_transporter_id', + 'default': frm.doc.gst_transporter_id + }, + { + 'fieldname': 'driver', + 'label': 'Driver', + 'fieldtype': 'Link', + 'options': 'Driver', + 'default': frm.doc.driver + }, + { + 'fieldname': 'lr_no', + 'label': 'Transport Receipt No', + 'fieldtype': 'Data', + 'default': frm.doc.lr_no + }, + { + 'fieldname': 'vehicle_no', + 'label': 'Vehicle No', + 'fieldtype': 'Data', + 'default': frm.doc.vehicle_no + }, + { + 'fieldname': 'distance', + 'label': 'Distance (in km)', + 'fieldtype': 'Float', + 'default': frm.doc.distance + }, + { + 'fieldname': 'transporter_col_break', + 'fieldtype': 'Column Break', + }, + { + 'fieldname': 'transporter_name', + 'label': 'Transporter Name', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.name', + 'read_only': 1, + 'default': frm.doc.transporter_name + }, + { + 'fieldname': 'mode_of_transport', + 'label': 'Mode of Transport', + 'fieldtype': 'Select', + 'options': `\nRoad\nAir\nRail\nShip`, + 'default': frm.doc.mode_of_transport + }, + { + 'fieldname': 'driver_name', + 'label': 'Driver Name', + 'fieldtype': 'Data', + 'fetch_from': 'driver.full_name', + 'read_only': 1, + 'default': frm.doc.driver_name + }, + { + 'fieldname': 'lr_date', + 'label': 'Transport Receipt Date', + 'fieldtype': 'Date', + 'default': frm.doc.lr_date + }, + { + 'fieldname': 'gst_vehicle_type', + 'label': 'GST Vehicle Type', + 'fieldtype': 'Select', + 'options': `Regular\nOver Dimensional Cargo (ODC)`, + 'depends_on': 'eval:(doc.mode_of_transport === "Road")', + 'default': frm.doc.gst_vehicle_type + } + ]; +}; + +const request_irn_generation = (frm) => { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_irn', + args: { doctype: frm.doc.doctype, docname: frm.doc.name }, + freeze: true, + callback: () => frm.reload_doc() + }); +}; + +const get_preview_dialog = (frm, action) => { + const dialog = new frappe.ui.Dialog({ + title: __("Preview"), + size: "large", + fields: [ + { + "label": "Preview", + "fieldname": "preview_html", + "fieldtype": "HTML" + } + ], + primary_action: () => action(frm) || dialog.hide(), + primary_action_label: __('Generate IRN') + }); + return dialog; +}; + +const show_einvoice_preview = (frm, einvoice) => { + const preview_dialog = get_preview_dialog(frm, request_irn_generation); + + // initialize e-invoice fields + einvoice["Irn"] = einvoice["AckNo"] = ''; einvoice["AckDt"] = frappe.datetime.nowdate(); + frm.doc.signed_einvoice = JSON.stringify(einvoice); + + // initialize preview wrapper + const $preview_wrapper = preview_dialog.get_field("preview_html").$wrapper; + $preview_wrapper.html( + `
+ +
+
` + ); + + frappe.call({ + method: "frappe.www.printview.get_html_and_style", + args: { + doc: frm.doc, + print_format: "GST E-Invoice", + no_letterhead: 1 + }, + callback: function (r) { + if (!r.exc) { + $preview_wrapper.find(".print-format").html(r.message.html); + const style = ` + .print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; } + .print-preview { min-height: 0px; } + .modal-dialog { width: 720px; }`; + + frappe.dom.set_style(style, "custom-print-style"); + preview_dialog.show(); + } + } + }); +}; diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py new file mode 100644 index 0000000000..e3f7e90ff3 --- /dev/null +++ b/erpnext/regional/india/e_invoice/utils.py @@ -0,0 +1,1171 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import base64 +import io +import json +import os +import re +import sys +import traceback + +import frappe +import jwt +from frappe import _, bold +from frappe.core.page.background_jobs.background_jobs import get_info +from frappe.integrations.utils import make_get_request, make_post_request +from frappe.utils.background_jobs import enqueue +from frappe.utils.data import ( + add_to_date, + cint, + cstr, + flt, + format_date, + get_link_to_form, + getdate, + now_datetime, + time_diff_in_hours, + time_diff_in_seconds, +) +from frappe.utils.scheduler import is_scheduler_inactive +from pyqrcode import create as qrcreate + +from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply + + +@frappe.whitelist() +def validate_eligibility(doc): + if isinstance(doc, str): + doc = json.loads(doc) + + invalid_doctype = doc.get('doctype') != 'Sales Invoice' + if invalid_doctype: + return False + + einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable')) + if not einvoicing_enabled: + return False + + einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' + if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): + return False + + invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') }) + invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] + company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') + + # if export invoice, then taxes can be empty + # invoice can only be ineligible if no taxes applied and is not an export invoice + no_taxes_applied = not doc.get('taxes') and not doc.get('gst_category') == 'Overseas' + has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst')) + + if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: + return False + + return True + +def validate_einvoice_fields(doc): + invoice_eligible = validate_eligibility(doc) + + if not invoice_eligible: + return + + if doc.docstatus == 0 and doc._action == 'save': + if doc.irn: + frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) + if len(doc.name) > 16: + raise_document_name_too_long_error() + + doc.einvoice_status = 'Pending' + + elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: + frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) + + elif doc.irn and doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: + frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) + +def raise_document_name_too_long_error(): + title = _('Document ID Too Long') + msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice') + msg += ', ' + msg += _('document id {} exceed 16 letters.').format(bold(_('should not'))) + msg += '

' + msg += _('You must {} your {} in order to have document id of {} length 16.').format( + bold(_('modify')), bold(_('naming series')), bold(_('maximum')) + ) + msg += _('Please account for ammended documents too.') + frappe.throw(msg, title=title) + +def read_json(name): + file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name)) + with open(file_path, 'r') as f: + return cstr(f.read()) + +def get_transaction_details(invoice): + supply_type = '' + if invoice.gst_category == 'Registered Regular': supply_type = 'B2B' + elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP' + elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP' + elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP' + + if not supply_type: + rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export') + frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export), + title=_('Invalid Supply Type')) + + return frappe._dict(dict( + tax_scheme='GST', + supply_type=supply_type, + reverse_charge=invoice.reverse_charge + )) + +def get_doc_details(invoice): + if getdate(invoice.posting_date) < getdate('2021-01-01'): + frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed')) + + invoice_type = 'CRN' if invoice.is_return else 'INV' + + invoice_name = invoice.name + invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') + + return frappe._dict(dict( + invoice_type=invoice_type, + invoice_name=invoice_name, + invoice_date=invoice_date + )) + +def validate_address_fields(address, skip_gstin_validation): + if ((not address.gstin and not skip_gstin_validation) + or not address.city + or not address.pincode + or not address.address_title + or not address.address_line1 + or not address.gst_state_number): + + frappe.throw( + msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name), + title=_('Missing Address Fields') + ) + + if address.address_line2 and len(address.address_line2) < 2: + # to prevent "The field Address 2 must be a string with a minimum length of 3 and a maximum length of 100" + address.address_line2 = "" + +def get_party_details(address_name, skip_gstin_validation=False): + addr = frappe.get_doc('Address', address_name) + + validate_address_fields(addr, skip_gstin_validation) + + if addr.gst_state_number == 97: + # according to einvoice standard + addr.pincode = 999999 + + party_address_details = frappe._dict(dict( + legal_name=sanitize_for_json(addr.address_title), + location=sanitize_for_json(addr.city), + pincode=addr.pincode, gstin=addr.gstin, + state_code=addr.gst_state_number, + address_line1=sanitize_for_json(addr.address_line1), + address_line2=sanitize_for_json(addr.address_line2) + )) + + return party_address_details + +def get_overseas_address_details(address_name): + address_title, address_line1, address_line2, city = frappe.db.get_value( + 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city'] + ) + + if not address_title or not address_line1 or not city: + frappe.throw( + msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format( + get_link_to_form('Address', address_name) + ), + title=_('Missing Address Fields') + ) + + return frappe._dict(dict( + gstin='URP', + legal_name=sanitize_for_json(address_title), + location=city, + address_line1=sanitize_for_json(address_line1), + address_line2=sanitize_for_json(address_line2), + pincode=999999, state_code=96, place_of_supply=96 + )) + +def get_item_list(invoice): + item_list = [] + + for d in invoice.items: + einvoice_item_schema = read_json('einv_item_template') + item = frappe._dict({}) + item.update(d.as_dict()) + + item.sr_no = d.idx + item.description = sanitize_for_json(d.item_name) + + item.qty = abs(item.qty) + if flt(item.qty) != 0.0: + item.unit_rate = abs(item.taxable_value / item.qty) + else: + item.unit_rate = abs(item.taxable_value) + item.gross_amount = abs(item.taxable_value) + item.taxable_value = abs(item.taxable_value) + item.discount_amount = 0 + + item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None + item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None + item.is_service_item = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N' + item.serial_no = "" + + item = update_item_taxes(invoice, item) + + item.total_value = abs( + item.taxable_value + item.igst_amount + item.sgst_amount + + item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges + ) + einv_item = einvoice_item_schema.format(item=item) + item_list.append(einv_item) + + return ', '.join(item_list) + +def update_item_taxes(invoice, item): + gst_accounts = get_gst_accounts(invoice.company) + gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] + + for attr in [ + 'tax_rate', 'cess_rate', 'cess_nadv_amount', + 'cgst_amount', 'sgst_amount', 'igst_amount', + 'cess_amount', 'cess_nadv_amount', 'other_charges' + ]: + item[attr] = 0 + + for t in invoice.taxes: + is_applicable = t.tax_amount and t.account_head in gst_accounts_list + if is_applicable: + # this contains item wise tax rate & tax amount (incl. discount) + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name) + + item_tax_rate = item_tax_detail[0] + # item tax amount excluding discount amount + item_tax_amount = (item_tax_rate / 100) * item.taxable_value + + if t.account_head in gst_accounts.cess_account: + item_tax_amount_after_discount = item_tax_detail[1] + if t.charge_type == 'On Item Quantity': + item.cess_nadv_amount += abs(item_tax_amount_after_discount) + else: + item.cess_rate += item_tax_rate + item.cess_amount += abs(item_tax_amount_after_discount) + + for tax_type in ['igst', 'cgst', 'sgst']: + if t.account_head in gst_accounts[f'{tax_type}_account']: + item.tax_rate += item_tax_rate + item[f'{tax_type}_amount'] += abs(item_tax_amount) + else: + # TODO: other charges per item + pass + + return item + +def get_invoice_value_details(invoice): + invoice_value_details = frappe._dict(dict()) + invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) + invoice_value_details.invoice_discount_amt = 0 + + invoice_value_details.round_off = invoice.base_rounding_adjustment + invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) + invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total) + + invoice_value_details = update_invoice_taxes(invoice, invoice_value_details) + + return invoice_value_details + +def update_invoice_taxes(invoice, invoice_value_details): + gst_accounts = get_gst_accounts(invoice.company) + gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] + + invoice_value_details.total_cgst_amt = 0 + invoice_value_details.total_sgst_amt = 0 + invoice_value_details.total_igst_amt = 0 + invoice_value_details.total_cess_amt = 0 + invoice_value_details.total_other_charges = 0 + considered_rows = [] + + for t in invoice.taxes: + tax_amount = t.base_tax_amount_after_discount_amount + if t.account_head in gst_accounts_list: + if t.account_head in gst_accounts.cess_account: + # using after discount amt since item also uses after discount amt for cess calc + invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) + + for tax_type in ['igst', 'cgst', 'sgst']: + if t.account_head in gst_accounts[f'{tax_type}_account']: + + invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount) + update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows) + else: + invoice_value_details.total_other_charges += abs(tax_amount) + + return invoice_value_details + +def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows): + prev_row_id = cint(tax_row.row_id) - 1 + if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows: + if tax_row.charge_type == 'On Previous Row Amount': + amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount + invoice_value_details.total_other_charges -= abs(amount) + considered_rows.append(prev_row_id) + if tax_row.charge_type == 'On Previous Row Total': + amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total + invoice_value_details.total_other_charges -= abs(amount) + considered_rows.append(prev_row_id) + +def get_payment_details(invoice): + payee_name = invoice.company + mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments]) + paid_amount = invoice.base_paid_amount + outstanding_amount = invoice.outstanding_amount + + return frappe._dict(dict( + payee_name=payee_name, mode_of_payment=mode_of_payment, + paid_amount=paid_amount, outstanding_amount=outstanding_amount + )) + +def get_return_doc_reference(invoice): + invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') + return frappe._dict(dict( + invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') + )) + +def get_eway_bill_details(invoice): + if invoice.is_return: + frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'), + title=_('Invalid Fields')) + + + mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } + vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } + + return frappe._dict(dict( + gstin=invoice.gst_transporter_id, + name=invoice.transporter_name, + mode_of_transport=mode_of_transport[invoice.mode_of_transport], + distance=invoice.distance or 0, + document_name=invoice.lr_no, + document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'), + vehicle_no=invoice.vehicle_no, + vehicle_type=vehicle_type[invoice.gst_vehicle_type] + )) + +def validate_mandatory_fields(invoice): + if not invoice.company_address: + frappe.throw( + _('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'), + title=_('Missing Fields') + ) + if not invoice.customer_address: + frappe.throw( + _('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'), + title=_('Missing Fields') + ) + if not frappe.db.get_value('Address', invoice.company_address, 'gstin'): + frappe.throw( + _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), + title=_('Missing Fields') + ) + if invoice.gst_category != 'Overseas' and not frappe.db.get_value('Address', invoice.customer_address, 'gstin'): + frappe.throw( + _('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'), + title=_('Missing Fields') + ) + +def validate_totals(einvoice): + item_list = einvoice['ItemList'] + value_details = einvoice['ValDtls'] + + total_item_ass_value = 0 + total_item_cgst_value = 0 + total_item_sgst_value = 0 + total_item_igst_value = 0 + total_item_value = 0 + for item in item_list: + total_item_ass_value += flt(item['AssAmt']) + total_item_cgst_value += flt(item['CgstAmt']) + total_item_sgst_value += flt(item['SgstAmt']) + total_item_igst_value += flt(item['IgstAmt']) + total_item_value += flt(item['TotItemVal']) + + if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1: + frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx)) + + if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1: + frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.')) + + if abs(flt(value_details['CgstVal']) + flt(value_details['SgstVal']) - total_item_cgst_value - total_item_sgst_value) > 1: + frappe.throw(_('CGST + SGST value of the items is not equal to total CGST + SGST value. Please review taxes for any correction.')) + + if abs(flt(value_details['IgstVal']) - total_item_igst_value) > 1: + frappe.throw(_('IGST value of all items is not equal to total IGST value. Please review taxes for any correction.')) + + if abs( + flt(value_details['TotInvVal']) + flt(value_details['Discount']) - + flt(value_details['OthChrg']) - flt(value_details['RndOffAmt']) - + total_item_value + ) > 1: + frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.')) + + calculated_invoice_value = \ + flt(value_details['AssVal']) + flt(value_details['CgstVal']) \ + + flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \ + + flt(value_details['OthChrg']) + flt(value_details['RndOffAmt']) - flt(value_details['Discount']) + + if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1: + frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.')) + +def make_einvoice(invoice): + validate_mandatory_fields(invoice) + + schema = read_json('einv_template') + + transaction_details = get_transaction_details(invoice) + item_list = get_item_list(invoice) + doc_details = get_doc_details(invoice) + invoice_value_details = get_invoice_value_details(invoice) + seller_details = get_party_details(invoice.company_address) + + if invoice.gst_category == 'Overseas': + buyer_details = get_overseas_address_details(invoice.customer_address) + else: + buyer_details = get_party_details(invoice.customer_address) + place_of_supply = get_place_of_supply(invoice, invoice.doctype) + if place_of_supply: + place_of_supply = place_of_supply.split('-')[0] + else: + place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2] + buyer_details.update(dict(place_of_supply=place_of_supply)) + + seller_details.update(dict(legal_name=invoice.company)) + buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer)) + + shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) + if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: + if invoice.gst_category == 'Overseas': + shipping_details = get_overseas_address_details(invoice.shipping_address_name) + else: + shipping_details = get_party_details(invoice.shipping_address_name, skip_gstin_validation=True) + + dispatch_details = frappe._dict({}) + if invoice.dispatch_address_name: + dispatch_details = get_party_details(invoice.dispatch_address_name, skip_gstin_validation=True) + + if invoice.is_pos and invoice.base_paid_amount: + payment_details = get_payment_details(invoice) + + if invoice.is_return and invoice.return_against: + prev_doc_details = get_return_doc_reference(invoice) + + if invoice.transporter and not invoice.is_return: + eway_bill_details = get_eway_bill_details(invoice) + + # not yet implemented + period_details = export_details = frappe._dict({}) + + einvoice = schema.format( + transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details, + seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details, + item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details, + period_details=period_details, prev_doc_details=prev_doc_details, + export_details=export_details, eway_bill_details=eway_bill_details + ) + + try: + einvoice = safe_json_load(einvoice) + einvoice = santize_einvoice_fields(einvoice) + except Exception: + show_link_to_error_log(invoice, einvoice) + + try: + validate_totals(einvoice) + except Exception: + log_error(einvoice) + raise + + return einvoice + +def show_link_to_error_log(invoice, einvoice): + err_log = log_error(einvoice) + link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log') + frappe.throw( + _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format( + invoice.name, link_to_error_log), + title=_('E Invoice Creation Failed') + ) + +def log_error(data=None): + if isinstance(data, str): + data = json.loads(data) + + seperator = "--" * 50 + err_tb = traceback.format_exc() + err_msg = str(sys.exc_info()[1]) + data = json.dumps(data, indent=4) + + message = "\n".join([ + "Error", err_msg, seperator, + "Data:", data, seperator, + "Exception:", err_tb + ]) + return frappe.log_error(title=_('E Invoice Request Failed'), message=message) + +def santize_einvoice_fields(einvoice): + int_fields = ["Pin","Distance","CrDay"] + float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",] + copy = einvoice.copy() + for key, value in copy.items(): + if isinstance(value, list): + for idx, d in enumerate(value): + santized_dict = santize_einvoice_fields(d) + if santized_dict: + einvoice[key][idx] = santized_dict + else: + einvoice[key].pop(idx) + + if not einvoice[key]: + einvoice.pop(key, None) + + elif isinstance(value, dict): + santized_dict = santize_einvoice_fields(value) + if santized_dict: + einvoice[key] = santized_dict + else: + einvoice.pop(key, None) + + elif not value or value == "None": + einvoice.pop(key, None) + + elif key in float_fields: + einvoice[key] = flt(value, 2) + + elif key in int_fields: + einvoice[key] = cint(value) + + return einvoice + +def safe_json_load(json_string): + try: + return json.loads(json_string) + except json.JSONDecodeError as e: + # print a snippet of 40 characters around the location where error occured + pos = e.pos + start, end = max(0, pos-20), min(len(json_string)-1, pos+20) + snippet = json_string[start:end] + frappe.throw(_("Error in input data. Please check for any special characters near following input:
{}").format(snippet)) + +class RequestFailed(Exception): + pass +class CancellationNotAllowed(Exception): + pass + +class GSPConnector(): + def __init__(self, doctype=None, docname=None): + self.doctype = doctype + self.docname = docname + + self.set_invoice() + self.set_credentials() + + # authenticate url is same for sandbox & live + self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token' + self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test' + + self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel' + self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' + self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice' + self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin' + self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB' + self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' + + def set_invoice(self): + self.invoice = None + if self.doctype and self.docname: + self.invoice = frappe.get_cached_doc(self.doctype, self.docname) + + def set_credentials(self): + self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') + + if not self.e_invoice_settings.enable: + frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings"))) + + if self.invoice: + gstin = self.get_seller_gstin() + credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin] + if credentials_for_gstin: + self.credentials = credentials_for_gstin[0] + else: + frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings')) + else: + self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None + + def get_seller_gstin(self): + gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin') + if not gstin: + frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.')) + return gstin + + def get_auth_token(self): + if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0: + self.fetch_auth_token() + + return self.e_invoice_settings.auth_token + + def make_request(self, request_type, url, headers=None, data=None): + if request_type == 'post': + res = make_post_request(url, headers=headers, data=data) + else: + res = make_get_request(url, headers=headers, data=data) + + self.log_request(url, headers, data, res) + return res + + def log_request(self, url, headers, data, res): + headers.update({ 'password': self.credentials.password }) + request_log = frappe.get_doc({ + "doctype": "E Invoice Request Log", + "user": frappe.session.user, + "reference_invoice": self.invoice.name if self.invoice else None, + "url": url, + "headers": json.dumps(headers, indent=4) if headers else None, + "data": json.dumps(data, indent=4) if isinstance(data, dict) else data, + "response": json.dumps(res, indent=4) if res else None + }) + request_log.save(ignore_permissions=True) + frappe.db.commit() + + def get_client_credentials(self): + if self.e_invoice_settings.client_id and self.e_invoice_settings.client_secret: + return self.e_invoice_settings.client_id, self.e_invoice_settings.get_password('client_secret') + + return frappe.conf.einvoice_client_id, frappe.conf.einvoice_client_secret + + def fetch_auth_token(self): + client_id, client_secret = self.get_client_credentials() + headers = { + 'gspappid': client_id, + 'gspappsecret': client_secret + } + res = {} + try: + res = self.make_request('post', self.authenticate_url, headers) + self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token')) + self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in')) + self.e_invoice_settings.save(ignore_permissions=True) + self.e_invoice_settings.reload() + + except Exception: + log_error(res) + self.raise_error(True) + + def get_headers(self): + return { + 'content-type': 'application/json', + 'user_name': self.credentials.username, + 'password': self.credentials.get_password(), + 'gstin': self.credentials.gstin, + 'authorization': self.get_auth_token(), + 'requestid': str(base64.b64encode(os.urandom(18))), + } + + def fetch_gstin_details(self, gstin): + headers = self.get_headers() + + try: + params = '?gstin={gstin}'.format(gstin=gstin) + res = self.make_request('get', self.gstin_details_url + params, headers) + if res.get('success'): + return res.get('result') + else: + log_error(res) + raise RequestFailed + + except RequestFailed: + self.raise_error() + + except Exception: + log_error() + self.raise_error(True) + @staticmethod + def get_gstin_details(gstin): + '''fetch and cache GSTIN details''' + if not hasattr(frappe.local, 'gstin_cache'): + frappe.local.gstin_cache = {} + + key = gstin + gsp_connector = GSPConnector() + details = gsp_connector.fetch_gstin_details(gstin) + + frappe.local.gstin_cache[key] = details + frappe.cache().hset('gstin_cache', key, details) + return details + + def generate_irn(self): + data = {} + try: + headers = self.get_headers() + einvoice = make_einvoice(self.invoice) + data = json.dumps(einvoice, indent=4) + res = self.make_request('post', self.generate_irn_url, headers, data) + + if res.get('success'): + self.set_einvoice_data(res.get('result')) + + elif '2150' in res.get('message'): + # IRN already generated but not updated in invoice + # Extract the IRN from the response description and fetch irn details + irn = res.get('result')[0].get('Desc').get('Irn') + irn_details = self.get_irn_details(irn) + if irn_details: + self.set_einvoice_data(irn_details) + else: + raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \ + Contact ERPNext support to resolve the issue.') + + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.set_failed_status(errors=errors) + self.raise_error(errors=errors) + + except Exception as e: + self.set_failed_status(errors=str(e)) + log_error(data) + self.raise_error(True) + + @staticmethod + def bulk_generate_irn(invoices): + gsp_connector = GSPConnector() + gsp_connector.doctype = 'Sales Invoice' + + failed = [] + + for invoice in invoices: + try: + gsp_connector.docname = invoice + gsp_connector.set_invoice() + gsp_connector.set_credentials() + gsp_connector.generate_irn() + + except Exception as e: + failed.append({ + 'docname': invoice, + 'message': str(e) + }) + + return failed + + def get_irn_details(self, irn): + headers = self.get_headers() + + try: + params = '?irn={irn}'.format(irn=irn) + res = self.make_request('get', self.irn_details_url + params, headers) + if res.get('success'): + return res.get('result') + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) + + except Exception: + log_error() + self.raise_error(True) + + def cancel_irn(self, irn, reason, remark): + data, res = {}, {} + try: + # validate cancellation + if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24: + frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed) + if not irn: + frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed) + + headers = self.get_headers() + data = json.dumps({ + 'Irn': irn, + 'Cnlrsn': reason, + 'Cnlrem': remark + }, indent=4) + + res = self.make_request('post', self.cancel_irn_url, headers, data) + if res.get('success') or '9999' in res.get('message'): + self.invoice.irn_cancelled = 1 + self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else "" + self.invoice.einvoice_status = 'Cancelled' + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('IRN Cancelled - {}').format(remark) + } + self.update_invoice() + + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.set_failed_status(errors=errors) + self.raise_error(errors=errors) + + except CancellationNotAllowed as e: + self.set_failed_status(errors=str(e)) + self.raise_error(errors=str(e)) + + except Exception as e: + self.set_failed_status(errors=str(e)) + log_error(data) + self.raise_error(True) + + @staticmethod + def bulk_cancel_irn(invoices, reason, remark): + gsp_connector = GSPConnector() + gsp_connector.doctype = 'Sales Invoice' + + failed = [] + + for invoice in invoices: + try: + gsp_connector.docname = invoice + gsp_connector.set_invoice() + gsp_connector.set_credentials() + irn = gsp_connector.invoice.irn + gsp_connector.cancel_irn(irn, reason, remark) + + except Exception as e: + failed.append({ + 'docname': invoice, + 'message': str(e) + }) + + return failed + + def generate_eway_bill(self, **kwargs): + args = frappe._dict(kwargs) + + headers = self.get_headers() + eway_bill_details = get_eway_bill_details(args) + data = json.dumps({ + 'Irn': args.irn, + 'Distance': cint(eway_bill_details.distance), + 'TransMode': eway_bill_details.mode_of_transport, + 'TransId': eway_bill_details.gstin, + 'TransName': eway_bill_details.transporter, + 'TrnDocDt': eway_bill_details.document_date, + 'TrnDocNo': eway_bill_details.document_name, + 'VehNo': eway_bill_details.vehicle_no, + 'VehType': eway_bill_details.vehicle_type + }, indent=4) + + try: + res = self.make_request('post', self.generate_ewaybill_url, headers, data) + if res.get('success'): + self.invoice.ewaybill = res.get('result').get('EwbNo') + self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill') + self.invoice.eway_bill_cancelled = 0 + self.invoice.update(args) + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('E-Way Bill Generated') + } + self.update_invoice() + + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) + + except Exception: + log_error(data) + self.raise_error(True) + + def cancel_eway_bill(self, eway_bill, reason, remark): + headers = self.get_headers() + data = json.dumps({ + 'ewbNo': eway_bill, + 'cancelRsnCode': reason, + 'cancelRmrk': remark + }, indent=4) + headers["username"] = headers["user_name"] + del headers["user_name"] + try: + res = self.make_request('post', self.cancel_ewaybill_url, headers, data) + if res.get('success'): + self.invoice.ewaybill = '' + self.invoice.eway_bill_cancelled = 1 + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('E-Way Bill Cancelled - {}').format(remark) + } + self.update_invoice() + + else: + raise RequestFailed + + except RequestFailed: + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) + + except Exception: + log_error(data) + self.raise_error(True) + + def sanitize_error_message(self, message): + ''' + On validation errors, response message looks something like this: + message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, + 3095 : Supplier GSTIN is inactive' + we search for string between ':' to extract the error messages + errors = [ + ': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ', + ': Test' + ] + then we trim down the message by looping over errors + ''' + if not message: + return [] + + errors = re.findall(': [^:]+', message) + for idx, e in enumerate(errors): + # remove colons + errors[idx] = errors[idx].replace(':', '').strip() + # if not last + if idx != len(errors) - 1: + # remove last 7 chars eg: ', 3095 ' + errors[idx] = errors[idx][:-6] + + return errors + + def raise_error(self, raise_exception=False, errors=None): + if errors is None: + errors = [] + title = _('E Invoice Request Failed') + if errors: + frappe.throw(errors, title=title, as_list=1) + else: + link_to_error_list = 'Error Log' + frappe.msgprint( + _('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list), + title=title, + raise_exception=raise_exception, + indicator='red' + ) + + def set_einvoice_data(self, res): + enc_signed_invoice = res.get('SignedInvoice') + dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})['data'] + + self.invoice.irn = res.get('Irn') + self.invoice.ewaybill = res.get('EwbNo') + self.invoice.eway_bill_validity = res.get('EwbValidTill') + self.invoice.ack_no = res.get('AckNo') + self.invoice.ack_date = res.get('AckDt') + self.invoice.signed_einvoice = dec_signed_invoice + self.invoice.ack_no = res.get('AckNo') + self.invoice.ack_date = res.get('AckDt') + self.invoice.signed_qr_code = res.get('SignedQRCode') + self.invoice.einvoice_status = 'Generated' + + self.attach_qrcode_image() + + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('IRN Generated') + } + self.update_invoice() + + def attach_qrcode_image(self): + qrcode = self.invoice.signed_qr_code + doctype = self.invoice.doctype + docname = self.invoice.name + filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__") + + qr_image = io.BytesIO() + url = qrcreate(qrcode, error='L') + url.png(qr_image, scale=2, quiet_zone=1) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "attached_to_doctype": doctype, + "attached_to_name": docname, + "attached_to_field": "qrcode_image", + "is_private": 0, + "content": qr_image.getvalue()}) + _file.save() + frappe.db.commit() + self.invoice.qrcode_image = _file.file_url + + def update_invoice(self): + self.invoice.flags.ignore_validate_update_after_submit = True + self.invoice.flags.ignore_validate = True + self.invoice.save() + + def set_failed_status(self, errors=None): + frappe.db.rollback() + self.invoice.einvoice_status = 'Failed' + self.invoice.failure_description = self.get_failure_message(errors) if errors else "" + self.update_invoice() + frappe.db.commit() + + def get_failure_message(self, errors): + if isinstance(errors, list): + errors = ', '.join(errors) + return errors + +def sanitize_for_json(string): + """Escape JSON specific characters from a string.""" + + # json.dumps adds double-quotes to the string. Indexing to remove them. + return json.dumps(string)[1:-1] + +@frappe.whitelist() +def get_einvoice(doctype, docname): + invoice = frappe.get_doc(doctype, docname) + return make_einvoice(invoice) + +@frappe.whitelist() +def generate_irn(doctype, docname): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.generate_irn() + +@frappe.whitelist() +def cancel_irn(doctype, docname, irn, reason, remark): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.cancel_irn(irn, reason, remark) + +@frappe.whitelist() +def generate_eway_bill(doctype, docname, **kwargs): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.generate_eway_bill(**kwargs) + +@frappe.whitelist() +def cancel_eway_bill(doctype, docname): + # TODO: uncomment when eway_bill api from Adequare is enabled + # gsp_connector = GSPConnector(doctype, docname) + # gsp_connector.cancel_eway_bill(eway_bill, reason, remark) + + frappe.db.set_value(doctype, docname, 'ewaybill', '') + frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) + +@frappe.whitelist() +def generate_einvoices(docnames): + docnames = json.loads(docnames) or [] + + if len(docnames) < 10: + failures = GSPConnector.bulk_generate_irn(docnames) + frappe.local.message_log = [] + + if failures: + show_bulk_action_failure_message(failures) + + success = len(docnames) - len(failures) + frappe.msgprint( + _('{} e-invoices generated successfully').format(success), + title=_('Bulk E-Invoice Generation Complete') + ) + + else: + enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames) + +def schedule_bulk_generate_irn(docnames): + failures = GSPConnector.bulk_generate_irn(docnames) + frappe.local.message_log = [] + + frappe.publish_realtime("bulk_einvoice_generation_complete", { + "user": frappe.session.user, + "failures": failures, + "invoices": docnames + }) + +def show_bulk_action_failure_message(failures): + for doc in failures: + docname = '{0}'.format(doc.get('docname')) + message = doc.get('message').replace("'", '"') + if message[0] == '[': + errors = json.loads(message) + error_list = ''.join(['
  • {}
  • '.format(err) for err in errors]) + message = '''{} has following errors:
    + '''.format(docname, error_list) + else: + message = '{} - {}'.format(docname, message) + + frappe.msgprint( + message, + title=_('Bulk E-Invoice Generation Complete'), + indicator='red' + ) + +@frappe.whitelist() +def cancel_irns(docnames, reason, remark): + docnames = json.loads(docnames) or [] + + if len(docnames) < 10: + failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) + frappe.local.message_log = [] + + if failures: + show_bulk_action_failure_message(failures) + + success = len(docnames) - len(failures) + frappe.msgprint( + _('{} e-invoices cancelled successfully').format(success), + title=_('Bulk E-Invoice Cancellation Complete') + ) + else: + enqueue_bulk_action(schedule_bulk_cancel_irn, docnames=docnames, reason=reason, remark=remark) + +def schedule_bulk_cancel_irn(docnames, reason, remark): + failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) + frappe.local.message_log = [] + + frappe.publish_realtime("bulk_einvoice_cancellation_complete", { + "user": frappe.session.user, + "failures": failures, + "invoices": docnames + }) + +def enqueue_bulk_action(job, **kwargs): + check_scheduler_status() + + enqueue( + job, + **kwargs, + queue="long", + timeout=10000, + event="processing_bulk_einvoice_action", + now=frappe.conf.developer_mode or frappe.flags.in_test, + ) + + if job == schedule_bulk_generate_irn: + msg = _('E-Invoices will be generated in a background process.') + else: + msg = _('E-Invoices will be cancelled in a background process.') + + frappe.msgprint(msg, alert=1) + +def check_scheduler_status(): + if is_scheduler_inactive() and not frappe.flags.in_test: + frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive")) + +def job_already_enqueued(job_name): + enqueued_jobs = [d.get("job_name") for d in get_info()] + if job_name in enqueued_jobs: + return True diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index c0dcb70b92..074bd527e2 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -60,7 +60,7 @@ def create_hsn_codes(data, code_field): def add_custom_roles_for_reports(): for report_name in ('GST Sales Register', 'GST Purchase Register', - 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill'): + 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill', 'E-Invoice Summary'): if not frappe.db.get_value('Custom Role', dict(report=report_name)): frappe.get_doc(dict( @@ -99,7 +99,7 @@ def add_custom_roles_for_reports(): )).insert() def add_permissions(): - for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'): + for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate', 'E Invoice Settings'): add_permission(doctype, 'All', 0) for role in ('Accounts Manager', 'Accounts User', 'System Manager'): add_permission(doctype, role, 0) @@ -115,9 +115,11 @@ def add_permissions(): def add_print_formats(): frappe.reload_doc("regional", "print_format", "gst_tax_invoice") frappe.reload_doc("accounts", "print_format", "gst_pos_invoice") + frappe.reload_doc("accounts", "print_format", "GST E-Invoice") frappe.db.set_value("Print Format", "GST POS Invoice", "disabled", 0) frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0) + frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0) def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters @@ -453,7 +455,7 @@ def get_custom_fields(): 'fieldname': 'ewaybill', 'label': 'E-Way Bill No.', 'fieldtype': 'Data', - 'depends_on': 'eval:(doc.docstatus === 1)', + 'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)', 'allow_on_submit': 1, 'insert_after': 'tax_id', 'translatable': 0, @@ -481,6 +483,46 @@ def get_custom_fields(): fetch_from='customer_address.gstin', print_hide=1, read_only=1) ] + si_einvoice_fields = [ + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, + depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval: doc.irn', allow_on_submit=1, insert_after='customer'), + + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'), + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', + print_hide=1, hidden=1), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', + no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + no_copy=1, print_hide=1), + + dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image', + options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON', + hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1) + ] + custom_fields = { 'Address': [ dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data', @@ -493,7 +535,7 @@ def get_custom_fields(): 'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields, 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, - 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, + 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields, 'POS Invoice': sales_invoice_gst_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category, 'Payment Entry': payment_entry_fields, @@ -567,16 +609,16 @@ def get_custom_fields(): fieldtype='Link', options='Salary Component', insert_after='basic_component'), dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'), dict(fieldname='arrear_component', label='Arrear Component', - fieldtype='Link', options='Salary Component', insert_after='hra_component'), + fieldtype='Link', options='Salary Component', insert_after='hra_column_break'), dict(fieldname='non_profit_section', label='Non Profit Settings', - fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1), + fieldtype='Section Break', insert_after='arrear_component', collapsible=1), dict(fieldname='company_80g_number', label='80G Number', fieldtype='Data', insert_after='non_profit_section'), dict(fieldname='with_effect_from', label='80G With Effect From', fieldtype='Date', insert_after='company_80g_number'), dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'), dict(fieldname='pan_details', label='PAN Number', - fieldtype='Data', insert_after='with_effect_from') + fieldtype='Data', insert_after='non_profit_column_break') ], 'Employee Tax Exemption Declaration':[ dict(fieldname='hra_section', label='HRA Exemption', diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 215b483c7a..8715ef57ba 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -215,10 +215,11 @@ def get_regional_address_details(party_details, doctype, company): if tax_template_by_category: party_details['taxes_and_charges'] = tax_template_by_category - return + return party_details if not party_details.place_of_supply: return party_details if not party_details.company_gstin: return party_details + if not party_details.supplier_gstin: return party_details if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice", diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py index 14d5495eed..052fb2a724 100644 --- a/erpnext/regional/report/datev/test_datev.py +++ b/erpnext/regional/report/datev/test_datev.py @@ -1,9 +1,9 @@ import zipfile +from io import BytesIO from unittest import TestCase import frappe from frappe.utils import cstr, now_datetime, today -from six import BytesIO from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.regional.germany.utils.datev.datev_constants import ( diff --git a/erpnext/agriculture/doctype/linked_plant_analysis/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py similarity index 100% rename from erpnext/agriculture/doctype/linked_plant_analysis/__init__.py rename to erpnext/regional/report/e_invoice_summary/__init__.py diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js new file mode 100644 index 0000000000..4713217d83 --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js @@ -0,0 +1,55 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["E-Invoice Summary"] = { + "filters": [ + { + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "fieldname": "company", + "label": __("Company"), + "default": frappe.defaults.get_user_default("Company"), + }, + { + "fieldtype": "Link", + "options": "Customer", + "fieldname": "customer", + "label": __("Customer") + }, + { + "fieldtype": "Date", + "reqd": 1, + "fieldname": "from_date", + "label": __("From Date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldtype": "Date", + "reqd": 1, + "fieldname": "to_date", + "label": __("To Date"), + "default": frappe.datetime.get_today(), + }, + { + "fieldtype": "Select", + "fieldname": "status", + "label": __("Status"), + "options": "\nPending\nGenerated\nCancelled\nFailed" + } + ], + + "formatter": function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.fieldname == "einvoice_status" && value) { + if (value == 'Pending') value = `${value}`; + else if (value == 'Generated') value = `${value}`; + else if (value == 'Cancelled') value = `${value}`; + else if (value == 'Failed') value = `${value}`; + } + + return value; + } +}; diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json new file mode 100644 index 0000000000..d0000ad50d --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json @@ -0,0 +1,28 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-12 11:23:37.312294", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "Logo", + "modified": "2021-03-13 12:36:48.689413", + "modified_by": "Administrator", + "module": "Regional", + "name": "E-Invoice Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Sales Invoice", + "report_name": "E-Invoice Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Administrator" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py new file mode 100644 index 0000000000..2110c44447 --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py @@ -0,0 +1,110 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ + + +def execute(filters=None): + validate_filters(filters) + + columns = get_columns() + data = get_data(filters) + + return columns, data + +def validate_filters(filters=None): + if filters is None: + filters = {} + filters = frappe._dict(filters) + + if not filters.company: + frappe.throw(_('{} is mandatory for generating E-Invoice Summary Report').format(_('Company')), title=_('Invalid Filter')) + if filters.company: + # validate if company has e-invoicing enabled + pass + if not filters.from_date or not filters.to_date: + frappe.throw(_('From Date & To Date is mandatory for generating E-Invoice Summary Report'), title=_('Invalid Filter')) + if filters.from_date > filters.to_date: + frappe.throw(_('From Date must be before To Date'), title=_('Invalid Filter')) + +def get_data(filters=None): + if filters is None: + filters = {} + query_filters = { + 'posting_date': ['between', [filters.from_date, filters.to_date]], + 'einvoice_status': ['is', 'set'], + 'company': filters.company + } + if filters.customer: + query_filters['customer'] = filters.customer + if filters.status: + query_filters['einvoice_status'] = filters.status + + data = frappe.get_all( + 'Sales Invoice', + filters=query_filters, + fields=[d.get('fieldname') for d in get_columns()] + ) + + return data + +def get_columns(): + return [ + { + "fieldtype": "Date", + "fieldname": "posting_date", + "label": _("Posting Date"), + "width": 0 + }, + { + "fieldtype": "Link", + "fieldname": "name", + "label": _("Sales Invoice"), + "options": "Sales Invoice", + "width": 140 + }, + { + "fieldtype": "Data", + "fieldname": "einvoice_status", + "label": _("Status"), + "width": 100 + }, + { + "fieldtype": "Link", + "fieldname": "customer", + "options": "Customer", + "label": _("Customer") + }, + { + "fieldtype": "Check", + "fieldname": "is_return", + "label": _("Is Return"), + "width": 85 + }, + { + "fieldtype": "Data", + "fieldname": "ack_no", + "label": "Ack. No.", + "width": 145 + }, + { + "fieldtype": "Data", + "fieldname": "ack_date", + "label": "Ack. Date", + "width": 165 + }, + { + "fieldtype": "Data", + "fieldname": "irn", + "label": _("IRN No."), + "width": 250 + }, + { + "fieldtype": "Currency", + "options": "Company:company:default_currency", + "fieldname": "base_grand_total", + "label": _("Grand Total"), + "width": 120 + } + ] diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index ef2bdb6798..4b98978f13 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -53,7 +53,8 @@ frappe.query_reports["GSTR-1"] = { { "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") }, { "value": "CDNR-UNREG", "label": __("Credit/Debit Notes (Unregistered) - 9B") }, { "value": "EXPORT", "label": __("Export Invoice - 6A") }, - { "value": "Advances", "label": __("Tax Liability (Advances Received) - 11A(1), 11A(2)") } + { "value": "Advances", "label": __("Tax Liability (Advances Received) - 11A(1), 11A(2)") }, + { "value": "NIL Rated", "label": __("NIL RATED/EXEMPTED Invoices") } ], "default": "B2B" } diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 11b684d3f6..e50ff18032 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -40,7 +40,8 @@ class Gstr1Report(object): port_code, shipping_bill_number, shipping_bill_date, - reason_for_issuing_document + reason_for_issuing_document, + company_gstin """ def run(self): @@ -62,6 +63,8 @@ class Gstr1Report(object): self.get_b2c_data() elif self.filters.get("type_of_business") == "Advances": self.get_advance_data() + elif self.filters.get("type_of_business") == "NIL Rated": + self.get_nil_rated_invoices() elif self.invoices: for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) @@ -91,6 +94,57 @@ class Gstr1Report(object): row= [key[0], key[1], value[0], value[1]] self.data.append(row) + def get_nil_rated_invoices(self): + nil_exempt_output = [ + { + "description": "Inter-State supplies to registered persons", + "nil_rated": 0.0, + "exempted": 0.0, + "non_gst": 0.0 + }, + { + "description": "Intra-State supplies to registered persons", + "nil_rated": 0.0, + "exempted": 0.0, + "non_gst": 0.0 + }, + { + "description": "Inter-State supplies to unregistered persons", + "nil_rated": 0.0, + "exempted": 0.0, + "non_gst": 0.0 + }, + { + "description": "Intra-State supplies to unregistered persons", + "nil_rated": 0.0, + "exempted": 0.0, + "non_gst": 0.0 + } + ] + + for invoice, details in self.nil_exempt_non_gst.items(): + invoice_detail = self.invoices.get(invoice) + if invoice_detail.get('gst_category') in ("Registered Regular", "Deemed Export", "SEZ"): + if is_inter_state(invoice_detail): + nil_exempt_output[0]["nil_rated"] += details[0] + nil_exempt_output[0]["exempted"] += details[1] + nil_exempt_output[0]["non_gst"] += details[2] + else: + nil_exempt_output[1]["nil_rated"] += details[0] + nil_exempt_output[1]["exempted"] += details[1] + nil_exempt_output[1]["non_gst"] += details[2] + else: + if is_inter_state(invoice_detail): + nil_exempt_output[2]["nil_rated"] += details[0] + nil_exempt_output[2]["exempted"] += details[1] + nil_exempt_output[2]["non_gst"] += details[2] + else: + nil_exempt_output[3]["nil_rated"] += details[0] + nil_exempt_output[3]["exempted"] += details[1] + nil_exempt_output[3]["non_gst"] += details[2] + + self.data = nil_exempt_output + def get_b2c_data(self): b2cs_output = {} @@ -240,10 +294,11 @@ class Gstr1Report(object): def get_invoice_items(self): self.invoice_items = frappe._dict() self.item_tax_rate = frappe._dict() + self.nil_exempt_non_gst = {} items = frappe.db.sql(""" - select item_code, parent, taxable_value, base_net_amount, item_tax_rate - from `tab%s Item` + select item_code, parent, taxable_value, base_net_amount, item_tax_rate, is_nil_exempt, + is_non_gst from `tab%s Item` where parent in (%s) """ % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) @@ -260,6 +315,16 @@ class Gstr1Report(object): tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, []) tax_rate_dict.append(rate) + if d.is_nil_exempt: + self.nil_exempt_non_gst.setdefault(d.parent, [0.0, 0.0, 0.0]) + if item_tax_rate: + self.nil_exempt_non_gst[d.parent][0] += d.get('taxable_value', 0) + else: + self.nil_exempt_non_gst[d.parent][1] += d.get('taxable_value', 0) + elif d.is_non_gst: + self.nil_exempt_non_gst.setdefault(d.parent, [0.0, 0.0, 0.0]) + self.nil_exempt_non_gst[d.parent][2] += d.get('taxable_value', 0) + def get_items_based_on_tax_rate(self): self.tax_details = frappe.db.sql(""" select @@ -322,21 +387,24 @@ class Gstr1Report(object): self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) def get_columns(self): - self.tax_columns = [ - { - "fieldname": "rate", - "label": "Rate", - "fieldtype": "Int", - "width": 60 - }, - { - "fieldname": "taxable_value", - "label": "Taxable Value", - "fieldtype": "Currency", - "width": 100 - } - ] self.other_columns = [] + self.tax_columns = [] + + if self.filters.get("type_of_business") != "NIL Rated": + self.tax_columns = [ + { + "fieldname": "rate", + "label": "Rate", + "fieldtype": "Int", + "width": 60 + }, + { + "fieldname": "taxable_value", + "label": "Taxable Value", + "fieldtype": "Currency", + "width": 100 + } + ] if self.filters.get("type_of_business") == "B2B": self.invoice_columns = [ @@ -705,6 +773,33 @@ class Gstr1Report(object): "width": 100 } ] + elif self.filters.get("type_of_business") == "NIL Rated": + self.invoice_columns = [ + { + "fieldname": "description", + "label": "Description", + "fieldtype": "Data", + "width": 420 + }, + { + "fieldname": "nil_rated", + "label": "Nil Rated", + "fieldtype": "Currency", + "width": 200 + }, + { + "fieldname": "exempted", + "label": "Exempted", + "fieldtype": "Currency", + "width": 200 + }, + { + "fieldname": "non_gst", + "label": "Non GST", + "fieldtype": "Currency", + "width": 200 + } + ] self.columns = self.invoice_columns + self.tax_columns + self.other_columns @@ -768,6 +863,11 @@ def get_json(filters, report_name, data): out = get_advances_json(res, gstin) gst_json["at"] = out + elif filters["type_of_business"] == "NIL Rated": + res = report_data[:-1] + out = get_exempted_json(res) + gst_json["nil"] = out + return { 'report_name': report_name, 'report_type': filters['type_of_business'], @@ -980,6 +1080,36 @@ def get_cdnr_unreg_json(res, gstin): return out +def get_exempted_json(data): + out = { + "inv": [ + { + "sply_ty": "INTRB2B" + }, + { + "sply_ty": "INTRAB2B" + }, + { + "sply_ty": "INTRB2C" + }, + { + "sply_ty": "INTRAB2C" + } + ] + } + + for i, v in enumerate(data): + if data[i].get('nil_rated'): + out['inv'][i]['nil_amt'] = data[i]['nil_rated'] + + if data[i].get('exempted'): + out['inv'][i]['expt_amt'] = data[i]['exempted'] + + if data[i].get('non_gst'): + out['inv'][i]['ngsup_amt'] = data[i]['non_gst'] + + return out + def get_invoice_type_for_cdnr(row): if row.get('gst_category') == 'SEZ': if row.get('export_type') == 'WPAY': @@ -1064,3 +1194,9 @@ def download_json_file(): frappe.response['filecontent'] = data['data'] frappe.response['content_type'] = 'application/json' frappe.response['type'] = 'download' + +def is_inter_state(invoice_detail): + if invoice_detail.place_of_supply.split("-")[0] != invoice_detail.company_gstin[:2]: + return True + else: + return False \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index b41b2b0428..cc26bd7a57 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -20,25 +20,35 @@ def get_columns(): "fieldname": "title", "label": _("Title"), "fieldtype": "Data", - "width": 300 + "width": 300, }, { "fieldname": "amount", "label": _("Amount (SAR)"), "fieldtype": "Currency", + "options": "currency", "width": 150, }, { "fieldname": "adjustment_amount", "label": _("Adjustment (SAR)"), "fieldtype": "Currency", + "options": "currency", "width": 150, }, { "fieldname": "vat_amount", "label": _("VAT Amount (SAR)"), "fieldtype": "Currency", + "options": "currency", "width": 150, + }, + { + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Currency", + "width": 150, + "hidden": 1 } ] @@ -47,6 +57,8 @@ def get_data(filters): # Validate if vat settings exist company = filters.get('company') + company_currency = frappe.get_cached_value('Company', company, "default_currency") + if frappe.db.exists('KSA VAT Setting', company) is None: url = get_url_to_list('KSA VAT Setting') frappe.msgprint(_('Create KSA VAT Setting for this company').format(url)) @@ -55,7 +67,7 @@ def get_data(filters): ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) # Sales Heading - append_data(data, 'VAT on Sales', '', '', '') + append_data(data, 'VAT on Sales', '', '', '', company_currency) grand_total_taxable_amount = 0 grand_total_taxable_adjustment_amount = 0 @@ -67,7 +79,7 @@ def get_data(filters): # Adding results to data append_data(data, vat_setting.title, total_taxable_amount, - total_taxable_adjustment_amount, total_tax) + total_taxable_adjustment_amount, total_tax, company_currency) grand_total_taxable_amount += total_taxable_amount grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount @@ -75,13 +87,13 @@ def get_data(filters): # Sales Grand Total append_data(data, 'Grand Total', grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, grand_total_tax) + grand_total_taxable_adjustment_amount, grand_total_tax, company_currency) # Blank Line - append_data(data, '', '', '', '') + append_data(data, '', '', '', '', company_currency) # Purchase Heading - append_data(data, 'VAT on Purchases', '', '', '') + append_data(data, 'VAT on Purchases', '', '', '', company_currency) grand_total_taxable_amount = 0 grand_total_taxable_adjustment_amount = 0 @@ -93,7 +105,7 @@ def get_data(filters): # Adding results to data append_data(data, vat_setting.title, total_taxable_amount, - total_taxable_adjustment_amount, total_tax) + total_taxable_adjustment_amount, total_tax, company_currency) grand_total_taxable_amount += total_taxable_amount grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount @@ -101,7 +113,7 @@ def get_data(filters): # Purchase Grand Total append_data(data, 'Grand Total', grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, grand_total_tax) + grand_total_taxable_adjustment_amount, grand_total_tax, company_currency) return data @@ -147,9 +159,10 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): -def append_data(data, title, amount, adjustment_amount, vat_amount): +def append_data(data, title, amount, adjustment_amount, vat_amount, company_currency): """Returns data with appended value.""" - data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount, + "currency": company_currency}) def get_tax_amount(item_code, account_head, doctype, parent): if doctype == 'Sales Invoice': diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 2e31c03d5c..15d524d5b8 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -3,12 +3,10 @@ import frappe from frappe.permissions import add_permission, update_permission_property -from erpnext.regional.united_arab_emirates.setup import make_custom_fields as uae_custom_fields from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def setup(company=None, patch=True): - uae_custom_fields() add_print_formats() add_permissions() make_custom_fields() @@ -40,38 +38,67 @@ def make_custom_fields(): - Company Name in Arabic - Address in Arabic """ + is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated', + fieldtype='Check', fetch_from='item_code.is_zero_rated', insert_after='description', + print_hide=1) + + is_exempt = dict(fieldname='is_exempt', label='Is Exempt', + fieldtype='Check', fetch_from='item_code.is_exempt', insert_after='is_zero_rated', + print_hide=1) + + purchase_invoice_fields = [ + dict(fieldname='company_trn', label='Company TRN', + fieldtype='Read Only', insert_after='shipping_address', + fetch_from='company.tax_id', print_hide=1), + dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', + fieldtype='Read Only', insert_after='supplier_name', + fetch_from='supplier.supplier_name_in_arabic', print_hide=1) + ] + + sales_invoice_fields = [ + dict(fieldname='company_trn', label='Company TRN', + fieldtype='Read Only', insert_after='company_address', + fetch_from='company.tax_id', print_hide=1), + dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', + fieldtype='Read Only', insert_after='customer_name', + fetch_from='customer.customer_name_in_arabic', print_hide=1), + dict(fieldname='ksa_einv_qr', label='KSA E-Invoicing QR', + fieldtype='Attach Image', read_only=1, no_copy=1, hidden=1) + ] + custom_fields = { - 'Sales Invoice': [ - dict( - fieldname='ksa_einv_qr', - label='KSA E-Invoicing QR', - fieldtype='Attach Image', - read_only=1, no_copy=1, hidden=1 - ) + 'Item': [is_zero_rated, is_exempt], + 'Customer': [ + dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', + fieldtype='Data', insert_after='customer_name'), ], - 'POS Invoice': [ - dict( - fieldname='ksa_einv_qr', - label='KSA E-Invoicing QR', - fieldtype='Attach Image', - read_only=1, no_copy=1, hidden=1 - ) + 'Supplier': [ + dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', + fieldtype='Data', insert_after='supplier_name'), ], + 'Purchase Invoice': purchase_invoice_fields, + 'Purchase Order': purchase_invoice_fields, + 'Purchase Receipt': purchase_invoice_fields, + 'Sales Invoice': sales_invoice_fields, + 'POS Invoice': sales_invoice_fields, + 'Sales Order': sales_invoice_fields, + 'Delivery Note': sales_invoice_fields, + 'Sales Invoice Item': [is_zero_rated, is_exempt], + 'POS Invoice Item': [is_zero_rated, is_exempt], + 'Purchase Invoice Item': [is_zero_rated, is_exempt], + 'Sales Order Item': [is_zero_rated, is_exempt], + 'Delivery Note Item': [is_zero_rated, is_exempt], + 'Quotation Item': [is_zero_rated, is_exempt], + 'Purchase Order Item': [is_zero_rated, is_exempt], + 'Purchase Receipt Item': [is_zero_rated, is_exempt], + 'Supplier Quotation Item': [is_zero_rated, is_exempt], 'Address': [ - dict( - fieldname='address_in_arabic', - label='Address in Arabic', - fieldtype='Data', - insert_after='address_line2' - ) + dict(fieldname='address_in_arabic', label='Address in Arabic', + fieldtype='Data',insert_after='address_line2') ], 'Company': [ - dict( - fieldname='company_name_in_arabic', - label='Company Name In Arabic', - fieldtype='Data', - insert_after='company_name' - ) + dict(fieldname='company_name_in_arabic', label='Company Name In Arabic', + fieldtype='Data', insert_after='company_name') ] } diff --git a/erpnext/restaurant/__init__.py b/erpnext/restaurant/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/restaurant/doctype/__init__.py b/erpnext/restaurant/doctype/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/restaurant/doctype/restaurant/__init__.py b/erpnext/restaurant/doctype/restaurant/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.js b/erpnext/restaurant/doctype/restaurant/restaurant.js deleted file mode 100644 index 13fda73922..0000000000 --- a/erpnext/restaurant/doctype/restaurant/restaurant.js +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Restaurant', { - refresh: function(frm) { - frm.add_custom_button(__('Order Entry'), () => { - frappe.set_route('Form', 'Restaurant Order Entry'); - }); - } -}); diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.json b/erpnext/restaurant/doctype/restaurant/restaurant.json deleted file mode 100644 index 8572687411..0000000000 --- a/erpnext/restaurant/doctype/restaurant/restaurant.json +++ /dev/null @@ -1,309 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "prompt", - "beta": 1, - "creation": "2017-09-15 12:40:41.546933", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "image", - "fieldtype": "Attach Image", - "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": "Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "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, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "invoice_series_prefix", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Invoice Series Prefix", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "active_menu", - "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": "Active Menu", - "length": 0, - "no_copy": 0, - "options": "Restaurant Menu", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_tax_template", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Tax Template", - "length": 0, - "no_copy": 0, - "options": "Sales Taxes and Charges Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address", - "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": "Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_field": "image", - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-12-09 12:13:10.185496", - "modified_by": "Administrator", - "module": "Restaurant", - "name": "Restaurant", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 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, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.py b/erpnext/restaurant/doctype/restaurant/restaurant.py deleted file mode 100644 index 67838d29a3..0000000000 --- a/erpnext/restaurant/doctype/restaurant/restaurant.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class Restaurant(Document): - pass diff --git a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py b/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py deleted file mode 100644 index bfdd052753..0000000000 --- a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py +++ /dev/null @@ -1,17 +0,0 @@ -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'restaurant', - 'transactions': [ - { - 'label': _('Setup'), - 'items': ['Restaurant Menu', 'Restaurant Table'] - }, - { - 'label': _('Operations'), - 'items': ['Restaurant Reservation', 'Sales Invoice'] - } - ] - } diff --git a/erpnext/restaurant/doctype/restaurant/test_restaurant.py b/erpnext/restaurant/doctype/restaurant/test_restaurant.py deleted file mode 100644 index f88f980129..0000000000 --- a/erpnext/restaurant/doctype/restaurant/test_restaurant.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -test_records = [ - dict(doctype='Restaurant', name='Test Restaurant 1', company='_Test Company 1', - invoice_series_prefix='Test-Rest-1-Inv-', default_customer='_Test Customer 1'), - dict(doctype='Restaurant', name='Test Restaurant 2', company='_Test Company 1', - invoice_series_prefix='Test-Rest-2-Inv-', default_customer='_Test Customer 1'), -] - -class TestRestaurant(unittest.TestCase): - pass diff --git a/erpnext/restaurant/doctype/restaurant_menu/__init__.py b/erpnext/restaurant/doctype/restaurant_menu/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.js b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.js deleted file mode 100644 index da7d43f8a3..0000000000 --- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Restaurant Menu', { - setup: function(frm) { - frm.add_fetch('item', 'standard_rate', 'rate'); - }, -}); diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json deleted file mode 100644 index 1b1610dbac..0000000000 --- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json +++ /dev/null @@ -1,247 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "prompt", - "beta": 1, - "creation": "2017-09-15 12:48:29.818715", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "restaurant", - "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": "Restaurant", - "length": 0, - "no_copy": 0, - "options": "Restaurant", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "enabled", - "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": "Enabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "price_list", - "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": "Price List (Auto created)", - "length": 0, - "no_copy": 0, - "options": "Price List", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items", - "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": "Items", - "length": 0, - "no_copy": 0, - "options": "Restaurant Menu Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-12-09 12:13:13.684500", - "modified_by": "Administrator", - "module": "Restaurant", - "name": "Restaurant Menu", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 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": "Restaurant Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py deleted file mode 100644 index 64eb40f364..0000000000 --- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe.model.document import Document - - -class RestaurantMenu(Document): - def validate(self): - for d in self.items: - if not d.rate: - d.rate = frappe.db.get_value('Item', d.item, 'standard_rate') - - def on_update(self): - '''Sync Price List''' - self.make_price_list() - - def on_trash(self): - '''clear prices''' - self.clear_item_price() - - def clear_item_price(self, price_list=None): - '''clear all item prices for this menu''' - if not price_list: - price_list = self.get_price_list().name - frappe.db.sql('delete from `tabItem Price` where price_list = %s', price_list) - - def make_price_list(self): - # create price list for menu - price_list = self.get_price_list() - self.db_set('price_list', price_list.name) - - # delete old items - self.clear_item_price(price_list.name) - - for d in self.items: - frappe.get_doc(dict( - doctype = 'Item Price', - price_list = price_list.name, - item_code = d.item, - price_list_rate = d.rate - )).insert() - - def get_price_list(self): - '''Create price list for menu if missing''' - price_list_name = frappe.db.get_value('Price List', dict(restaurant_menu=self.name)) - if price_list_name: - price_list = frappe.get_doc('Price List', price_list_name) - else: - price_list = frappe.new_doc('Price List') - price_list.restaurant_menu = self.name - price_list.price_list_name = self.name - - price_list.enabled = 1 - price_list.selling = 1 - price_list.save() - - return price_list diff --git a/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py b/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py deleted file mode 100644 index 27020eb869..0000000000 --- a/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe - -test_records = [ - dict(doctype='Item', item_code='Food Item 1', - item_group='Products', is_stock_item=0), - dict(doctype='Item', item_code='Food Item 2', - item_group='Products', is_stock_item=0), - dict(doctype='Item', item_code='Food Item 3', - item_group='Products', is_stock_item=0), - dict(doctype='Item', item_code='Food Item 4', - item_group='Products', is_stock_item=0), - dict(doctype='Restaurant Menu', restaurant='Test Restaurant 1', name='Test Restaurant 1 Menu 1', - items = [ - dict(item='Food Item 1', rate=400), - dict(item='Food Item 2', rate=300), - dict(item='Food Item 3', rate=200), - dict(item='Food Item 4', rate=100), - ]), - dict(doctype='Restaurant Menu', restaurant='Test Restaurant 1', name='Test Restaurant 1 Menu 2', - items = [ - dict(item='Food Item 1', rate=450), - dict(item='Food Item 2', rate=350), - ]) -] - -class TestRestaurantMenu(unittest.TestCase): - def test_price_list_creation_and_editing(self): - menu1 = frappe.get_doc('Restaurant Menu', 'Test Restaurant 1 Menu 1') - menu1.save() - - menu2 = frappe.get_doc('Restaurant Menu', 'Test Restaurant 1 Menu 2') - menu2.save() - - self.assertTrue(frappe.db.get_value('Price List', 'Test Restaurant 1 Menu 1')) - self.assertEqual(frappe.db.get_value('Item Price', - dict(price_list = 'Test Restaurant 1 Menu 1', item_code='Food Item 1'), 'price_list_rate'), 400) - self.assertEqual(frappe.db.get_value('Item Price', - dict(price_list = 'Test Restaurant 1 Menu 2', item_code='Food Item 1'), 'price_list_rate'), 450) - - menu1.items[0].rate = 401 - menu1.save() - - self.assertEqual(frappe.db.get_value('Item Price', - dict(price_list = 'Test Restaurant 1 Menu 1', item_code='Food Item 1'), 'price_list_rate'), 401) - - menu1.items[0].rate = 400 - menu1.save() diff --git a/erpnext/restaurant/doctype/restaurant_menu_item/__init__.py b/erpnext/restaurant/doctype/restaurant_menu_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.json b/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.json deleted file mode 100644 index 87568bf981..0000000000 --- a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2017-09-15 12:49:36.072636", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-09-15 14:18:55.145088", - "modified_by": "Administrator", - "module": "Restaurant", - "name": "Restaurant Menu Item", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py b/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py deleted file mode 100644 index 98b245edec..0000000000 --- a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class RestaurantMenuItem(Document): - pass diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/__init__.py b/erpnext/restaurant/doctype/restaurant_order_entry/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js deleted file mode 100644 index 8df851c62b..0000000000 --- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Restaurant Order Entry', { - setup: function(frm) { - let get_item_query = () => { - return { - query: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.item_query_restaurant', - filters: { - 'table': frm.doc.restaurant_table - } - }; - }; - frm.set_query('item', 'items', get_item_query); - frm.set_query('add_item', get_item_query); - }, - onload_post_render: function(frm) { - if(!frm.item_selector) { - frm.item_selector = new erpnext.ItemSelector({ - frm: frm, - item_field: 'item', - item_query: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.item_query_restaurant', - get_filters: () => { - return {table: frm.doc.restaurant_table}; - } - }); - } - - let $input = frm.get_field('add_item').$input; - - $input.on('keyup', function(e) { - if (e.which===13) { - if (frm.clear_item_timeout) { - clearTimeout (frm.clear_item_timeout); - } - - // clear the item input so user can enter a new item - frm.clear_item_timeout = setTimeout (() => { - frm.set_value('add_item', ''); - }, 1000); - - let item = $input.val(); - - if (!item) return; - - var added = false; - (frm.doc.items || []).forEach((d) => { - if (d.item===item) { - d.qty += 1; - added = true; - } - }); - - return frappe.run_serially([ - () => { - if (!added) { - return frm.add_child('items', {item: item, qty: 1}); - } - }, - () => frm.get_field("items").refresh() - ]); - } - }); - }, - refresh: function(frm) { - frm.disable_save(); - frm.add_custom_button(__('Update'), () => { - return frm.trigger('sync'); - }); - frm.add_custom_button(__('Clear'), () => { - return frm.trigger('clear'); - }); - frm.add_custom_button(__('Bill'), () => { - return frm.trigger('make_invoice'); - }); - }, - clear: function(frm) { - frm.doc.add_item = ''; - frm.doc.grand_total = 0; - frm.doc.items = []; - frm.refresh(); - frm.get_field('add_item').$input.focus(); - }, - restaurant_table: function(frm) { - // select the open sales order items for this table - if (!frm.doc.restaurant_table) { - return; - } - return frappe.call({ - method: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.get_invoice', - args: { - table: frm.doc.restaurant_table - }, - callback: (r) => { - frm.events.set_invoice_items(frm, r); - } - }); - }, - sync: function(frm) { - return frappe.call({ - method: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.sync', - args: { - table: frm.doc.restaurant_table, - items: frm.doc.items - }, - callback: (r) => { - frm.events.set_invoice_items(frm, r); - frappe.show_alert({message: __('Saved'), indicator: 'green'}); - } - }); - - }, - make_invoice: function(frm) { - frm.trigger('sync').then(() => { - frappe.prompt([ - { - fieldname: 'customer', - label: __('Customer'), - fieldtype: 'Link', - reqd: 1, - options: 'Customer', - 'default': frm.invoice.customer - }, - { - fieldname: 'mode_of_payment', - label: __('Mode of Payment'), - fieldtype: 'Link', - reqd: 1, - options: 'Mode of Payment', - 'default': frm.mode_of_payment || '' - } - ], (data) => { - // cache this for next entry - frm.mode_of_payment = data.mode_of_payment; - return frappe.call({ - method: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.make_invoice', - args: { - table: frm.doc.restaurant_table, - customer: data.customer, - mode_of_payment: data.mode_of_payment - }, - callback: (r) => { - frm.set_value('last_sales_invoice', r.message); - frm.trigger('clear'); - } - }); - }, - __("Select Customer")); - }); - }, - set_invoice_items: function(frm, r) { - let invoice = r.message; - frm.doc.items = []; - (invoice.items || []).forEach((d) => { - frm.add_child('items', {item: d.item_code, qty: d.qty, rate: d.rate}); - }); - frm.set_value('grand_total', invoice.grand_total); - frm.set_value('last_sales_invoice', invoice.name); - frm.invoice = invoice; - frm.refresh(); - } -}); diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.json b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.json deleted file mode 100644 index 3e4d593d5b..0000000000 --- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.json +++ /dev/null @@ -1,280 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2017-09-15 15:10:24.530365", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "restaurant_table", - "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": "Restaurant Table", - "length": 0, - "no_copy": 0, - "options": "Restaurant Table", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "restaurant_table", - "description": "Click Enter To Add", - "fieldname": "add_item", - "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": "Add Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "grand_total", - "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": "Grand Total", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "last_sales_invoice", - "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": "Last Sales Invoice", - "length": 0, - "no_copy": 0, - "options": "Sales Invoice", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "restaurant_table", - "fieldname": "current_order", - "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": "Current Order", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "restaurant_table", - "fieldname": "items", - "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": "Items", - "length": 0, - "no_copy": 0, - "options": "Restaurant Order Entry Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2017-10-04 17:06:20.926999", - "modified_by": "Administrator", - "module": "Restaurant", - "name": "Restaurant Order Entry", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Restaurant Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py deleted file mode 100644 index f9e75b47a0..0000000000 --- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import json - -import frappe -from frappe import _ -from frappe.model.document import Document - -from erpnext.controllers.queries import item_query - - -class RestaurantOrderEntry(Document): - pass - -@frappe.whitelist() -def get_invoice(table): - '''returns the active invoice linked to the given table''' - invoice_name = frappe.get_value('Sales Invoice', dict(restaurant_table = table, docstatus=0)) - restaurant, menu_name = get_restaurant_and_menu_name(table) - if invoice_name: - invoice = frappe.get_doc('Sales Invoice', invoice_name) - else: - invoice = frappe.new_doc('Sales Invoice') - invoice.naming_series = frappe.db.get_value('Restaurant', restaurant, 'invoice_series_prefix') - invoice.is_pos = 1 - default_customer = frappe.db.get_value('Restaurant', restaurant, 'default_customer') - if not default_customer: - frappe.throw(_('Please set default customer in Restaurant Settings')) - invoice.customer = default_customer - - invoice.taxes_and_charges = frappe.db.get_value('Restaurant', restaurant, 'default_tax_template') - invoice.selling_price_list = frappe.db.get_value('Price List', dict(restaurant_menu=menu_name, enabled=1)) - - return invoice - -@frappe.whitelist() -def sync(table, items): - '''Sync the sales order related to the table''' - invoice = get_invoice(table) - items = json.loads(items) - - invoice.items = [] - invoice.restaurant_table = table - for d in items: - invoice.append('items', dict( - item_code = d.get('item'), - qty = d.get('qty') - )) - - invoice.save() - return invoice.as_dict() - -@frappe.whitelist() -def make_invoice(table, customer, mode_of_payment): - '''Make table based on Sales Order''' - restaurant, menu = get_restaurant_and_menu_name(table) - invoice = get_invoice(table) - invoice.customer = customer - invoice.restaurant = restaurant - invoice.calculate_taxes_and_totals() - invoice.append('payments', dict(mode_of_payment=mode_of_payment, amount=invoice.grand_total)) - invoice.save() - invoice.submit() - - frappe.msgprint(_('Invoice Created'), indicator='green', alert=True) - - return invoice.name - -@frappe.whitelist() -def item_query_restaurant(doctype='Item', txt='', searchfield='name', start=0, page_len=20, filters=None, as_dict=False): - '''Return items that are selected in active menu of the restaurant''' - restaurant, menu = get_restaurant_and_menu_name(filters['table']) - items = frappe.db.get_all('Restaurant Menu Item', ['item'], dict(parent = menu)) - del filters['table'] - filters['name'] = ('in', [d.item for d in items]) - - return item_query('Item', txt, searchfield, start, page_len, filters, as_dict) - -def get_restaurant_and_menu_name(table): - if not table: - frappe.throw(_('Please select a table')) - - restaurant = frappe.db.get_value('Restaurant Table', table, 'restaurant') - menu = frappe.db.get_value('Restaurant', restaurant, 'active_menu') - - if not menu: - frappe.throw(_('Please set an active menu for Restaurant {0}').format(restaurant)) - - return restaurant, menu diff --git a/erpnext/restaurant/doctype/restaurant_order_entry_item/__init__.py b/erpnext/restaurant/doctype/restaurant_order_entry_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.json b/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.json deleted file mode 100644 index 0240013c78..0000000000 --- a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-09-15 15:11:50.313241", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "served", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Served", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-09-21 08:39:27.232175", - "modified_by": "Administrator", - "module": "Restaurant", - "name": "Restaurant Order Entry Item", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py b/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py deleted file mode 100644 index 0d9c236c0e..0000000000 --- a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class RestaurantOrderEntryItem(Document): - pass diff --git a/erpnext/restaurant/doctype/restaurant_reservation/__init__.py b/erpnext/restaurant/doctype/restaurant_reservation/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.js b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.js deleted file mode 100644 index cebd1052a8..0000000000 --- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.js +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Restaurant Reservation', { - setup: function(frm) { - frm.add_fetch('customer', 'customer_name', 'customer_name'); - }, - refresh: function(frm) { - - } -}); diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json deleted file mode 100644 index 17df2b931d..0000000000 --- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json +++ /dev/null @@ -1,355 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "RES-RES-.YYYY.-.#####", - "beta": 1, - "creation": "2017-09-15 13:05:51.063661", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "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": "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": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Open\nWaitlisted\nCancelled\nNo Show\nSuccess", - "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": "restaurant", - "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": "Restaurant", - "length": 0, - "no_copy": 0, - "options": "Restaurant", - "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": "no_of_people", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "No of People", - "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": "reservation_time", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reservation Time", - "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, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reservation_end_time", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reservation End Time", - "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_4", - "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": "customer", - "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": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "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": "customer_name", - "fieldtype": "Data", - "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": "Customer Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_number", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Contact Number", - "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": "2018-08-21 16:15:38.435656", - "modified_by": "Administrator", - "module": "Restaurant", - "name": "Restaurant Reservation", - "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": "Restaurant Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "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/restaurant/doctype/restaurant_reservation/restaurant_reservation.py b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py deleted file mode 100644 index 02ffaf6c20..0000000000 --- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from datetime import timedelta - -from frappe.model.document import Document -from frappe.utils import get_datetime - - -class RestaurantReservation(Document): - def validate(self): - if not self.reservation_end_time: - self.reservation_end_time = get_datetime(self.reservation_time) + timedelta(hours=1) diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation_calendar.js b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation_calendar.js deleted file mode 100644 index fe3dc57a72..0000000000 --- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation_calendar.js +++ /dev/null @@ -1,18 +0,0 @@ -frappe.views.calendar["Restaurant Reservation"] = { - field_map: { - "start": "reservation_time", - "end": "reservation_end_time", - "id": "name", - "title": "customer_name", - "allDay": "allDay", - }, - gantt: true, - filters: [ - { - "fieldtype": "Data", - "fieldname": "customer_name", - "label": __("Customer Name") - } - ], - get_events_method: "frappe.desk.calendar.get_events" -}; diff --git a/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py b/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py deleted file mode 100644 index 11a3541bd5..0000000000 --- a/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - - -class TestRestaurantReservation(unittest.TestCase): - pass diff --git a/erpnext/restaurant/doctype/restaurant_table/__init__.py b/erpnext/restaurant/doctype/restaurant_table/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.js b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.js deleted file mode 100644 index a55605c90b..0000000000 --- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Restaurant Table', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json deleted file mode 100644 index 5fc6e62ecb..0000000000 --- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 1, - "creation": "2017-09-15 12:45:24.717355", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "restaurant", - "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": "Restaurant", - "length": 0, - "no_copy": 0, - "options": "Restaurant", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "no_of_seats", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "No of Seats", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "minimum_seating", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Minimum Seating", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-12-09 12:13:24.382345", - "modified_by": "Administrator", - "module": "Restaurant", - "name": "Restaurant Table", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 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": "Restaurant Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Hospitality", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py deleted file mode 100644 index 29f8a1a12b..0000000000 --- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import re - -from frappe.model.document import Document -from frappe.model.naming import make_autoname - - -class RestaurantTable(Document): - def autoname(self): - prefix = re.sub('-+', '-', self.restaurant.replace(' ', '-')) - self.name = make_autoname(prefix + '-.##') diff --git a/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py b/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py deleted file mode 100644 index 00d14d2bb2..0000000000 --- a/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -test_records = [ - dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1), - dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1), - dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1), - dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1), -] - -class TestRestaurantTable(unittest.TestCase): - pass diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index b7f74df105..d74d5a6df9 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -142,7 +142,7 @@ class Customer(TransactionBase): self.update_lead_status() if self.flags.is_new_doc: - self.create_lead_address_contact() + self.link_lead_address_and_contact() self.update_customer_groups() @@ -176,62 +176,24 @@ class Customer(TransactionBase): if self.lead_name: frappe.db.set_value("Lead", self.lead_name, "status", "Converted") - def create_lead_address_contact(self): + def link_lead_address_and_contact(self): if self.lead_name: - # assign lead address to customer (if already not set) - address_names = frappe.get_all('Dynamic Link', filters={ - "parenttype":"Address", - "link_doctype":"Lead", - "link_name":self.lead_name - }, fields=["parent as name"]) + # assign lead address and contact to customer (if already not set) + linked_contacts_and_addresses = frappe.get_all( + "Dynamic Link", + filters=[ + ["parenttype", "in", ["Contact", "Address"]], + ["link_doctype", "=", "Lead"], + ["link_name", "=", self.lead_name], + ], + fields=["parent as name", "parenttype as doctype"], + ) - for address_name in address_names: - address = frappe.get_doc('Address', address_name.get('name')) - if not address.has_link('Customer', self.name): - address.append('links', dict(link_doctype='Customer', link_name=self.name)) - address.save(ignore_permissions=self.flags.ignore_permissions) - - lead = frappe.db.get_value("Lead", self.lead_name, ["company_name", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True) - - if not lead.lead_name: - frappe.throw(_("Please mention the Lead Name in Lead {0}").format(self.lead_name)) - - contact_names = frappe.get_all('Dynamic Link', filters={ - "parenttype":"Contact", - "link_doctype":"Lead", - "link_name":self.lead_name - }, fields=["parent as name"]) - - for contact_name in contact_names: - contact = frappe.get_doc('Contact', contact_name.get('name')) - if not contact.has_link('Customer', self.name): - contact.append('links', dict(link_doctype='Customer', link_name=self.name)) - contact.save(ignore_permissions=self.flags.ignore_permissions) - - if not contact_names: - lead.lead_name = lead.lead_name.lstrip().split(" ") - lead.first_name = lead.lead_name[0] - lead.last_name = " ".join(lead.lead_name[1:]) - - # create contact from lead - contact = frappe.new_doc('Contact') - contact.first_name = lead.first_name - contact.last_name = lead.last_name - contact.gender = lead.gender - contact.salutation = lead.salutation - contact.email_id = lead.email_id - contact.phone = lead.phone - contact.mobile_no = lead.mobile_no - contact.is_primary_contact = 1 - contact.append('links', dict(link_doctype='Customer', link_name=self.name)) - if lead.email_id: - contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1)) - if lead.mobile_no: - contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1)) - contact.flags.ignore_permissions = self.flags.ignore_permissions - contact.autoname() - if not frappe.db.exists("Contact", contact.name): - contact.insert() + for row in linked_contacts_and_addresses: + linked_doc = frappe.get_doc(row.doctype, row.name) + if not linked_doc.has_link('Customer', self.name): + linked_doc.append('links', dict(link_doctype='Customer', link_name=self.name)) + linked_doc.save(ignore_permissions=self.flags.ignore_permissions) def validate_name_with_customer_group(self): if frappe.db.exists("Customer Group", self.name): diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index c4752aebb5..daab6fbb8f 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -8,6 +8,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.utils import flt, getdate, nowdate from erpnext.controllers.selling_controller import SellingController +from erpnext.crm.utils import add_link_in_communication, copy_comments form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -34,6 +35,16 @@ class Quotation(SellingController): from erpnext.stock.doctype.packed_item.packed_item import make_packing_list make_packing_list(self) + def after_insert(self): + if frappe.db.get_single_value("CRM Settings", "carry_forward_communication_and_comments"): + if self.opportunity: + copy_comments("Opportunity", self.opportunity, self) + add_link_in_communication("Opportunity", self.opportunity, self) + + elif self.quotation_to == "Lead" and self.party_name: + copy_comments("Lead", self.party_name, self) + add_link_in_communication("Lead", self.party_name, self) + def validate_valid_till(self): if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date): frappe.throw(_("Valid till date cannot be before transaction date")) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 79e9e17e41..eb98e6c0bf 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -457,12 +457,8 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex make_delivery_note_based_on_delivery_date() { var me = this; - var delivery_dates = []; - $.each(this.frm.doc.items || [], function(i, d) { - if(!delivery_dates.includes(d.delivery_date)) { - delivery_dates.push(d.delivery_date); - } - }); + var delivery_dates = this.frm.doc.items.map(i => i.delivery_date); + delivery_dates = [ ...new Set(delivery_dates) ]; var item_grid = this.frm.fields_dict["items"].grid; if(!item_grid.get_selected().length && delivery_dates.length > 1) { @@ -500,14 +496,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex if(!dates) return; - $.each(dates, function(i, d) { - $.each(item_grid.grid_rows || [], function(j, row) { - if(row.doc.delivery_date == d) { - row.doc.__checked = 1; - } - }); - }) - me.make_delivery_note(); + me.make_delivery_note(dates); dialog.hide(); }); dialog.show(); @@ -516,10 +505,13 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } } - make_delivery_note() { + make_delivery_note(delivery_dates) { frappe.model.open_mapped_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", - frm: this.frm + frm: this.frm, + args: { + delivery_dates + } }) } diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index cc951850a4..0f5b1e3b89 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -565,6 +565,13 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False): } if not skip_item_mapping: + def condition(doc): + # make_mapped_doc sets js `args` into `frappe.flags.args` + if frappe.flags.args and frappe.flags.args.delivery_dates: + if cstr(doc.delivery_date) not in frappe.flags.args.delivery_dates: + return False + return abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1 + mapper["Sales Order Item"] = { "doctype": "Delivery Note Item", "field_map": { @@ -573,7 +580,7 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False): "parent": "against_sales_order", }, "postprocess": update_item, - "condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1 + "condition": condition } target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index fb86e614b6..e1ef63578e 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -16,7 +16,7 @@ class SellingSettings(Document): self.toggle_editable_rate_for_bundle_items() def validate(self): - for key in ["cust_master_name", "campaign_naming_by", "customer_group", "territory", + for key in ["cust_master_name", "customer_group", "territory", "maintain_same_sales_rate", "editable_price_list_rate", "selling_price_list"]: frappe.db.set_default(key, self.get(key, "")) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 79dc7f71ae..56aa24f6ec 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -649,7 +649,7 @@ erpnext.PointOfSale.Controller = class { return; } } else if (available_qty < qty_needed) { - frappe.show_alert({ + frappe.throw({ message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]), indicator: 'orange' }); diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 68a46a97cc..1177615aee 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -119,7 +119,7 @@ erpnext.PointOfSale.ItemSelector = class { `
    ${get_item_image_html()} diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py index 777b02ca66..dd49f1355d 100644 --- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py +++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py @@ -23,19 +23,24 @@ def execute(filters=None): row = [] outstanding_amt = get_customer_outstanding(d.name, filters.get("company"), - ignore_outstanding_sales_order=d.bypass_credit_limit_check_at_sales_order) + ignore_outstanding_sales_order=d.bypass_credit_limit_check) credit_limit = get_credit_limit(d.name, filters.get("company")) bal = flt(credit_limit) - flt(outstanding_amt) if customer_naming_type == "Naming Series": - row = [d.name, d.customer_name, credit_limit, outstanding_amt, bal, - d.bypass_credit_limit_check, d.is_frozen, - d.disabled] + row = [ + d.name, d.customer_name, credit_limit, + outstanding_amt, bal, d.bypass_credit_limit_check, + d.is_frozen, d.disabled + ] else: - row = [d.name, credit_limit, outstanding_amt, bal, - d.bypass_credit_limit_check_at_sales_order, d.is_frozen, d.disabled] + row = [ + d.name, credit_limit, outstanding_amt, bal, + d.bypass_credit_limit_check, d.is_frozen, + d.disabled + ] if credit_limit: data.append(row) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 0c0acc76e3..3e22d0fa8c 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -68,7 +68,8 @@ def get_data(conditions, filters): (soi.billed_amt * IFNULL(so.conversion_rate, 1)) as billed_amount, (soi.base_amount - (soi.billed_amt * IFNULL(so.conversion_rate, 1))) as pending_amount, soi.warehouse as warehouse, - so.company, soi.name + so.company, soi.name, + soi.description as description FROM `tabSales Order` so, (`tabSales Order Item` soi @@ -84,7 +85,7 @@ def get_data(conditions, filters): and so.docstatus = 1 {conditions} GROUP BY soi.name - ORDER BY so.transaction_date ASC + ORDER BY so.transaction_date ASC, soi.item_code ASC """.format(conditions=conditions), filters, as_dict=1) return data @@ -184,6 +185,12 @@ def get_columns(filters): "options": "Item", "width": 100 }) + columns.append({ + "label":_("Description"), + "fieldname": "description", + "fieldtype": "Small Text", + "width": 100 + }) columns.extend([ { diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 540aca234b..16e3847168 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -486,7 +486,7 @@ frappe.ui.form.on(cur_frm.doctype, { "options": "Competitor Detail" }, { - "fieldtype": "Text", + "fieldtype": "Small Text", "label": __("Detailed Reason"), "fieldname": "detailed_reason" }, @@ -499,7 +499,7 @@ frappe.ui.form.on(cur_frm.doctype, { method: 'declare_enquiry_lost', args: { 'lost_reasons_list': values.lost_reason, - 'competitors': values.competitors, + 'competitors': values.competitors ? values.competitors : [], 'detailed_reason': values.detailed_reason }, callback: function(r) { diff --git a/erpnext/selling/workspace/retail/retail.json b/erpnext/selling/workspace/retail/retail.json index a851ace738..5bce3ca648 100644 --- a/erpnext/selling/workspace/retail/retail.json +++ b/erpnext/selling/workspace/retail/retail.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Point Of Sale\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings & Configurations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loyalty Program\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening & Closing\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Point Of Sale\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings & Configurations\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loyalty Program\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening & Closing\",\"col\":4}}]", "creation": "2020-03-02 17:18:32.505616", "docstatus": 0, "doctype": "Workspace", @@ -14,7 +14,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings & Configurations", - "link_count": 0, + "link_count": 2, "onboard": 0, "type": "Card Break" }, @@ -44,7 +44,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loyalty Program", - "link_count": 0, + "link_count": 2, "onboard": 0, "type": "Card Break" }, @@ -74,7 +74,7 @@ "hidden": 0, "is_query_report": 0, "label": "Opening & Closing", - "link_count": 0, + "link_count": 2, "onboard": 0, "type": "Card Break" }, @@ -101,7 +101,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.840989", + "modified": "2022-01-13 18:07:56.711095", "modified_by": "Administrator", "module": "Selling", "name": "Retail", @@ -110,7 +110,7 @@ "public": 1, "restrict_to_domain": "Retail", "roles": [], - "sequence_id": 22, + "sequence_id": 22.0, "shortcuts": [ { "doc_view": "", diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json index db2e6bafd5..a700ad89a3 100644 --- a/erpnext/selling/workspace/selling/selling.json +++ b/erpnext/selling/workspace/selling/selling.json @@ -5,7 +5,7 @@ "label": "Sales Order Trends" } ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Selling\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Sales Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Quick Access\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Selling\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items and Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Selling\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Sales Order Trends\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Quick Access\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Order Analysis\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Selling\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", "creation": "2020-01-28 11:49:12.092882", "docstatus": 0, "doctype": "Workspace", @@ -562,7 +562,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.990703", + "modified": "2022-01-13 17:43:02.778627", "modified_by": "Administrator", "module": "Selling", "name": "Selling", @@ -571,7 +571,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 23, + "sequence_id": 23.0, "shortcuts": [ { "color": "Grey", diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 91f60fbd4e..dd185fc663 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -79,14 +79,11 @@ frappe.ui.form.on("Company", { }, refresh: function(frm) { - if(!frm.doc.__islocal) { - frm.doc.abbr && frm.set_df_property("abbr", "read_only", 1); - frm.set_df_property("parent_company", "read_only", 1); - disbale_coa_fields(frm); - } + frm.toggle_display('address_html', !frm.is_new()); - frm.toggle_display('address_html', !frm.doc.__islocal); - if(!frm.doc.__islocal) { + if (!frm.is_new()) { + frm.doc.abbr && frm.set_df_property("abbr", "read_only", 1); + disbale_coa_fields(frm); frappe.contacts.render_address_and_contact(frm); frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'} @@ -216,6 +213,9 @@ erpnext.company.setup_queries = function(frm) { ["default_payroll_payable_account", {"root_type": "Liability"}], ["round_off_account", {"root_type": "Expense"}], ["write_off_account", {"root_type": "Expense"}], + ["default_deferred_expense_account", {}], + ["default_deferred_revenue_account", {}], + ["default_expense_claim_payable_account", {}], ["default_discount_account", {}], ["discount_allowed_account", {"root_type": "Expense"}], ["discount_received_account", {"root_type": "Income"}], diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index e739739458..0a02bcd6cd 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -47,6 +47,7 @@ class Company(NestedSet): self.validate_perpetual_inventory() self.validate_perpetual_inventory_for_non_stock_items() self.check_country_change() + self.check_parent_changed() self.set_chart_of_accounts() self.validate_parent_company() @@ -130,6 +131,10 @@ class Company(NestedSet): self.name in frappe.local.enable_perpetual_inventory: frappe.local.enable_perpetual_inventory[self.name] = self.enable_perpetual_inventory + if frappe.flags.parent_company_changed: + from frappe.utils.nestedset import rebuild_tree + rebuild_tree("Company", "parent_company") + frappe.clear_cache() def create_default_warehouses(self): @@ -191,7 +196,7 @@ class Company(NestedSet): def check_country_change(self): frappe.flags.country_change = False - if not self.get('__islocal') and \ + if not self.is_new() and \ self.country != frappe.get_cached_value('Company', self.name, 'country'): frappe.flags.country_change = True @@ -396,6 +401,13 @@ class Company(NestedSet): if not frappe.db.get_value('GL Entry', {'company': self.name}): frappe.db.sql("delete from `tabProcess Deferred Accounting` where company=%s", self.name) + def check_parent_changed(self): + frappe.flags.parent_company_changed = False + + if not self.is_new() and \ + self.parent_company != frappe.db.get_value("Company", self.name, "parent_company"): + frappe.flags.parent_company_changed = True + def get_name_with_abbr(name, company): company_abbr = frappe.get_cached_value('Company', company, "abbr") parts = name.split(" - ") diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index 4ee9492738..e175c5435a 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -93,6 +93,61 @@ class TestCompany(unittest.TestCase): frappe.db.sql(""" delete from `tabMode of Payment Account` where company =%s """, (company)) + def test_basic_tree(self, records=None): + min_lft = 1 + max_rgt = frappe.db.sql("select max(rgt) from `tabCompany`")[0][0] + + if not records: + records = test_records[2:] + + for company in records: + lft, rgt, parent_company = frappe.db.get_value("Company", company["company_name"], + ["lft", "rgt", "parent_company"]) + + if parent_company: + parent_lft, parent_rgt = frappe.db.get_value("Company", parent_company, + ["lft", "rgt"]) + else: + # root + parent_lft = min_lft - 1 + parent_rgt = max_rgt + 1 + + self.assertTrue(lft) + self.assertTrue(rgt) + self.assertTrue(lft < rgt) + self.assertTrue(parent_lft < parent_rgt) + self.assertTrue(lft > parent_lft) + self.assertTrue(rgt < parent_rgt) + self.assertTrue(lft >= min_lft) + self.assertTrue(rgt <= max_rgt) + + def get_no_of_children(self, company): + def get_no_of_children(companies, no_of_children): + children = [] + for company in companies: + children += frappe.db.sql_list("""select name from `tabCompany` + where ifnull(parent_company, '')=%s""", company or '') + + if len(children): + return get_no_of_children(children, no_of_children + len(children)) + else: + return no_of_children + + return get_no_of_children([company], 0) + + def test_change_parent_company(self): + child_company = frappe.get_doc("Company", "_Test Company 5") + + # changing parent of company + child_company.parent_company = "_Test Company 3" + child_company.save() + self.test_basic_tree() + + # move it back + child_company.parent_company = "_Test Company 4" + child_company.save() + self.test_basic_tree() + def create_company_communication(doctype, docname): comm = frappe.get_doc({ "doctype": "Communication", diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index 2b007e9efd..06a79b4102 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -62,8 +62,13 @@ def patched_requests_get(*args, **kwargs): if kwargs['params'].get('date') and kwargs['params'].get('from') and kwargs['params'].get('to'): if test_exchange_values.get(kwargs['params']['date']): return PatchResponse({'result': test_exchange_values[kwargs['params']['date']]}, 200) + elif args[0].startswith("https://frankfurter.app") and kwargs.get('params'): + if kwargs['params'].get('base') and kwargs['params'].get('symbols'): + date = args[0].replace("https://frankfurter.app/", "") + if test_exchange_values.get(date): + return PatchResponse({'rates': {kwargs['params'].get('symbols'): test_exchange_values.get(date)}}, 200) - return PatchResponse({'result': None}, 404) + return PatchResponse({'rates': None}, 404) @mock.patch('requests.get', side_effect=patched_requests_get) class TestCurrencyExchange(unittest.TestCase): @@ -102,6 +107,41 @@ class TestCurrencyExchange(unittest.TestCase): self.assertFalse(exchange_rate == 60) self.assertEqual(flt(exchange_rate, 3), 65.1) + def test_exchange_rate_via_exchangerate_host(self, mock_get): + save_new_records(test_records) + + # Update Currency Exchange Rate + settings = frappe.get_single("Currency Exchange Settings") + settings.service_provider = 'exchangerate.host' + settings.save() + + # Update exchange + frappe.db.set_value("Accounts Settings", None, "allow_stale", 1) + + # Start with allow_stale is True + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying") + self.assertEqual(flt(exchange_rate, 3), 60.0) + + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") + self.assertEqual(exchange_rate, 65.1) + + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling") + self.assertEqual(exchange_rate, 62.9) + + # Exchange rate as on 15th Dec, 2015 + self.clear_cache() + exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling") + self.assertFalse(exchange_rate == 60) + self.assertEqual(flt(exchange_rate, 3), 66.999) + + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-20", "for_buying") + self.assertFalse(exchange_rate == 60) + self.assertEqual(flt(exchange_rate, 3), 65.1) + + settings = frappe.get_single("Currency Exchange Settings") + settings.service_provider = 'frankfurter.app' + settings.save() + def test_exchange_rate_strict(self, mock_get): # strict currency settings frappe.db.set_value("Accounts Settings", None, "allow_stale", 0) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index c94b3463fc..9f1eb753d9 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -3,6 +3,7 @@ import copy +from urllib.parse import quote import frappe from frappe import _ @@ -10,7 +11,6 @@ from frappe.utils import cint, cstr, nowdate from frappe.utils.nestedset import NestedSet from frappe.website.utils import clear_cache from frappe.website.website_generator import WebsiteGenerator -from six.moves.urllib.parse import quote from erpnext.shopping_cart.filters import ProductFiltersBuilder from erpnext.shopping_cart.product_info import set_product_info_for_website diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 86c9b3f178..1d7bad2686 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -60,6 +60,22 @@ def set_single_defaults(): frappe.db.set_default("date_format", "dd-mm-yyyy") + setup_currency_exchange() + +def setup_currency_exchange(): + ces = frappe.get_single('Currency Exchange Settings') + try: + ces.set('result_key', []) + ces.set('req_params', []) + + ces.api_endpoint = "https://frankfurter.app/{transaction_date}" + ces.append('result_key', {'key': 'rates'}) + ces.append('result_key', {'key': '{to_currency}'}) + ces.append('req_params', {'key': 'base', 'value': '{from_currency}'}) + ces.append('req_params', {'key': 'symbols', 'value': '{to_currency}'}) + ces.save() + except frappe.ValidationError: + pass def create_compact_item_print_custom_field(): create_custom_field('Print Settings', { @@ -173,7 +189,7 @@ def add_non_standard_user_types(): user_type_limit = {} for user_type, data in user_types.items(): - user_type_limit.setdefault(frappe.scrub(user_type), 10) + user_type_limit.setdefault(frappe.scrub(user_type), 20) update_site_config('user_type_doctype_limit', user_type_limit) @@ -188,15 +204,33 @@ def get_user_types_data(): 'apply_user_permission_on': 'Employee', 'user_id_field': 'user_id', 'doctypes': { - 'Salary Slip': ['read'], + # masters + 'Holiday List': ['read'], 'Employee': ['read', 'write'], + # payroll + 'Salary Slip': ['read'], + 'Employee Benefit Application': ['read', 'write', 'create', 'delete'], + # expenses 'Expense Claim': ['read', 'write', 'create', 'delete'], + 'Employee Advance': ['read', 'write', 'create', 'delete'], + # leave and attendance 'Leave Application': ['read', 'write', 'create', 'delete'], 'Attendance Request': ['read', 'write', 'create', 'delete'], 'Compensatory Leave Request': ['read', 'write', 'create', 'delete'], + # tax 'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'], 'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'], - 'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'] + # projects + 'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'], + # trainings + 'Training Program': ['read'], + 'Training Feedback': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'], + # shifts + 'Shift Request': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'], + # misc + 'Employee Grievance': ['read', 'write', 'create', 'delete'], + 'Employee Referral': ['read', 'write', 'create', 'delete'], + 'Travel Request': ['read', 'write', 'create', 'delete'] } } } diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 97d850ba19..9dbf49eae7 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -33,7 +33,6 @@ def install(country=None): { 'doctype': 'Domain', 'domain': 'Services'}, { 'doctype': 'Domain', 'domain': 'Education'}, { 'doctype': 'Domain', 'domain': 'Healthcare'}, - { 'doctype': 'Domain', 'domain': 'Agriculture'}, { 'doctype': 'Domain', 'domain': 'Non Profit'}, # ensure at least an empty Address Template exists for this Country @@ -354,7 +353,8 @@ def add_uom_data(): "doctype": "UOM", "uom_name": _(d.get("uom_name")), "name": _(d.get("uom_name")), - "must_be_whole_number": d.get("must_be_whole_number") + "must_be_whole_number": d.get("must_be_whole_number"), + "enabled": 1, }).db_insert() # bootstrap uom conversion factors diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index cad4c54d7d..4441bb9562 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -100,15 +100,21 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No if not value: import requests - api_url = "https://api.exchangerate.host/convert" - response = requests.get(api_url, params={ - "date": transaction_date, - "from": from_currency, - "to": to_currency - }) + settings = frappe.get_cached_doc('Currency Exchange Settings') + req_params = { + "transaction_date": transaction_date, + "from_currency": from_currency, + "to_currency": to_currency + } + params = {} + for row in settings.req_params: + params[row.key] = format_ces_api(row.value, req_params) + response = requests.get(format_ces_api(settings.api_endpoint, req_params), params=params) # expire in 6 hours response.raise_for_status() - value = response.json()["result"] + value = response.json() + for res_key in settings.result_key: + value = value[format_ces_api(str(res_key.key), req_params)] cache.setex(name=key, time=21600, value=flt(value)) return flt(value) except Exception: @@ -116,6 +122,13 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually").format(from_currency, to_currency, transaction_date)) return 0.0 +def format_ces_api(data, param): + return data.format( + transaction_date=param.get("transaction_date"), + to_currency=param.get("to_currency"), + from_currency=param.get("from_currency") + ) + def enable_all_roles_and_domains(): """ enable all roles and domain for testing """ # add all roles to users diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index e47837f2ca..c5640bc079 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Manufacturing Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Education Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Hotel Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"CRM Settings\",\"col\":3}}]", "creation": "2020-03-12 14:47:51.166455", "docstatus": 0, "doctype": "Workspace", @@ -10,7 +10,7 @@ "idx": 0, "label": "ERPNext Settings", "links": [], - "modified": "2021-11-05 21:32:55.323591", + "modified": "2022-01-13 19:18:59.362820", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", @@ -19,7 +19,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 12, + "sequence_id": 12.0, "shortcuts": [ { "icon": "project", @@ -104,13 +104,6 @@ "restrict_to_domain": "Hospitality", "type": "DocType" }, - { - "icon": "non-profit", - "label": "Healthcare Settings", - "link_to": "Healthcare Settings", - "restrict_to_domain": "Healthcare", - "type": "DocType" - }, { "icon": "setting", "label": "Domain Settings", diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json index f9c585c015..19ff2a0830 100644 --- a/erpnext/setup/workspace/home/home.json +++ b/erpnext/setup/workspace/home/home.json @@ -1,18 +1,13 @@ { "charts": [], - "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Home\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Home\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]", "creation": "2020-01-23 13:46:38.833076", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "getting-started", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Home", "links": [ { @@ -276,18 +271,16 @@ "type": "Link" } ], - "modified": "2021-11-22 12:50:15.771366", + "modified": "2022-01-13 17:24:17.002665", "modified_by": "Administrator", "module": "Setup", "name": "Home", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 1, + "sequence_id": 1.0, "shortcuts": [ { "label": "Item", diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index fdefd24878..96751d6eae 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -292,6 +292,7 @@ def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None): join `tabStock Ledger Entry` ignore index (item_code, warehouse) on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no ) where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s + and `tabStock Ledger Entry`.is_cancelled = 0 and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0} group by batch_id order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC @@ -312,3 +313,28 @@ def make_batch(args): if frappe.db.get_value("Item", args.item, "has_batch_no"): args.doctype = "Batch" frappe.get_doc(args).insert().name + +@frappe.whitelist() +def get_pos_reserved_batch_qty(filters): + import json + + if isinstance(filters, str): + filters = json.loads(filters) + + p = frappe.qb.DocType("POS Invoice").as_("p") + item = frappe.qb.DocType("POS Invoice Item").as_("item") + sum_qty = frappe.query_builder.functions.Sum(item.qty).as_("qty") + + reserved_batch_qty = frappe.qb.from_(p).from_(item).select(sum_qty).where( + (p.name == item.parent) & + (p.consolidated_invoice.isnull()) & + (p.status != "Consolidated") & + (p.docstatus == 1) & + (item.docstatus == 1) & + (item.item_code == filters.get('item_code')) & + (item.warehouse == filters.get('warehouse')) & + (item.batch_no == filters.get('batch_no')) + ).run() + + flt_reserved_batch_qty = flt(reserved_batch_qty[0][0]) + return flt_reserved_batch_qty diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index 8e79f0e555..56dc71c57e 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -33,6 +33,7 @@ "oldfieldtype": "Link", "options": "Warehouse", "read_only": 1, + "reqd": 1, "search_index": 1 }, { @@ -46,6 +47,7 @@ "oldfieldtype": "Link", "options": "Item", "read_only": 1, + "reqd": 1, "search_index": 1 }, { @@ -169,10 +171,11 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2021-03-30 23:09:39.572776", + "modified": "2022-01-30 17:04:54.715288", "modified_by": "Administrator", "module": "Stock", "name": "Bin", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -200,5 +203,6 @@ "quick_entry": 1, "search_fields": "item_code,warehouse", "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "states": [] } \ No newline at end of file diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 0ef7ce2923..c34e9d05ce 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -123,7 +123,7 @@ class Bin(Document): self.db_set('projected_qty', self.projected_qty) def on_doctype_update(): - frappe.db.add_index("Bin", ["item_code", "warehouse"]) + frappe.db.add_unique("Bin", ["item_code", "warehouse"], constraint_name="unique_item_warehouse") def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_voucher=False): diff --git a/erpnext/stock/doctype/bin/test_bin.py b/erpnext/stock/doctype/bin/test_bin.py index 9c390d94b4..250126c6b9 100644 --- a/erpnext/stock/doctype/bin/test_bin.py +++ b/erpnext/stock/doctype/bin/test_bin.py @@ -1,9 +1,36 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +import frappe -# test_records = frappe.get_test_records('Bin') +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.utils import _create_bin +from erpnext.tests.utils import ERPNextTestCase -class TestBin(unittest.TestCase): - pass + +class TestBin(ERPNextTestCase): + + + def test_concurrent_inserts(self): + """ Ensure no duplicates are possible in case of concurrent inserts""" + item_code = "_TestConcurrentBin" + make_item(item_code) + warehouse = "_Test Warehouse - _TC" + + bin1 = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse) + bin1.insert() + + bin2 = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse) + with self.assertRaises(frappe.UniqueValidationError): + bin2.insert() + + # util method should handle it + bin = _create_bin(item_code, warehouse) + self.assertEqual(bin.item_code, item_code) + + frappe.db.rollback() + + def test_index_exists(self): + indexes = frappe.db.sql("show index from tabBin where Non_unique = 0", as_dict=1) + if not any(index.get("Key_name") == "unique_item_warehouse" for index in indexes): + self.fail(f"Expected unique index on item-warehouse") diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 70d48a42d7..d1e22440b9 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -14,6 +14,7 @@ from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.controllers.selling_controller import SellingController from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no +from erpnext.stock.utils import calculate_mapped_packed_items_return form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -128,8 +129,12 @@ class DeliveryNote(SellingController): self.validate_uom_is_integer("uom", "qty") self.validate_with_previous_doc() - from erpnext.stock.doctype.packed_item.packed_item import make_packing_list - make_packing_list(self) + # Keeps mapped packed_items in case product bundle is updated. + if self.is_return and self.return_against: + calculate_mapped_packed_items_return(self) + else: + from erpnext.stock.doctype.packed_item.packed_item import make_packing_list + make_packing_list(self) if self._action != 'submit' and not self.is_return: set_batch_nos(self, 'warehouse', throw=True) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 4f89a19f3c..bd18e788ba 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -386,8 +386,7 @@ class TestDeliveryNote(ERPNextTestCase): self.assertEqual(actual_qty, 25) # return bundled item - dn1 = create_delivery_note(item_code='_Test Product Bundle Item', is_return=1, - return_against=dn.name, qty=-2, rate=500, company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") + dn1 = create_return_delivery_note(source_name=dn.name, rate=500, qty=-2) # qty after return actual_qty = get_qty_after_transaction(warehouse="Stores - TCP1") @@ -823,6 +822,15 @@ class TestDeliveryNote(ERPNextTestCase): automatically_fetch_payment_terms(enable=0) +def create_return_delivery_note(**args): + args = frappe._dict(args) + from erpnext.controllers.sales_and_purchase_return import make_return_doc + doc = make_return_doc("Delivery Note", args.source_name, None) + doc.items[0].rate = args.rate + doc.items[0].qty = args.qty + doc.submit() + return doc + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") args = frappe._dict(args) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 752a1fe732..86c702c539 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -376,8 +376,7 @@ $.extend(erpnext.item, { // Show Stock Levels only if is_stock_item if (frm.doc.is_stock_item) { frappe.require('item-dashboard.bundle.js', function() { - frm.dashboard.parent.find('.stock-levels').remove(); - const section = frm.dashboard.add_section('', __("Stock Levels"), 'stock-levels'); + const section = frm.dashboard.add_section('', __("Stock Levels")); erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({ parent: section, item_code: frm.doc.name, diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 29abd45fcc..2d28cc09f9 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -28,6 +28,7 @@ "standard_rate", "is_fixed_asset", "auto_create_assets", + "is_grouped_asset", "asset_category", "asset_naming_series", "over_delivery_receipt_allowance", @@ -1026,6 +1027,13 @@ "fieldname": "grant_commission", "fieldtype": "Check", "label": "Grant Commission" + }, + { + "default": "0", + "depends_on": "auto_create_assets", + "fieldname": "is_grouped_asset", + "fieldtype": "Check", + "label": "Create Grouped Asset" } ], "has_web_view": 1, @@ -1034,7 +1042,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-12-14 04:13:16.857534", + "modified": "2022-01-18 12:57:54.273202", "modified_by": "Administrator", "module": "Stock", "name": "Item", @@ -1104,6 +1112,7 @@ "show_preview_popup": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "item_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index decf522d2f..d99fadca46 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -492,18 +492,20 @@ class Item(WebsiteGenerator): context.shopping_cart = get_product_info_for_website(self.name, skip_quotation_creation=True) def add_default_uom_in_conversion_factor_table(self): - uom_conv_list = [d.uom for d in self.get("uoms")] - if self.stock_uom not in uom_conv_list: - ch = self.append('uoms', {}) - ch.uom = self.stock_uom - ch.conversion_factor = 1 + if not self.is_new() and self.has_value_changed("stock_uom"): + self.uoms = [] + frappe.msgprint( + _("Successfully changed Stock UOM, please redefine conversion factors for new UOM."), + alert=True, + ) - to_remove = [] - for d in self.get("uoms"): - if d.conversion_factor == 1 and d.uom != self.stock_uom: - to_remove.append(d) + uoms_list = [d.uom for d in self.get("uoms")] - [self.remove(d) for d in to_remove] + if self.stock_uom not in uoms_list: + self.append("uoms", { + "uom": self.stock_uom, + "conversion_factor": 1 + }) def update_show_in_website(self): if self.disabled: @@ -600,14 +602,6 @@ class Item(WebsiteGenerator): frappe.throw(_("Barcode {0} is not a valid {1} code").format( item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode) - if item_barcode.barcode != item_barcode.name: - # if barcode is getting updated , the row name has to reset. - # Delete previous old row doc and re-enter row as if new to reset name in db. - item_barcode.set("__islocal", True) - item_barcode_entry_name = item_barcode.name - item_barcode.name = None - frappe.delete_doc("Item Barcode", item_barcode_entry_name) - def validate_warehouse_for_reorder(self): '''Validate Reorder level table for duplicate and conditional mandatory''' warehouse = [] diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 4028d93334..0957ce0615 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -584,6 +584,16 @@ class TestItem(ERPNextTestCase): except frappe.ValidationError as e: self.fail(f"UoM change not allowed even though no SLE / BIN with positive qty exists: {e}") + def test_erasure_of_old_conversions(self): + item = create_item("_item change uom") + item.stock_uom = "Gram" + item.append("uoms", frappe._dict(uom="Box", conversion_factor=2)) + item.save() + item.reload() + item.stock_uom = "Nos" + item.save() + self.assertEqual(len(item.uoms), 1) + def test_validate_stock_item(self): self.assertRaises(frappe.ValidationError, validate_is_stock_item, "_Test Non Stock Item") diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index cd7e63b18b..0ba97d59a1 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -1,7 +1,7 @@ { "actions": [], "autoname": "REPOST-ITEM-VAL-.######", - "creation": "2020-10-22 22:27:07.742161", + "creation": "2022-01-11 15:03:38.273179", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -129,7 +129,7 @@ "reqd": 1 }, { - "default": "0", + "default": "1", "fieldname": "allow_negative_stock", "fieldtype": "Check", "label": "Allow Negative Stock" @@ -177,7 +177,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-11-24 02:18:10.524560", + "modified": "2022-01-18 10:57:33.450907", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", @@ -227,5 +227,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" -} + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index fb3b355fb7..01c5e3e4e2 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -27,8 +27,7 @@ class RepostItemValuation(Document): self.item_code = None self.warehouse = None - self.allow_negative_stock = self.allow_negative_stock or \ - cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) + self.allow_negative_stock = 1 def set_company(self): if self.based_on == "Transaction": diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 2947fafe52..ee08e38f33 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -402,10 +402,16 @@ def update_serial_nos(sle, item_det): def get_auto_serial_nos(serial_no_series, qty): serial_nos = [] for i in range(cint(qty)): - serial_nos.append(make_autoname(serial_no_series, "Serial No")) + serial_nos.append(get_new_serial_number(serial_no_series)) return "\n".join(serial_nos) +def get_new_serial_number(series): + sr_no = make_autoname(series, "Serial No") + if frappe.db.exists("Serial No", sr_no): + sr_no = get_new_serial_number(series) + return sr_no + def auto_make_serial_nos(args): serial_nos = get_serial_nos(args.get('serial_no')) created_numbers = [] @@ -459,6 +465,13 @@ def get_serial_nos(serial_no): return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n') if s.strip()] +def clean_serial_no_string(serial_no: str) -> str: + if not serial_no: + return "" + + serial_no_list = get_serial_nos(serial_no) + return "\n".join(serial_no_list) + def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False): for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]: if args.get(field): diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 99000d1201..f8cea71725 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -8,8 +8,10 @@ import frappe from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse @@ -21,6 +23,10 @@ from erpnext.tests.utils import ERPNextTestCase class TestSerialNo(ERPNextTestCase): + + def tearDown(self): + frappe.db.rollback() + def test_cannot_create_direct(self): frappe.delete_doc_if_exists("Serial No", "_TCSER0001") @@ -176,6 +182,24 @@ class TestSerialNo(ERPNextTestCase): self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") self.assertEqual(sn_doc.purchase_document_no, se.name) + def test_auto_creation_of_serial_no(self): + """ + Test if auto created Serial No excludes existing serial numbers + """ + item_code = make_item("_Test Auto Serial Item ", { + "has_serial_no": 1, + "serial_no_series": "XYZ.###" + }).item_code + + # Reserve XYZ005 + pr_1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no="XYZ005") + # XYZ005 is already used and will throw an error if used again + pr_2 = make_purchase_receipt(item_code=item_code, qty=10) + + self.assertEqual(get_serial_nos(pr_1.get("items")[0].serial_no)[0], "XYZ005") + for serial_no in get_serial_nos(pr_2.get("items")[0].serial_no): + self.assertNotEqual(serial_no, "XYZ005") + def test_serial_no_sanitation(self): "Test if Serial No input is sanitised before entering the DB." item_code = "_Test Serialized Item" @@ -192,7 +216,28 @@ class TestSerialNo(ERPNextTestCase): self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3\n_TS4 - 2021") - frappe.db.rollback() + def test_correct_serial_no_incoming_rate(self): + """ Check correct consumption rate based on serial no record. + """ + item_code = "_Test Serialized Item" + warehouse = "_Test Warehouse - _TC" + serial_nos = ["LOWVALUATION", "HIGHVALUATION"] + + in1 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=42, + serial_no=serial_nos[0]) + in2 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=113, + serial_no=serial_nos[1]) + + out = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_nos[0], do_not_submit=True) + + # change serial no + out.items[0].serial_no = serial_nos[1] + out.save() + out.submit() + + value_diff = frappe.db.get_value("Stock Ledger Entry", + {"voucher_no": out.name, "voucher_type": "Delivery Note"}, + "stock_value_difference" + ) + self.assertEqual(value_diff, -113) - def tearDown(self): - frappe.db.rollback() diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 93e303c9a7..c51c9bc5f4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -8,6 +8,7 @@ from collections import defaultdict import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc +from frappe.query_builder.functions import Sum from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate import erpnext @@ -85,8 +86,11 @@ class StockEntry(StockController): self.validate_warehouse() self.validate_work_order() self.validate_bom() - self.mark_finished_and_scrap_items() - self.validate_finished_goods() + + if self.purpose in ("Manufacture", "Repack"): + self.mark_finished_and_scrap_items() + self.validate_finished_goods() + self.validate_with_material_request() self.validate_batch() self.validate_inspection() @@ -109,8 +113,12 @@ class StockEntry(StockController): self.set_actual_qty() self.calculate_rate_and_amount() self.validate_putaway_capacity() - self.reset_default_field_value("from_warehouse", "items", "s_warehouse") - self.reset_default_field_value("to_warehouse", "items", "t_warehouse") + + if not self.get("purpose") == "Manufacture": + # ignore scrap item wh difference and empty source/target wh + # in Manufacture Entry + self.reset_default_field_value("from_warehouse", "items", "s_warehouse") + self.reset_default_field_value("to_warehouse", "items", "t_warehouse") def on_submit(self): self.update_stock_ledger() @@ -701,26 +709,25 @@ class StockEntry(StockController): validate_bom_no(item_code, d.bom_no) def mark_finished_and_scrap_items(self): - if self.purpose in ("Repack", "Manufacture"): - if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]): - return + if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]): + return - finished_item = self.get_finished_item() + finished_item = self.get_finished_item() - if not finished_item and self.purpose == "Manufacture": - # In case of independent Manufacture entry, don't auto set - # user must decide and set - return + if not finished_item and self.purpose == "Manufacture": + # In case of independent Manufacture entry, don't auto set + # user must decide and set + return - for d in self.items: - if d.t_warehouse and not d.s_warehouse: - if self.purpose=="Repack" or d.item_code == finished_item: - d.is_finished_item = 1 - else: - d.is_scrap_item = 1 + for d in self.items: + if d.t_warehouse and not d.s_warehouse: + if self.purpose=="Repack" or d.item_code == finished_item: + d.is_finished_item = 1 else: - d.is_finished_item = 0 - d.is_scrap_item = 0 + d.is_scrap_item = 1 + else: + d.is_finished_item = 0 + d.is_scrap_item = 0 def get_finished_item(self): finished_item = None @@ -733,9 +740,9 @@ class StockEntry(StockController): def validate_finished_goods(self): """ - 1. Check if FG exists - 2. Check if Multiple FG Items are present - 3. Check FG Item and Qty against WO if present + 1. Check if FG exists (mfg, repack) + 2. Check if Multiple FG Items are present (mfg) + 3. Check FG Item and Qty against WO if present (mfg) """ production_item, wo_qty, finished_items = None, 0, [] @@ -748,8 +755,9 @@ class StockEntry(StockController): for d in self.get('items'): if d.is_finished_item: if not self.work_order: + # Independent MFG Entry/ Repack Entry, no WO to match against finished_items.append(d.item_code) - continue # Independent Manufacture Entry, no WO to match against + continue if d.item_code != production_item: frappe.throw(_("Finished Item {0} does not match with Work Order {1}") @@ -762,19 +770,17 @@ class StockEntry(StockController): finished_items.append(d.item_code) - if len(set(finished_items)) > 1: + if not finished_items: frappe.throw( - msg=_("Multiple items cannot be marked as finished item"), - title=_("Note"), - exc=FinishedGoodError + msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name), + title=_("Missing Finished Good"), exc=FinishedGoodError ) if self.purpose == "Manufacture": - if not finished_items: + if len(set(finished_items)) > 1: frappe.throw( - msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name), - title=_("Missing Finished Good"), - exc=FinishedGoodError + msg=_("Multiple items cannot be marked as finished item"), + title=_("Note"), exc=FinishedGoodError ) allowance_percentage = flt( @@ -1275,22 +1281,29 @@ class StockEntry(StockController): if not self.pro_doc: self.set_work_order_details() - scrap_items = frappe.db.sql(''' - SELECT - JCSI.item_code, JCSI.item_name, SUM(JCSI.stock_qty) as stock_qty, JCSI.stock_uom, JCSI.description - FROM - `tabJob Card` JC, `tabJob Card Scrap Item` JCSI - WHERE - JCSI.parent = JC.name AND JC.docstatus = 1 - AND JCSI.item_code IS NOT NULL AND JC.work_order = %s - GROUP BY - JCSI.item_code - ''', self.work_order, as_dict=1) - - pending_qty = flt(self.pro_doc.qty) - flt(self.pro_doc.produced_qty) - if pending_qty <=0: + if not self.pro_doc.operations: return [] + job_card = frappe.qb.DocType('Job Card') + job_card_scrap_item = frappe.qb.DocType('Job Card Scrap Item') + + scrap_items = ( + frappe.qb.from_(job_card) + .select( + Sum(job_card_scrap_item.stock_qty).as_('stock_qty'), + job_card_scrap_item.item_code, job_card_scrap_item.item_name, + job_card_scrap_item.description, job_card_scrap_item.stock_uom) + .join(job_card_scrap_item) + .on(job_card_scrap_item.parent == job_card.name) + .where( + (job_card_scrap_item.item_code.isnotnull()) + & (job_card.work_order == self.work_order) + & (job_card.docstatus == 1)) + .groupby(job_card_scrap_item.item_code) + ).run(as_dict=1) + + pending_qty = flt(self.get_completed_job_card_qty()) - flt(self.pro_doc.produced_qty) + used_scrap_items = self.get_used_scrap_items() for row in scrap_items: row.stock_qty -= flt(used_scrap_items.get(row.item_code)) @@ -1304,6 +1317,9 @@ class StockEntry(StockController): return scrap_items + def get_completed_job_card_qty(self): + return flt(min([d.completed_qty for d in self.pro_doc.operations])) + def get_used_scrap_items(self): used_scrap_items = defaultdict(float) data = frappe.get_all( @@ -1429,14 +1445,15 @@ class StockEntry(StockController): qty = req_qty_each * flt(self.fg_completed_qty) elif backflushed_materials.get(item.item_code): + precision = frappe.get_precision("Stock Entry Detail", "qty") for d in backflushed_materials.get(item.item_code): - if d.get(item.warehouse): + if d.get(item.warehouse) > 0: if (qty > req_qty): - qty = (qty/trans_qty) * flt(self.fg_completed_qty) + qty = ((flt(qty, precision) - flt(d.get(item.warehouse), precision)) + / (flt(trans_qty, precision) - flt(produced_qty, precision)) + ) * flt(self.fg_completed_qty) - if consumed_qty and frappe.db.get_single_value("Manufacturing Settings", - "material_consumption"): - qty -= consumed_qty + d[item.warehouse] -= qty if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')): qty = frappe.utils.ceil(qty) @@ -1656,6 +1673,8 @@ class StockEntry(StockController): for d in self.get("items"): item_code = d.get('original_item') or d.get('item_code') reserve_warehouse = item_wh.get(item_code) + if not (reserve_warehouse and item_code): + continue stock_bin = get_bin(item_code, reserve_warehouse) stock_bin.update_reserved_qty_for_sub_contracting() diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index b874874adf..306f2c3e69 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -226,9 +226,47 @@ class TestStockEntry(ERPNextTestCase): mtn.cancel() - def test_repack_no_change_in_valuation(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') + def test_repack_multiple_fg(self): + "Test `is_finished_item` for one item repacked into two items." + make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100) + repack = frappe.copy_doc(test_records[3]) + repack.posting_date = nowdate() + repack.posting_time = nowtime() + + repack.items[0].qty = 100.0 + repack.items[0].transfer_qty = 100.0 + repack.items[1].qty = 50.0 + + repack.append("items", { + "conversion_factor": 1.0, + "cost_center": "_Test Cost Center - _TC", + "doctype": "Stock Entry Detail", + "expense_account": "Stock Adjustment - _TC", + "basic_rate": 150, + "item_code": "_Test Item 2", + "parentfield": "items", + "qty": 50.0, + "stock_uom": "_Test UOM", + "t_warehouse": "_Test Warehouse - _TC", + "transfer_qty": 50.0, + "uom": "_Test UOM" + }) + repack.set_stock_entry_type() + repack.insert() + + self.assertEqual(repack.items[1].is_finished_item, 1) + self.assertEqual(repack.items[2].is_finished_item, 1) + + repack.items[1].is_finished_item = 0 + repack.items[2].is_finished_item = 0 + + # must raise error if 0 fg in repack entry + self.assertRaises(FinishedGoodError, repack.validate_finished_goods) + + repack.delete() # teardown + + def test_repack_no_change_in_valuation(self): make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100) make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", qty=50, basic_rate=100) @@ -813,6 +851,34 @@ class TestStockEntry(ERPNextTestCase): self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1) self.assertEqual(se.get("items")[0].amount, 0) + def test_zero_incoming_rate(self): + """ Make sure incoming rate of 0 is allowed while consuming. + + qty | rate | valuation rate + 1 | 100 | 100 + 1 | 0 | 50 + -1 | 100 | 0 + -1 | 0 <--- assert this + """ + item_code = "_TestZeroVal" + warehouse = "_Test Warehouse - _TC" + create_item('_TestZeroVal') + _receipt = make_stock_entry(item_code=item_code, qty=1, to_warehouse=warehouse, rate=100) + receipt2 = make_stock_entry(item_code=item_code, qty=1, to_warehouse=warehouse, rate=0, do_not_save=True) + receipt2.items[0].allow_zero_valuation_rate = 1 + receipt2.save() + receipt2.submit() + + issue = make_stock_entry(item_code=item_code, qty=1, from_warehouse=warehouse) + + value_diff = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": issue.name, "voucher_type": "Stock Entry"}, "stock_value_difference") + self.assertEqual(value_diff, -100) + + issue2 = make_stock_entry(item_code=item_code, qty=1, from_warehouse=warehouse) + value_diff = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": issue2.name, "voucher_type": "Stock Entry"}, "stock_value_difference") + self.assertEqual(value_diff, 0) + + def test_gle_for_opening_stock_entry(self): mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory", qty=50, basic_rate=100, diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index cafbd7581c..a1030d5496 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -5,7 +5,10 @@ import frappe from frappe.core.page.permission_manager.permission_manager import reset from frappe.utils import add_days, today -from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.delivery_note.test_delivery_note import ( + create_delivery_note, + create_return_delivery_note, +) from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( create_landed_cost_voucher, @@ -232,8 +235,7 @@ class TestStockLedgerEntry(ERPNextTestCase): self.assertEqual(outgoing_rate, 100) # Return Entry: Qty = -2, Rate = 150 - return_dn = create_delivery_note(is_return=1, return_against=dn.name, item_code=bundled_item, qty=-2, rate=150, - company=company, warehouse="Stores - _TC", expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC") + return_dn = create_return_delivery_note(source_name=dn.name, rate=150, qty=-2) # check incoming rate for Return entry incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index c4ddc9e2d6..428370cc75 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -6,7 +6,7 @@ import frappe -from frappe.utils import add_days, flt, nowdate, nowtime, random_string +from frappe.utils import add_days, cstr, flt, nowdate, nowtime, random_string from erpnext.accounts.utils import get_stock_and_account_balance from erpnext.stock.doctype.item.test_item import create_item @@ -439,8 +439,8 @@ class TestStockReconciliation(ERPNextTestCase): self.assertRaises(frappe.ValidationError, sr.submit) def test_serial_no_cancellation(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + item = create_item("Stock-Reco-Serial-Item-9", is_stock_item=1) if not item.has_serial_no: item.has_serial_no = 1 @@ -466,6 +466,31 @@ class TestStockReconciliation(ERPNextTestCase): self.assertEqual(len(active_sr_no), 10) + def test_serial_no_creation_and_inactivation(self): + item = create_item("_TestItemCreatedWithStockReco", is_stock_item=1) + if not item.has_serial_no: + item.has_serial_no = 1 + item.save() + + item_code = item.name + warehouse = "_Test Warehouse - _TC" + + sr = create_stock_reconciliation(item_code=item.name, warehouse=warehouse, + serial_no="SR-CREATED-SR-NO", qty=1, do_not_submit=True, rate=100) + sr.save() + self.assertEqual(cstr(sr.items[0].current_serial_no), "") + sr.submit() + + active_sr_no = frappe.get_all("Serial No", + filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"}) + self.assertEqual(len(active_sr_no), 1) + + sr.cancel() + active_sr_no = frappe.get_all("Serial No", + filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"}) + self.assertEqual(len(active_sr_no), 0) + + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) if not batch_item_doc.has_batch_no: diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py index 44e13869dd..87097c72fa 100644 --- a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py +++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py @@ -55,7 +55,8 @@ def get_stock_ledger_entries(filters): return frappe.db.sql("""select item_code, batch_no, warehouse, posting_date, actual_qty from `tabStock Ledger Entry` - where docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" % + where is_cancelled = 0 + and docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" % conditions, as_dict=1) def get_item_warehouse_batch_map(filters, float_precision): diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index 5f6184d6f3..058af77aa2 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -91,7 +91,7 @@ def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDLis voucher_nos = [fe.get('voucher_no') for fe in filtered_entries] svd_list = frappe.get_list( 'Stock Ledger Entry', fields=['item_code','stock_value_difference'], - filters=[('voucher_no', 'in', voucher_nos)] + filters=[('voucher_no', 'in', voucher_nos), ("is_cancelled", "=", 0)] ) assign_item_groups_to_svd_list(svd_list) return svd_list diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py index d452ffd913..be8597dfed 100644 --- a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py +++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py @@ -73,7 +73,7 @@ def get_stock_ledger_entries(report_filters): fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'serial_no as serial_nos', 'actual_qty', 'posting_date', 'posting_time', 'company', 'warehouse', '(stock_value_difference / actual_qty) as valuation_rate'] - filters = {'serial_no': ("is", "set")} + filters = {'serial_no': ("is", "set"), "is_cancelled": 0} if report_filters.get('item_code'): filters['item_code'] = report_filters.get('item_code') diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index 3f490653e1..cfa1e474c7 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -76,6 +76,7 @@ def get_consumed_items(condition): on sle.voucher_no = se.name where actual_qty < 0 + and is_cancelled = 0 and voucher_type not in ('Delivery Note', 'Sales Invoice') %s group by item_code""" % condition, as_dict=1) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index fe2417bba7..ef7c2cc7d9 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -86,10 +86,10 @@ frappe.query_reports["Stock Ledger"] = { ], "formatter": function (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (column.fieldname == "out_qty" && data.out_qty < 0) { + if (column.fieldname == "out_qty" && data && data.out_qty < 0) { value = "" + value + ""; } - else if (column.fieldname == "in_qty" && data.in_qty > 0) { + else if (column.fieldname == "in_qty" && data && data.in_qty > 0) { value = "" + value + ""; } diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index 1dcf863a9d..525af40b41 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -1,6 +1,8 @@ import unittest from typing import List, Tuple +import frappe + from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report DEFAULT_FILTERS = { @@ -10,8 +12,12 @@ DEFAULT_FILTERS = { } +batch = frappe.db.get_value("Batch", fieldname=["name"], as_dict=True, order_by="creation desc") + REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Stock Ledger", {"_optional": True}), + ("Stock Ledger", {"batch_no": batch}), + ("Stock Ledger", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}), ("Stock Balance", {"_optional": True}), ("Stock Projected Qty", {"_optional": True}), ("Batch-Wise Balance History", {}), @@ -40,6 +46,13 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Item Variant Details", {"item": "_Test Variant Item",}), ("Total Stock Summary", {"group_by": "warehouse",}), ("Batch Item Expiry Status", {}), + ("Incorrect Stock Value Report", {"company": "_Test Company with perpetual inventory"}), + ("Incorrect Serial No Valuation", {}), + ("Incorrect Balance Qty After Transaction", {}), + ("Supplier-Wise Sales Analytics", {}), + ("Item Prices", {"items": "Enabled Items only"}), + ("Delayed Item Report", {"based_on": "Sales Invoice"}), + ("Delayed Item Report", {"based_on": "Delivery Note"}), ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), ("Stock Ledger Invariant Check", { diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 107bb23222..0a7ab4009c 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -105,6 +105,7 @@ def get_args_for_future_sle(row): def validate_serial_no(sle): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + for sn in get_serial_nos(sle.serial_no): args = copy.deepcopy(sle) args.serial_no = sn @@ -423,6 +424,8 @@ class update_entries_after(object): return sorted(entries_to_fix, key=lambda k: k['timestamp']) def process_sle(self, sle): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + # previous sle data for this warehouse self.wh_data = self.data[sle.warehouse] @@ -437,7 +440,7 @@ class update_entries_after(object): if not self.args.get("sle_id"): self.get_dynamic_incoming_outgoing_rate(sle) - if sle.serial_no: + if get_serial_nos(sle.serial_no): self.get_serialized_values(sle) self.wh_data.qty_after_transaction += flt(sle.actual_qty) if sle.voucher_type == "Stock Reconciliation": @@ -449,8 +452,9 @@ class update_entries_after(object): # assert self.wh_data.valuation_rate = sle.valuation_rate self.wh_data.qty_after_transaction = sle.qty_after_transaction - self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]] self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate) + if self.valuation_method != "Moving Average": + self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]] else: if self.valuation_method == "Moving Average": self.get_moving_average_values(sle) @@ -602,9 +606,9 @@ class update_entries_after(object): incoming_rate = self.wh_data.valuation_rate stock_value_change = 0 - if incoming_rate: + if actual_qty > 0: stock_value_change = actual_qty * incoming_rate - elif actual_qty < 0: + else: # In case of delivery/stock issue, get average purchase rate # of serial nos of current entry if not sle.is_cancelled: @@ -646,6 +650,7 @@ class update_entries_after(object): where company = %s and actual_qty > 0 + and is_cancelled = 0 and (serial_no = %s or serial_no like %s or serial_no like %s @@ -901,6 +906,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, item_code = %s AND warehouse = %s AND valuation_rate >= 0 + AND is_cancelled = 0 AND NOT (voucher_no = %s AND voucher_type = %s) order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type)) @@ -911,6 +917,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, where item_code = %s AND valuation_rate > 0 + AND is_cancelled = 0 AND NOT(voucher_no = %s AND voucher_type = %s) order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type)) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 3b1ae3b43c..7c63c17ad0 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -86,8 +86,8 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None from erpnext.stock.stock_ledger import get_previous_sle - if not posting_date: posting_date = nowdate() - if not posting_time: posting_time = nowtime() + if posting_date is None: posting_date = nowdate() + if posting_time is None: posting_time = nowtime() args = { "item_code": item_code, @@ -103,7 +103,7 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None serial_nos = get_serial_nos_data_after_transactions(args) return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos) - if last_entry else (0.0, 0.0, 0.0)) + if last_entry else (0.0, 0.0, None)) else: return (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0) else: @@ -176,13 +176,7 @@ def get_latest_stock_balance(): def get_bin(item_code, warehouse): bin = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}) if not bin: - bin_obj = frappe.get_doc({ - "doctype": "Bin", - "item_code": item_code, - "warehouse": warehouse, - }) - bin_obj.flags.ignore_permissions = 1 - bin_obj.insert() + bin_obj = _create_bin(item_code, warehouse) else: bin_obj = frappe.get_doc('Bin', bin, for_update=True) bin_obj.flags.ignore_permissions = True @@ -192,16 +186,24 @@ def get_or_make_bin(item_code: str , warehouse: str) -> str: bin_record = frappe.db.get_value('Bin', {'item_code': item_code, 'warehouse': warehouse}) if not bin_record: - bin_obj = frappe.get_doc({ - "doctype": "Bin", - "item_code": item_code, - "warehouse": warehouse, - }) + bin_obj = _create_bin(item_code, warehouse) + bin_record = bin_obj.name + return bin_record + +def _create_bin(item_code, warehouse): + """Create a bin and take care of concurrent inserts.""" + + bin_creation_savepoint = "create_bin" + try: + frappe.db.savepoint(bin_creation_savepoint) + bin_obj = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse) bin_obj.flags.ignore_permissions = 1 bin_obj.insert() - bin_record = bin_obj.name + except frappe.UniqueValidationError: + frappe.db.rollback(save_point=bin_creation_savepoint) # preserve transaction in postgres + bin_obj = frappe.get_last_doc("Bin", {"item_code": item_code, "warehouse": warehouse}) - return bin_record + return bin_obj def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False): """WARNING: This function is deprecated. Inline this function instead of using it.""" @@ -419,6 +421,19 @@ def is_reposting_item_valuation_in_progress(): if reposting_in_progress: frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1) + +def calculate_mapped_packed_items_return(return_doc): + parent_items = set([item.parent_item for item in return_doc.packed_items]) + against_doc = frappe.get_doc(return_doc.doctype, return_doc.return_against) + + for original_bundle, returned_bundle in zip(against_doc.items, return_doc.items): + if original_bundle.item_code in parent_items: + for returned_packed_item, original_packed_item in zip(return_doc.packed_items, against_doc.packed_items): + if returned_packed_item.parent_item == original_bundle.item_code: + returned_packed_item.parent_detail_docname = returned_bundle.name + returned_packed_item.qty = (original_packed_item.qty / original_bundle.qty) * returned_bundle.qty + + def check_pending_reposting(posting_date: str, throw_error: bool = True) -> bool: """Check if there are pending reposting job till the specified posting date.""" diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json index 4df27f5dbf..ed33067e73 100644 --- a/erpnext/stock/workspace/stock/stock.json +++ b/erpnext/stock/workspace/stock/stock.json @@ -1,10 +1,11 @@ { "charts": [ { - "chart_name": "Warehouse wise Stock Value" + "chart_name": "Warehouse wise Stock Value", + "label": "Warehouse wise Stock Value" } ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Stock\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Quick Access\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Material Request\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Receipt\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Delivery Note\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Ledger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Balance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Masters & Reports\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items and Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Stock Transactions\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Stock Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Serial No and Batch\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Incorrect Data Report\", \"col\": 4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Quick Access\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Masters & Reports\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", "creation": "2020-03-02 15:43:10.096528", "docstatus": 0, "doctype": "Workspace", @@ -706,7 +707,7 @@ "type": "Link" } ], - "modified": "2021-11-23 04:34:00.420870", + "modified": "2022-01-13 17:47:38.339931", "modified_by": "Administrator", "module": "Stock", "name": "Stock", @@ -715,7 +716,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 24, + "sequence_id": 24.0, "shortcuts": [ { "color": "Green", diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index d5e5b78288..e211e24c40 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -236,7 +236,7 @@ def is_first_response(issue): return False def calculate_first_response_time(issue, first_responded_on): - issue_creation_date = issue.creation + issue_creation_date = issue.service_level_agreement_creation or issue.creation issue_creation_time = get_time_in_seconds(issue_creation_date) first_responded_on_in_seconds = get_time_in_seconds(first_responded_on) support_hours = frappe.get_cached_doc("Service Level Agreement", issue.service_level_agreement).support_and_resolution diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js index bfbffe22ad..4dbb0e7e86 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js @@ -111,6 +111,7 @@ frappe.ui.form.on('Service Level Agreement', { filters: [ ['DocType', 'issingle', '=', 0], ['DocType', 'istable', '=', 0], + ['DocType', 'is_submittable', '=', 0], ['DocType', 'name', 'not in', invalid_doctypes], ['DocType', 'module', 'not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]] ] diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index b3348f1e1e..526b6aa249 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -29,6 +29,7 @@ from erpnext.support.doctype.issue.issue import get_holidays class ServiceLevelAgreement(Document): def validate(self): + self.validate_selected_doctype() self.validate_doc() self.validate_status_field() self.check_priorities() @@ -106,6 +107,23 @@ class ServiceLevelAgreement(Document): frappe.throw(_("Service Level Agreement for {0} {1} already exists.").format( frappe.bold(self.entity_type), frappe.bold(self.entity))) + def validate_selected_doctype(self): + invalid_doctypes = list(frappe.model.core_doctypes_list) + invalid_doctypes.extend(['Cost Center', 'Company']) + valid_document_types = frappe.get_all('DocType', { + 'issingle': 0, + 'istable': 0, + 'is_submittable': 0, + 'name': ['not in', invalid_doctypes], + 'module': ['not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]] + }, pluck="name") + + if self.document_type not in valid_document_types: + frappe.throw( + msg=_("Please select valid document type."), + title=_("Invalid Document Type") + ) + def validate_status_field(self): meta = frappe.get_meta(self.document_type) if not meta.get_field("status"): @@ -247,9 +265,15 @@ def get_active_service_level_agreement_for(doc): ] customer = doc.get('customer') - or_filters.append( - ["Service Level Agreement", "entity", "in", [customer] + get_customer_group(customer) + get_customer_territory(customer)] - ) + if customer: + or_filters.extend([ + ["Service Level Agreement", "entity", "in", [customer] + get_customer_group(customer) + get_customer_territory(customer)], + ["Service Level Agreement", "entity_type", "is", "not set"] + ]) + else: + or_filters.append( + ["Service Level Agreement", "entity_type", "is", "not set"] + ) default_sla_filter = filters + [["Service Level Agreement", "default_service_level_agreement", "=", 1]] default_sla = frappe.get_all("Service Level Agreement", filters=default_sla_filter, @@ -361,11 +385,18 @@ def apply(doc, method=None): sla = get_active_service_level_agreement_for(doc) if not sla: + remove_sla_if_applied(doc) return process_sla(doc, sla) +def remove_sla_if_applied(doc): + doc.service_level_agreement = None + doc.response_by = None + doc.resolution_by = None + + def process_sla(doc, sla): if not doc.creation: @@ -670,7 +701,7 @@ def on_communication_update(doc, status): update_response_and_resolution_metrics(parent, for_resolution) update_agreement_status(parent, for_resolution) - parent.save() + parent.save(ignore_permissions=True) def reset_expected_response_and_resolution(doc): @@ -853,7 +884,7 @@ def get_user_time(user, to_string=False): @frappe.whitelist() def get_sla_doctypes(): doctypes = [] - data = frappe.get_list('Service Level Agreement', + data = frappe.get_all('Service Level Agreement', {'enabled': 1}, ['document_type'], distinct=1 diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py deleted file mode 100644 index 22e2c374e1..0000000000 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py +++ /dev/null @@ -1,13 +0,0 @@ -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'service_level_agreement', - 'transactions': [ - { - 'label': _('Issue'), - 'items': ['Issue'] - } - ] - } diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py index b07c862c7b..a34124fba2 100644 --- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py @@ -244,6 +244,13 @@ class TestServiceLevelAgreement(unittest.TestCase): applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement') self.assertEqual(applied_sla, lead_sla.name) + # check if SLA is removed if condition fails + lead.reload() + lead.source = None + lead.save() + applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement') + self.assertFalse(applied_sla) + def tearDown(self): for d in frappe.get_all("Service Level Agreement"): frappe.delete_doc("Service Level Agreement", d.name, force=1) diff --git a/erpnext/support/workspace/support/support.json b/erpnext/support/workspace/support/support.json index d68c7c70cf..8ca3a676c9 100644 --- a/erpnext/support/workspace/support/support.json +++ b/erpnext/support/workspace/support/support.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Issue\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Maintenance Visit\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Service Level Agreement\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Issues\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Service Level Agreement\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Warranty\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Issue\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Maintenance Visit\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Service Level Agreement\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Issues\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Service Level Agreement\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Warranty\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", "creation": "2020-03-02 15:48:23.224699", "docstatus": 0, "doctype": "Workspace", @@ -169,7 +169,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:02.699924", + "modified": "2022-01-13 17:48:27.247406", "modified_by": "Administrator", "module": "Support", "name": "Support", @@ -178,7 +178,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 25, + "sequence_id": 25.0, "shortcuts": [ { "color": "Yellow", diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index fbf25948a7..2bd7e9e71d 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -92,6 +92,8 @@ def change_settings(doctype, settings_dict): for key, value in settings_dict.items(): setattr(settings, key, value) settings.save() + # singles are cached by default, clear to avoid flake + frappe.db.value_cache[settings] = {} yield # yield control to calling function finally: @@ -125,17 +127,23 @@ def execute_script_report( if default_filters is None: default_filters = {} + test_filters = [] report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute") report_filters = frappe._dict(default_filters).copy().update(filters) - report_data = report_execute_fn(report_filters) + test_filters.append(report_filters) if optional_filters: for key, value in optional_filters.items(): - filter_with_optional_param = report_filters.copy().update({key: value}) - report_execute_fn(filter_with_optional_param) + test_filters.append(report_filters.copy().update({key: value})) + + for test_filter in test_filters: + try: + report_execute_fn(test_filter) + except Exception: + print(f"Report failed to execute with filters: {test_filter}") + raise - return report_data def timeout(seconds=30, error_message="Test timed out."): diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index d46ffb5609..0aca1a07bd 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -242,7 +242,7 @@ Apply Now,Jetzt bewerben, Appointment Confirmation,Terminbestätigung, Appointment Duration (mins),Termindauer (Min.), Appointment Type,Termin-Typ, -Appointment {0} and Sales Invoice {1} cancelled,Termin {0} und Verkaufsrechnung {1} wurden storniert, +Appointment {0} and Sales Invoice {1} cancelled,Termin {0} und Ausgangsrechnung {1} wurden storniert, Appointments and Encounters,Termine und Begegnungen, Appointments and Patient Encounters,Termine und Patienten-Begegnungen, Appraisal {0} created for Employee {1} in the given date range,Bewertung {0} für Mitarbeiter {1} im angegebenen Datumsbereich erstellt, @@ -427,7 +427,7 @@ Buying Price List,Kauf Preisliste, Buying Rate,Kaufrate, "Buying must be checked, if Applicable For is selected as {0}","Einkauf muss ausgewählt sein, wenn ""Anwenden auf"" auf {0} gesetzt wurde", By {0},Von {0}, -Bypass credit check at Sales Order ,Kreditprüfung im Kundenauftrag umgehen, +Bypass credit check at Sales Order ,Kreditprüfung im Auftrag umgehen, C-Form records,Kontakt-Formular Datensätze, C-form is not applicable for Invoice: {0},Kontaktformular nicht anwendbar auf Rechnung: {0}, CEO,CEO, @@ -474,11 +474,11 @@ Cannot deduct when category is for 'Valuation' or 'Vaulation and Total',"Kann ni "Cannot delete Serial No {0}, as it is used in stock transactions","Die Seriennummer {0} kann nicht gelöscht werden, da sie in Lagertransaktionen verwendet wird", Cannot enroll more than {0} students for this student group.,Kann nicht mehr als {0} Studenten für diese Studentengruppe einschreiben., Cannot find active Leave Period,Aktive Abwesenheitszeit kann nicht gefunden werden, -Cannot produce more Item {0} than Sales Order quantity {1},"Es können nicht mehr Artikel {0} produziert werden, als die über Kundenaufträge bestellte Stückzahl {1}", +Cannot produce more Item {0} than Sales Order quantity {1},"Es können nicht mehr Artikel {0} produziert werden, als die über den Auftrag bestellte Stückzahl {1}", Cannot promote Employee with status Left,Mitarbeiter mit Status "Links" kann nicht gefördert werden, Cannot refer row number greater than or equal to current row number for this Charge type,"Für diese Berechnungsart kann keine Zeilennummern zugeschrieben werden, die größer oder gleich der aktuellen Zeilennummer ist", Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row,"Die Berechnungsart kann für die erste Zeile nicht auf ""bezogen auf Menge der vorhergenden Zeile"" oder auf ""bezogen auf Gesamtmenge der vorhergenden Zeile"" gesetzt werden", -Cannot set as Lost as Sales Order is made.,"Kann nicht als verloren gekennzeichnet werden, da ein Kundenauftrag dazu existiert.", +Cannot set as Lost as Sales Order is made.,"Kann nicht als verloren gekennzeichnet werden, da ein Auftrag dazu existiert.", Cannot set authorization on basis of Discount for {0},Genehmigung kann nicht auf der Basis des Rabattes für {0} festgelegt werden, Cannot set multiple Item Defaults for a company.,Es können nicht mehrere Artikelstandards für ein Unternehmen festgelegt werden., Cannot set quantity less than delivered quantity,Menge kann nicht kleiner als gelieferte Menge sein, @@ -663,7 +663,7 @@ Create Leads,Leads erstellen, Create Maintenance Visit,Wartungsbesuch anlegen, Create Material Request,Materialanforderung erstellen, Create Multiple,Erstellen Sie mehrere, -Create Opening Sales and Purchase Invoices,Erstellen Sie Eingangsverkaufs- und Einkaufsrechnungen, +Create Opening Sales and Purchase Invoices,Erstellen Sie die eröffnungs Ein- und Ausgangsrechnungen, Create Payment Entries,Zahlungseinträge erstellen, Create Payment Entry,Zahlungseintrag erstellen, Create Print Format,Druckformat erstellen, @@ -672,9 +672,9 @@ Create Purchase Orders,Bestellungen erstellen, Create Quotation,Angebot erstellen, Create Salary Slip,Gehaltsabrechnung erstellen, Create Salary Slips,Gehaltszettel erstellen, -Create Sales Invoice,Verkaufsrechnung erstellen, -Create Sales Order,Kundenauftrag anlegen, -Create Sales Orders to help you plan your work and deliver on-time,"Erstellen Sie Kundenaufträge, um Ihre Arbeit zu planen und pünktlich zu liefern", +Create Sales Invoice,Ausgangsrechnung erstellen, +Create Sales Order,Auftrag anlegen, +Create Sales Orders to help you plan your work and deliver on-time,"Erstellen Sie Aufträge, um Ihre Arbeit zu planen und pünktlich zu liefern", Create Sample Retention Stock Entry,Legen Sie einen Muster-Retention-Stock-Eintrag an, Create Student,Schüler erstellen, Create Student Batch,Studentenstapel erstellen, @@ -808,7 +808,7 @@ Delivery Date,Liefertermin, Delivery Note,Lieferschein, Delivery Note {0} is not submitted,Lieferschein {0} ist nicht gebucht, Delivery Note {0} must not be submitted,Lieferschein {0} darf nicht gebucht sein, -Delivery Notes {0} must be cancelled before cancelling this Sales Order,Lieferscheine {0} müssen vor Löschung dieser Kundenaufträge storniert werden, +Delivery Notes {0} must be cancelled before cancelling this Sales Order,Lieferscheine {0} müssen vor Löschung dieser Aufträge storniert werden, Delivery Notes {0} updated,Lieferhinweise {0} aktualisiert, Delivery Status,Lieferstatus, Delivery Trip,Liefertrip, @@ -981,7 +981,7 @@ Execution,Ausführung, Executive Search,Direktsuche, Expand All,Alle ausklappen, Expected Delivery Date,Geplanter Liefertermin, -Expected Delivery Date should be after Sales Order Date,Voraussichtlicher Liefertermin sollte nach Kundenauftragsdatum erfolgen, +Expected Delivery Date should be after Sales Order Date,Voraussichtlicher Liefertermin sollte nach Auftragsdatum erfolgen, Expected End Date,Voraussichtliches Enddatum, Expected Hrs,Erwartete Stunden, Expected Start Date,Voraussichtliches Startdatum, @@ -1235,7 +1235,7 @@ ITC Reversed,ITC rückgängig gemacht, Identifying Decision Makers,Entscheidungsträger identifizieren, "If Auto Opt In is checked, then the customers will be automatically linked with the concerned Loyalty Program (on save)","Wenn Automatische Anmeldung aktiviert ist, werden die Kunden automatisch mit dem betreffenden Treueprogramm verknüpft (beim Speichern)", "If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.","Wenn mehrere Preisregeln weiterhin gleichrangig gelten, werden die Benutzer aufgefordert, Vorrangregelungen manuell zu erstellen, um den Konflikt zu lösen.", -"If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.","Wenn die ausgewählte Preisregel für "Rate" festgelegt wurde, wird die Preisliste überschrieben. Der Preisregelpreis ist der Endpreis, daher sollte kein weiterer Rabatt angewendet werden. Daher wird es in Transaktionen wie Kundenauftrag, Bestellung usw. im Feld 'Preis' und nicht im Feld 'Preislistenpreis' abgerufen.", +"If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.","Wenn die ausgewählte Preisregel für "Rate" festgelegt wurde, wird die Preisliste überschrieben. Der Preisregelpreis ist der Endpreis, daher sollte kein weiterer Rabatt angewendet werden. Daher wird es in Transaktionen wie Auftrag, Bestellung usw. im Feld 'Preis' und nicht im Feld 'Preislistenpreis' abgerufen.", "If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.","Wenn zwei oder mehrere Preisregeln basierend auf den oben genannten Bedingungen gefunden werden, wird eine Vorrangregelung angewandt. Priorität ist eine Zahl zwischen 0 und 20, wobei der Standardwert Null (leer) ist. Die höhere Zahl hat Vorrang, wenn es mehrere Preisregeln zu den gleichen Bedingungen gibt.", "If unlimited expiry for the Loyalty Points, keep the Expiry Duration empty or 0.","Wenn die Treuepunkte unbegrenzt ablaufen, lassen Sie die Ablaufdauer leer oder 0.", "If you have any questions, please get back to us.","Wenn Sie Fragen haben, wenden Sie sich bitte an uns.", @@ -1386,7 +1386,7 @@ Item {0} must be a Sub-contracted Item,Artikel {0} muss ein unterbeauftragter Ar Item {0} must be a non-stock item,Artikel {0} darf kein Lagerartikel sein, Item {0} must be a stock Item,Artikel {0} muss ein Lagerartikel sein, Item {0} not found,Artikel {0} nicht gefunden, -Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1},"Artikel {0} in Tabelle ""Rohmaterialien geliefert"" des Lieferantenauftrags {1} nicht gefunden", +Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1},"Artikel {0} in Tabelle ""Rohmaterialien geliefert"" der Bestellung {1} nicht gefunden", Item {0}: Ordered qty {1} cannot be less than minimum order qty {2} (defined in Item).,Artikel {0}: Bestellmenge {1} kann nicht weniger als Mindestbestellmenge {2} (im Artikel definiert) sein., Item: {0} does not exist in the system,Artikel: {0} ist nicht im System vorhanden, Items,Artikel, @@ -1489,7 +1489,7 @@ Lower Income,Niedrigeres Einkommen, Loyalty Amount,Loyalitätsbetrag, Loyalty Point Entry,Loyalitätspunkteintrag, Loyalty Points,Treuepunkte, -"Loyalty Points will be calculated from the spent done (via the Sales Invoice), based on collection factor mentioned.","Treuepunkte werden aus dem ausgegebenen Betrag (über die Verkaufsrechnung) berechnet, basierend auf dem genannten Sammelfaktor.", +"Loyalty Points will be calculated from the spent done (via the Sales Invoice), based on collection factor mentioned.","Treuepunkte werden aus dem ausgegebenen Betrag (über die Ausgangsrechnung) berechnet, basierend auf dem genannten Sammelfaktor.", Loyalty Points: {0},Treuepunkte: {0}, Loyalty Program,Treueprogramm, Main,Haupt, @@ -1499,11 +1499,11 @@ Maintenance Manager,Leiter der Wartung, Maintenance Schedule,Wartungsplan, Maintenance Schedule is not generated for all the items. Please click on 'Generate Schedule',"Wartungsplan wird nicht für alle Elemente erzeugt. Bitte klicken Sie auf ""Zeitplan generieren""", Maintenance Schedule {0} exists against {1},Wartungsplan {0} existiert gegen {1}, -Maintenance Schedule {0} must be cancelled before cancelling this Sales Order,Wartungsplan {0} muss vor Stornierung dieses Kundenauftrages aufgehoben werden, +Maintenance Schedule {0} must be cancelled before cancelling this Sales Order,Wartungsplan {0} muss vor Stornierung dieses Auftrags aufgehoben werden, Maintenance Status has to be Cancelled or Completed to Submit,Der Wartungsstatus muss abgebrochen oder zum Senden abgeschlossen werden, Maintenance User,Nutzer Instandhaltung, Maintenance Visit,Wartungsbesuch, -Maintenance Visit {0} must be cancelled before cancelling this Sales Order,Wartungsbesuch {0} muss vor Stornierung dieses Kundenauftrages abgebrochen werden, +Maintenance Visit {0} must be cancelled before cancelling this Sales Order,Wartungsbesuch {0} muss vor Stornierung dieses Auftrags abgebrochen werden, Maintenance start date can not be before delivery date for Serial No {0},Startdatum der Wartung kann nicht vor dem Liefertermin für Seriennummer {0} liegen, Make,Erstellen, Make Payment,Zahlung ausführen, @@ -1549,8 +1549,8 @@ Material Request,Materialanfrage, Material Request Date,Material Auftragsdatum, Material Request No,Materialanfragenr., "Material Request not created, as quantity for Raw Materials already available.","Materialanforderung nicht angelegt, da Menge für Rohstoffe bereits vorhanden.", -Material Request of maximum {0} can be made for Item {1} against Sales Order {2},Materialanfrage von maximal {0} kann für Artikel {1} zum Kundenauftrag {2} gemacht werden, -Material Request to Purchase Order,Von der Materialanfrage zum Lieferantenauftrag, +Material Request of maximum {0} can be made for Item {1} against Sales Order {2},Materialanfrage von maximal {0} kann für Artikel {1} zum Auftrag {2} gemacht werden, +Material Request to Purchase Order,Von der Materialanfrage zur Bestellung, Material Request {0} is cancelled or stopped,Materialanfrage {0} wird storniert oder gestoppt, Material Request {0} submitted.,Materialanfrage {0} gesendet., Material Transfer,Materialübertrag, @@ -2224,7 +2224,7 @@ Publishing,Veröffentlichung, Purchase,Einkauf, Purchase Amount,Gesamtbetrag des Einkaufs, Purchase Date,Kaufdatum, -Purchase Invoice,Einkaufsrechnung, +Purchase Invoice,Eingangsrechnung, Purchase Invoice {0} is already submitted,Eingangsrechnung {0} wurde bereits übertragen, Purchase Manager,Einkaufsleiter, Purchase Master Manager,Einkaufsstammdaten-Manager, @@ -2233,11 +2233,11 @@ Purchase Order Amount,Bestellbetrag, Purchase Order Amount(Company Currency),Bestellbetrag (Firmenwährung), Purchase Order Date,Bestelldatum, Purchase Order Items not received on time,Bestellpositionen nicht rechtzeitig erhalten, -Purchase Order number required for Item {0},Lieferantenauftragsnummer ist für den Artikel {0} erforderlich, -Purchase Order to Payment,Vom Lieferantenauftrag zur Zahlung, -Purchase Order {0} is not submitted,Lieferantenauftrag {0} wurde nicht übertragen, +Purchase Order number required for Item {0},Bestellnummer ist für den Artikel {0} erforderlich, +Purchase Order to Payment,Von der Bestellung zur Zahlung, +Purchase Order {0} is not submitted,Bestellung {0} wurde nicht übertragen, Purchase Orders are not allowed for {0} due to a scorecard standing of {1}.,Kaufaufträge sind für {0} wegen einer Scorecard von {1} nicht erlaubt., -Purchase Orders given to Suppliers.,An Lieferanten erteilte Lieferantenaufträge, +Purchase Orders given to Suppliers.,An Lieferanten erteilte Bestellungen, Purchase Price List,Einkaufspreisliste, Purchase Receipt,Kaufbeleg, Purchase Receipt {0} is not submitted,Kaufbeleg {0} wurde nicht übertragen, @@ -2440,7 +2440,7 @@ Row #{0}: Duplicate entry in References {1} {2},Zeile # {0}: Eintrag in Referenz Row #{0}: Expected Delivery Date cannot be before Purchase Order Date,Row # {0}: Voraussichtlicher Liefertermin kann nicht vor Bestelldatum sein, Row #{0}: Item added,Zeile # {0}: Element hinzugefügt, Row #{0}: Journal Entry {1} does not have account {2} or already matched against another voucher,Row # {0}: Journal Entry {1} nicht Konto {2} oder bereits abgestimmt gegen einen anderen Gutschein, -Row #{0}: Not allowed to change Supplier as Purchase Order already exists,"Zeile #{0}: Es ist nicht erlaubt den Lieferanten zu wechseln, da bereits ein Lieferantenauftrag vorhanden ist", +Row #{0}: Not allowed to change Supplier as Purchase Order already exists,"Zeile #{0}: Es ist nicht erlaubt den Lieferanten zu wechseln, da bereits eine Bestellung vorhanden ist", Row #{0}: Please set reorder quantity,Zeile #{0}: Bitte Nachbestellmenge angeben, Row #{0}: Please specify Serial No for Item {1},Zeile #{0}: Bitte Seriennummer für Artikel {1} angeben, Row #{0}: Qty increased by 1,Zeile # {0}: Menge um 1 erhöht, @@ -2483,7 +2483,7 @@ Row {0}: Hours value must be greater than zero.,Row {0}: Stunden-Wert muss grö Row {0}: Invalid reference {1},Zeile {0}: Ungültige Referenz {1}, Row {0}: Party / Account does not match with {1} / {2} in {3} {4},Zeile {0}: Gruppe / Konto stimmt nicht mit {1} / {2} in {3} {4} überein, Row {0}: Party Type and Party is required for Receivable / Payable account {1},Zeile {0}: Gruppen-Typ und Gruppe sind für Forderungen-/Verbindlichkeiten-Konto {1} zwingend erforderlich, -Row {0}: Payment against Sales/Purchase Order should always be marked as advance,"Zeile {0}: ""Zahlung zu Kunden-/Lieferantenauftrag"" sollte immer als ""Vorkasse"" eingestellt werden", +Row {0}: Payment against Sales/Purchase Order should always be marked as advance,"Zeile {0}: ""Zahlung zu Auftrag bzw. Bestellung"" sollte immer als ""Vorkasse"" eingestellt werden", Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry.,"Zeile {0}: Wenn es sich um eine Vorkasse-Buchung handelt, bitte ""Ist Vorkasse"" zu Konto {1} anklicken, .", Row {0}: Please set at Tax Exemption Reason in Sales Taxes and Charges,Zeile {0}: Bitte setzen Sie den Steuerbefreiungsgrund in den Umsatzsteuern und -gebühren, Row {0}: Please set the Mode of Payment in Payment Schedule,Zeile {0}: Bitte legen Sie die Zahlungsart im Zahlungsplan fest, @@ -2518,19 +2518,19 @@ Sales,Vertrieb, Sales Account,Verkaufskonto, Sales Expenses,Vertriebskosten, Sales Funnel,Verkaufstrichter, -Sales Invoice,Verkaufsrechnung, +Sales Invoice,Ausgangsrechnung, Sales Invoice {0} has already been submitted,Ausgangsrechnung {0} wurde bereits übertragen, -Sales Invoice {0} must be cancelled before cancelling this Sales Order,Ausgangsrechnung {0} muss vor Stornierung dieses Kundenauftrags abgebrochen werden, +Sales Invoice {0} must be cancelled before cancelling this Sales Order,Ausgangsrechnung {0} muss vor Stornierung dieses Auftrags abgebrochen werden, Sales Manager,Vertriebsleiter, Sales Master Manager,Hauptvertriebsleiter, -Sales Order,Auftragsbestätigung, -Sales Order Item,Kundenauftrags-Artikel, -Sales Order required for Item {0},Kundenauftrag für den Artikel {0} erforderlich, -Sales Order to Payment,Vom Kundenauftrag zum Zahlungseinang, -Sales Order {0} is not submitted,Kundenauftrag {0} wurde nicht übertragen, -Sales Order {0} is not valid,Kundenauftrag {0} ist nicht gültig, -Sales Order {0} is {1},Kundenauftrag {0} ist {1}, -Sales Orders,Kundenaufträge, +Sales Order,Auftrag, +Sales Order Item,Auftrags-Artikel, +Sales Order required for Item {0},Auftrag für den Artikel {0} erforderlich, +Sales Order to Payment,Vom Auftrag zum Zahlungseinang, +Sales Order {0} is not submitted,Auftrag {0} wurde nicht übertragen, +Sales Order {0} is not valid,Auftrag {0} ist nicht gültig, +Sales Order {0} is {1},Auftrag {0} ist {1}, +Sales Orders,Aufträge, Sales Partner,Vertriebspartner, Sales Pipeline,Vertriebspipeline, Sales Price List,Verkaufspreisliste, @@ -2541,7 +2541,7 @@ Sales Team,Verkaufsteam, Sales User,Nutzer Vertrieb, Sales and Returns,Verkauf und Retouren, Sales campaigns.,Vertriebskampagnen, -Sales orders are not available for production,Kundenaufträge sind für die Produktion nicht verfügbar, +Sales orders are not available for production,Aufträge sind für die Produktion nicht verfügbar, Salutation,Anrede, Same Company is entered more than once,Das selbe Unternehmen wurde mehrfach angegeben, Same item cannot be entered multiple times.,Das gleiche Einzelteil kann nicht mehrfach eingegeben werden., @@ -2650,7 +2650,7 @@ Serial No {0} not found,Seriennummer {0} wurde nicht gefunden, Serial No {0} not in stock,Seriennummer {0} ist nicht auf Lager, Serial No {0} quantity {1} cannot be a fraction,Seriennummer {0} mit Menge {1} kann nicht eine Teilmenge sein, Serial Nos Required for Serialized Item {0},Seriennummern sind erforderlich für den Artikel mit Seriennummer {0}, -Serial Number: {0} is already referenced in Sales Invoice: {1},Seriennummer: {0} wird bereits in der Verkaufsrechnung referenziert: {1}, +Serial Number: {0} is already referenced in Sales Invoice: {1},Seriennummer: {0} wird bereits in der Ausgangsrechnung referenziert: {1}, Serial Numbers,Seriennummer, Serial Numbers in row {0} does not match with Delivery Note,Seriennummern in Zeile {0} stimmt nicht mit der Lieferschein überein, Serial no {0} has been already returned,Seriennr. {0} wurde bereits zurückgegeben, @@ -3278,7 +3278,7 @@ Warning: Invalid SSL certificate on attachment {0},Warnung: Ungültiges SSL-Zert Warning: Invalid attachment {0},Warnung: Ungültige Anlage {0}, Warning: Leave application contains following block dates,Achtung: Die Urlaubsverwaltung enthält die folgenden gesperrten Daten, Warning: Material Requested Qty is less than Minimum Order Qty,Achtung : Materialanfragemenge ist geringer als die Mindestbestellmenge, -Warning: Sales Order {0} already exists against Customer's Purchase Order {1},Warnung: Kundenauftrag {0} zu Kunden-Bestellung bereits vorhanden {1}, +Warning: Sales Order {0} already exists against Customer's Purchase Order {1},Warnung: Auftrag {0} zu Kunden-Bestellung bereits vorhanden {1}, Warning: System will not check overbilling since amount for Item {0} in {1} is zero,"Achtung: Das System erkennt keine überhöhten Rechnungen, da der Betrag für Artikel {0} in {1} gleich Null ist", Warranty,Garantie, Warranty Claim,Garantieanspruch, @@ -3308,7 +3308,7 @@ Work Order already created for all items with BOM,Arbeitsauftrag wurde bereits f Work Order cannot be raised against a Item Template,Arbeitsauftrag kann nicht gegen eine Artikelbeschreibungsvorlage ausgelöst werden, Work Order has been {0},Arbeitsauftrag wurde {0}, Work Order not created,Arbeitsauftrag wurde nicht erstellt, -Work Order {0} must be cancelled before cancelling this Sales Order,Der Arbeitsauftrag {0} muss vor dem Stornieren dieses Kundenauftrags storniert werden, +Work Order {0} must be cancelled before cancelling this Sales Order,Der Arbeitsauftrag {0} muss vor dem Stornieren dieses Auftrags storniert werden, Work Order {0} must be submitted,Arbeitsauftrag {0} muss eingereicht werden, Work Orders Created: {0},Arbeitsaufträge erstellt: {0}, Work Summary for {0},Arbeitszusammenfassung für {0}, @@ -3382,9 +3382,9 @@ on,Am, {0} Student Groups created.,{0} Schülergruppen erstellt., {0} Students have been enrolled,{0} Studenten wurden angemeldet, {0} against Bill {1} dated {2},{0} zu Rechnung {1} vom {2}, -{0} against Purchase Order {1},{0} zu Lieferantenauftrag {1}, -{0} against Sales Invoice {1},{0} zu Verkaufsrechnung {1}, -{0} against Sales Order {1},{0} zu Kundenauftrag{1}, +{0} against Purchase Order {1},{0} zu Bestellung {1}, +{0} against Sales Invoice {1},{0} zu Ausgangsrechnung {1}, +{0} against Sales Order {1},{0} zu Auftrag{1}, {0} already allocated for Employee {1} for period {2} to {3},{0} bereits an Mitarbeiter {1} zugeteilt für den Zeitraum {2} bis {3}, {0} applicable after {1} working days,{0} gilt nach {1} Werktagen, {0} asset cannot be transferred,{0} Anlagevermögen kann nicht übertragen werden, @@ -3833,7 +3833,7 @@ Location,Ort, Log Type is required for check-ins falling in the shift: {0}.,Der Protokolltyp ist für Eincheckvorgänge in der Schicht erforderlich: {0}., Looks like someone sent you to an incomplete URL. Please ask them to look into it.,"Sieht aus wie jemand, den Sie zu einer unvollständigen URL gesendet. Bitte fragen Sie sie, sich in sie.", Make Journal Entry,Buchungssatz erstellen, -Make Purchase Invoice,Einkaufsrechnung erstellen, +Make Purchase Invoice,Eingangsrechnung erstellen, Manufactured,Hergestellt, Mark Work From Home,Markieren Sie Work From Home, Master,Vorlage, @@ -4302,7 +4302,7 @@ Row {}: Asset Naming Series is mandatory for the auto creation for item {},Zeile Assets not created for {0}. You will have to create asset manually.,Assets nicht für {0} erstellt. Sie müssen das Asset manuell erstellen., {0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.,{0} {1} hat Buchhaltungseinträge in Währung {2} für Firma {3}. Bitte wählen Sie ein Debitoren- oder Kreditorenkonto mit der Währung {2} aus., Invalid Account,Ungültiger Account, -Purchase Order Required,Lieferantenauftrag erforderlich, +Purchase Order Required,Bestellung erforderlich, Purchase Receipt Required,Kaufbeleg notwendig, Account Missing,Konto fehlt, Requested,Angefordert, @@ -4889,7 +4889,7 @@ Transaction Currency,Transaktionswährung, Subscription Plans,Abonnementpläne, SWIFT Number,SWIFT-Nummer, Recipient Message And Payment Details,Empfänger der Nachricht und Zahlungsdetails, -Make Sales Invoice,Verkaufsrechnung erstellen, +Make Sales Invoice,Ausgangsrechnung erstellen, Mute Email,Mute Email, payment_url,payment_url, Payment Gateway Details,Payment Gateway-Details, @@ -4988,7 +4988,7 @@ Apply Tax Withholding Amount,Steuereinbehaltungsbetrag anwenden, Accounting Dimensions ,Buchhaltung Dimensionen, Supplier Invoice Details,Lieferant Rechnungsdetails, Supplier Invoice Date,Lieferantenrechnungsdatum, -Return Against Purchase Invoice,Zurück zur Einkaufsrechnung, +Return Against Purchase Invoice,Zurück zur Eingangsrechnung, Select Supplier Address,Lieferantenadresse auswählen, Contact Person,Kontaktperson, Select Shipping Address,Lieferadresse auswählen, @@ -5081,7 +5081,7 @@ Service End Date,Service-Enddatum, Allow Zero Valuation Rate,Nullbewertung zulassen, Item Tax Rate,Artikelsteuersatz, Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges,Die Tabelle Steuerdetails wird aus dem Artikelstamm als Zeichenfolge entnommen und in diesem Feld gespeichert. Wird verwendet für Steuern und Abgaben, -Purchase Order Item,Lieferantenauftrags-Artikel, +Purchase Order Item,Bestellartikel, Purchase Receipt Detail,Kaufbelegdetail, Item Weight Details,Artikel Gewicht Details, Weight Per Unit,Gewicht pro Einheit, @@ -5110,10 +5110,10 @@ Include Payment (POS),(POS) Zahlung einschließen, Offline POS Name,Offline-Verkaufsstellen-Name, Is Return (Credit Note),ist Rücklieferung (Gutschrift), Return Against Sales Invoice,Zurück zur Kundenrechnung, -Update Billed Amount in Sales Order,Aktualisierung des Rechnungsbetrags im Kundenauftrag, -Customer PO Details,Kundenauftragsdetails, -Customer's Purchase Order,Kundenauftrag, -Customer's Purchase Order Date,Kundenauftragsdatum, +Update Billed Amount in Sales Order,Aktualisierung des Rechnungsbetrags im Auftrag, +Customer PO Details,Auftragsdetails, +Customer's Purchase Order,Bestellung des Kunden, +Customer's Purchase Order Date,Bestelldatum des Kunden, Customer Address,Kundenadresse, Shipping Address Name,Lieferadresse Bezeichnung, Company Address Name,Bezeichnung der Anschrift des Unternehmens, @@ -5483,7 +5483,7 @@ Sets 'Warehouse' in each row of the Items table.,Legt 'Warehouse' in jed Supply Raw Materials,Rohmaterial bereitstellen, Purchase Order Pricing Rule,Preisregel für Bestellungen, Set Reserve Warehouse,Legen Sie das Reservelager fest, -In Words will be visible once you save the Purchase Order.,"""In Worten"" wird sichtbar, sobald Sie den Lieferantenauftrag speichern.", +In Words will be visible once you save the Purchase Order.,"""In Worten"" wird sichtbar, sobald Sie die Bestellung speichern.", Advance Paid,Angezahlt, Tracking,Verfolgung, % Billed,% verrechnet, @@ -5500,7 +5500,7 @@ Against Blanket Order,Gegen Pauschalauftrag, Blanket Order,Blankoauftrag, Blanket Order Rate,Pauschale Bestellrate, Returned Qty,Zurückgegebene Menge, -Purchase Order Item Supplied,Lieferantenauftrags-Artikel geliefert, +Purchase Order Item Supplied,Bestellartikel geliefert, BOM Detail No,Stückliste Detailnr., Stock Uom,Lagermaßeinheit, Raw Material Item Code,Rohmaterial-Artikelnummer, @@ -6011,7 +6011,7 @@ Get financial breakup of Taxes and charges data by Amazon ,Erhalten Sie finanzie Sync Products,Produkte synchronisieren, Always sync your products from Amazon MWS before synching the Orders details,"Synchronisieren Sie Ihre Produkte immer mit Amazon MWS, bevor Sie die Bestelldetails synchronisieren", Sync Orders,Bestellungen synchronisieren, -Click this button to pull your Sales Order data from Amazon MWS.,"Klicken Sie auf diese Schaltfläche, um Ihre Kundenauftragsdaten von Amazon MWS abzurufen.", +Click this button to pull your Sales Order data from Amazon MWS.,"Klicken Sie auf diese Schaltfläche, um Ihre Auftragsdaten von Amazon MWS abzurufen.", Enable Scheduled Sync,Aktivieren Sie die geplante Synchronisierung, Check this to enable a scheduled Daily synchronization routine via scheduler,"Aktivieren Sie diese Option, um eine geplante tägliche Synchronisierungsroutine über den Scheduler zu aktivieren", Max Retry Limit,Max. Wiederholungslimit, @@ -6060,14 +6060,14 @@ Customer Settings,Kundeneinstellungen, Default Customer,Standardkunde, Customer Group will set to selected group while syncing customers from Shopify,Die Kundengruppe wird bei der Synchronisierung von Kunden von Shopify auf die ausgewählte Gruppe festgelegt, For Company,Für Unternehmen, -Cash Account will used for Sales Invoice creation,Cash Account wird für die Erstellung von Verkaufsrechnungen verwendet, +Cash Account will used for Sales Invoice creation,Cash Account wird für die Erstellung von Ausgangsrechnungen verwendet, Update Price from Shopify To ERPNext Price List,Preis von Shopify auf ERPNext Preisliste aktualisieren, -Default Warehouse to to create Sales Order and Delivery Note,Standard Warehouse zum Erstellen von Kundenauftrag und Lieferschein, -Sales Order Series,Kundenauftragsreihen, +Default Warehouse to to create Sales Order and Delivery Note,Standard Lager zum Erstellen von Auftrag und Lieferschein, +Sales Order Series,Auftragsnummernkreis, Import Delivery Notes from Shopify on Shipment,Lieferscheine von Shopify bei Versand importieren, Delivery Note Series,Lieferschein-Serie, -Import Sales Invoice from Shopify if Payment is marked,"Verkaufsrechnung aus Shopify importieren, wenn Zahlung markiert ist", -Sales Invoice Series,Verkaufsrechnung Serie, +Import Sales Invoice from Shopify if Payment is marked,"Ausgangsrechnung aus Shopify importieren, wenn Zahlung markiert ist", +Sales Invoice Series,Nummernkreis Ausgangsrechnung, Shopify Tax Account,Steuerkonto erstellen, Shopify Tax/Shipping Title,Steuern / Versand Titel, ERPNext Account,ERPNext Konto, @@ -6106,13 +6106,13 @@ API consumer secret,API-Konsumentengeheimnis, Tax Account,Steuerkonto, Freight and Forwarding Account,Fracht- und Speditionskonto, Creation User,Erstellungsbenutzer, -"The user that will be used to create Customers, Items and Sales Orders. This user should have the relevant permissions.","Der Benutzer, der zum Erstellen von Kunden, Artikeln und Kundenaufträgen verwendet wird. Dieser Benutzer sollte über die entsprechenden Berechtigungen verfügen.", -"This warehouse will be used to create Sales Orders. The fallback warehouse is ""Stores"".",Dieses Lager wird zum Erstellen von Kundenaufträgen verwendet. Das Fallback-Lager ist "Stores"., +"The user that will be used to create Customers, Items and Sales Orders. This user should have the relevant permissions.","Der Benutzer, der zum Erstellen von Kunden, Artikeln und Aufträgen verwendet wird. Dieser Benutzer sollte über die entsprechenden Berechtigungen verfügen.", +"This warehouse will be used to create Sales Orders. The fallback warehouse is ""Stores"".",Dieses Lager wird zum Erstellen von Aufträgen verwendet. Das Fallback-Lager ist "Stores"., "The fallback series is ""SO-WOO-"".",Die Fallback-Serie heißt "SO-WOO-"., -This company will be used to create Sales Orders.,Diese Firma wird zum Erstellen von Kundenaufträgen verwendet., +This company will be used to create Sales Orders.,Diese Firma wird zum Erstellen von Aufträgen verwendet., Delivery After (Days),Lieferung nach (Tage), -This is the default offset (days) for the Delivery Date in Sales Orders. The fallback offset is 7 days from the order placement date.,Dies ist der Standardversatz (Tage) für das Lieferdatum in Kundenaufträgen. Der Fallback-Offset beträgt 7 Tage ab Bestelldatum., -"This is the default UOM used for items and Sales orders. The fallback UOM is ""Nos"".","Dies ist die Standard-ME, die für Artikel und Kundenaufträge verwendet wird. Die Fallback-UOM lautet "Nos".", +This is the default offset (days) for the Delivery Date in Sales Orders. The fallback offset is 7 days from the order placement date.,Dies ist der Standardversatz (Tage) für das Lieferdatum in Aufträgen. Der Fallback-Offset beträgt 7 Tage ab Bestelldatum., +"This is the default UOM used for items and Sales orders. The fallback UOM is ""Nos"".","Dies ist die Standard-ME, die für Artikel und Aufträge verwendet wird. Die Fallback-UOM lautet "Nos".", Endpoints,Endpunkte, Endpoint,Endpunkt, Antibiotic Name,Antibiotika-Name, @@ -6230,8 +6230,8 @@ Appointment Reminder,Termin Erinnerung, Reminder Message,Erinnerungsmeldung, Remind Before,Vorher erinnern, Laboratory Settings,Laboreinstellungen, -Create Lab Test(s) on Sales Invoice Submission,Erstellen Sie Labortests für die Übermittlung von Verkaufsrechnungen, -Checking this will create Lab Test(s) specified in the Sales Invoice on submission.,"Wenn Sie dies aktivieren, werden Labortests erstellt, die bei der Übermittlung in der Verkaufsrechnung angegeben sind.", +Create Lab Test(s) on Sales Invoice Submission,Erstellen Sie Labortests für die Übermittlung von Ausgangsrechnungen, +Checking this will create Lab Test(s) specified in the Sales Invoice on submission.,"Wenn Sie dies aktivieren, werden Labortests erstellt, die bei der Übermittlung in der Ausgangsrechnung angegeben sind.", Create Sample Collection document for Lab Test,Erstellen Sie ein Probensammeldokument für den Labortest, Checking this will create a Sample Collection document every time you create a Lab Test,"Wenn Sie dies aktivieren, wird jedes Mal, wenn Sie einen Labortest erstellen, ein Probensammeldokument erstellt", Employee name and designation in print,Name und Bezeichnung des Mitarbeiters im Druck, @@ -6315,7 +6315,7 @@ Therapy,Therapie, Get Prescribed Therapies,Holen Sie sich verschriebene Therapien, Appointment Datetime,Termin Datum / Uhrzeit, Duration (In Minutes),Dauer (in Minuten), -Reference Sales Invoice,Referenzverkaufsrechnung, +Reference Sales Invoice,Referenzausgangsrechnung, More Info,Weitere Informationen, Referring Practitioner,Überweisender Praktiker, Reminded,Erinnert, @@ -7268,7 +7268,7 @@ Default Warehouses for Production,Standardlager für die Produktion, Default Work In Progress Warehouse,Standard-Fertigungslager, Default Finished Goods Warehouse,Standard-Fertigwarenlager, Default Scrap Warehouse,Standard-Schrottlager, -Overproduction Percentage For Sales Order,Überproduktionsprozentsatz für Kundenauftrag, +Overproduction Percentage For Sales Order,Überproduktionsprozentsatz für Auftrag, Overproduction Percentage For Work Order,Überproduktionsprozentsatz für Arbeitsauftrag, Other Settings,Weitere Einstellungen, Update BOM Cost Automatically,Stücklisten-Kosten automatisch aktualisieren, @@ -7281,7 +7281,7 @@ Default Workstation,Standard-Arbeitsplatz, Production Plan,Produktionsplan, MFG-PP-.YYYY.-,MFG-PP-.YYYY.-, Get Items From,Holen Sie Elemente aus, -Get Sales Orders,Kundenaufträge aufrufen, +Get Sales Orders,Aufträge aufrufen, Material Request Detail,Materialanforderungsdetail, Get Material Request,Get-Material anfordern, Material Requests,Materialwünsche, @@ -7304,8 +7304,8 @@ Quantity and Description,Menge und Beschreibung, material_request_item,material_request_item, Product Bundle Item,Produkt-Bundle-Artikel, Production Plan Material Request,Produktionsplan-Material anfordern, -Production Plan Sales Order,Produktionsplan für Kundenauftrag, -Sales Order Date,Kundenauftrags-Datum, +Production Plan Sales Order,Produktionsplan für Auftrag, +Sales Order Date,Auftragsdatum, Routing Name,Routing-Name, MFG-WO-.YYYY.-,MFG-WO-.YYYY.-, Item To Manufacture,Zu fertigender Artikel, @@ -7482,12 +7482,12 @@ Copied From,Kopiert von, Start and End Dates,Start- und Enddatum, Actual Time (in Hours),Tatsächliche Zeit (in Stunden), Costing and Billing,Kalkulation und Abrechnung, -Total Costing Amount (via Timesheets),Gesamtkalkulationsbetrag (über Arbeitszeittabellen), -Total Expense Claim (via Expense Claims),Gesamtbetrag der Aufwandsabrechnung (über Aufwandsabrechnungen), -Total Purchase Cost (via Purchase Invoice),Summe Einkaufskosten (über Einkaufsrechnung), -Total Sales Amount (via Sales Order),Gesamtverkaufsbetrag (über Kundenauftrag), -Total Billable Amount (via Timesheets),Gesamter abrechenbarer Betrag (über Arbeitszeittabellen), -Total Billed Amount (via Sales Invoices),Gesamtabrechnungsbetrag (über Verkaufsrechnungen), +Total Costing Amount (via Timesheets),Gesamtkalkulationsbetrag (über Zeiterfassung), +Total Expense Claim (via Expense Claims),Gesamtbetrag der Auslagenabrechnung (über Auslagenabrechnungen), +Total Purchase Cost (via Purchase Invoice),Summe Einkaufskosten (über Eingangsrechnung), +Total Sales Amount (via Sales Order),Auftragssumme (über Auftrag), +Total Billable Amount (via Timesheets),Abrechenbare Summe (über Zeiterfassung), +Total Billed Amount (via Sales Invoices),Abgerechnete Summe (über Ausgangsrechnungen), Total Consumed Material Cost (via Stock Entry),Summe der verbrauchten Materialkosten (über die Bestandsbuchung), Gross Margin,Handelsspanne, Gross Margin %,Handelsspanne %, @@ -7497,9 +7497,9 @@ Frequency To Collect Progress,"Häufigkeit, um Fortschritte zu sammeln", Twice Daily,Zweimal täglich, First Email,Erste E-Mail, Second Email,Zweite E-Mail, -Time to send,Zeit zu senden, -Day to Send,Tag zum Senden, -Message will be sent to the users to get their status on the Project,"Es wird eine Nachricht an die Benutzer gesendet, um deren Status für das Projekt zu erhalten", +Time to send,Sendezeit, +Day to Send,Sendetag, +Message will be sent to the users to get their status on the Project,"Es wird eine Nachricht an die Benutzer gesendet, um über den Projektstatus zu informieren", Projects Manager,Projektleiter, Project Template,Projektvorlage, Project Template Task,Projektvorlagenaufgabe, @@ -7518,27 +7518,27 @@ Timeline,Zeitleiste, Expected Time (in hours),Voraussichtliche Zeit (in Stunden), % Progress,% Fortschritt, Is Milestone,Ist Meilenstein, -Task Description,Aufgabenbeschreibung, +Task Description,Vorgangsbeschreibung, Dependencies,Abhängigkeiten, -Dependent Tasks,Abhängige Aufgaben, +Dependent Tasks,Abhängige Vorgänge, Depends on Tasks,Abhängig von Vorgang, Actual Start Date (via Time Sheet),Das tatsächliche Startdatum (durch Zeiterfassung), Actual Time (in hours),Tatsächliche Zeit (in Stunden), Actual End Date (via Time Sheet),Das tatsächliche Enddatum (durch Zeiterfassung), -Total Costing Amount (via Time Sheet),Gesamtkostenbetrag (über Arbeitszeitblatt), -Total Expense Claim (via Expense Claim),Gesamtbetrag der Aufwandsabrechnung (über Aufwandsabrechnung), -Total Billing Amount (via Time Sheet),Gesamtrechnungsbetrag (über Arbeitszeitblatt), +Total Costing Amount (via Time Sheet),Gesamtkosten (über Zeiterfassung), +Total Expense Claim (via Expense Claim),Summe der Auslagen (über Auslagenabrechnung), +Total Billing Amount (via Time Sheet),Gesamtrechnungsbetrag (über Zeiterfassung), Review Date,Überprüfungsdatum, Closing Date,Abschlussdatum, Task Depends On,Vorgang hängt ab von, -Task Type,Aufgabentyp, +Task Type,Vorgangstyp, TS-.YYYY.-,TS-.YYYY.-, Employee Detail,Mitarbeiterdetails, Billing Details,Rechnungsdetails, -Total Billable Hours,Insgesamt abrechenbare Stunden, -Total Billed Hours,Insgesamt Angekündigt Stunden, +Total Billable Hours,Summe abrechenbare Stunden, +Total Billed Hours,Summe abgerechneter Stunden, Total Costing Amount,Gesamtkalkulation Betrag, -Total Billable Amount,Insgesamt abrechenbare Betrag, +Total Billable Amount,Summe abrechenbarer Betrag, Total Billed Amount,Gesamtrechnungsbetrag, % Amount Billed,% des Betrages berechnet, Hrs,Std, @@ -7555,10 +7555,10 @@ Quality Goal,Qualitätsziel, Monitoring Frequency,Überwachungsfrequenz, Weekday,Wochentag, Objectives,Ziele, -Quality Goal Objective,Qualitätsziel Ziel, +Quality Goal Objective,Qualitätsziel, Objective,Zielsetzung, Agenda,Agenda, -Minutes,Protokoll, +Minutes,Protokolle, Quality Meeting Agenda,Qualitätstreffen Agenda, Quality Meeting Minutes,Qualitätssitzungsprotokoll, Minute,Minute, @@ -7627,7 +7627,7 @@ Restaurant Menu Item,Restaurant-Menüpunkt, Restaurant Order Entry,Restaurantbestellung, Restaurant Table,Restaurant-Tisch, Click Enter To Add,Klicken Sie zum Hinzufügen auf Hinzufügen., -Last Sales Invoice,Letzte Verkaufsrechnung, +Last Sales Invoice,Letzte Ausgangsrechnung, Current Order,Aktueller Auftrag, Restaurant Order Entry Item,Restaurantbestellzugangsposten, Served,Serviert, @@ -7639,7 +7639,7 @@ Reservation Time,Reservierungszeit, Reservation End Time,Reservierungsendzeit, No of Seats,Anzahl der Sitze, Minimum Seating,Mindestbestuhlung, -"Keep Track of Sales Campaigns. Keep track of Leads, Quotations, Sales Order etc from Campaigns to gauge Return on Investment. ","Verkaufskampagne verfolgen: Leads, Angebote, Kundenaufträge usw. von Kampagnen beobachten um die Kapitalverzinsung (RoI) zu messen.", +"Keep Track of Sales Campaigns. Keep track of Leads, Quotations, Sales Order etc from Campaigns to gauge Return on Investment. ","Verkaufskampagne verfolgen: Leads, Angebote, Aufträge usw. von Kampagnen beobachten um die Kapitalverzinsung (RoI) zu messen.", SAL-CAM-.YYYY.-,SAL-CAM-.YYYY.-, Campaign Schedules,Kampagnenpläne, Buyer of Goods and Services.,Käufer von Waren und Dienstleistungen., @@ -7647,8 +7647,8 @@ CUST-.YYYY.-,CUST-.YYYY.-, Default Company Bank Account,Standard-Bankkonto des Unternehmens, From Lead,Von Lead, Account Manager,Buchhalter, -Allow Sales Invoice Creation Without Sales Order,Ermöglichen Sie die Erstellung von Kundenrechnungen ohne Kundenauftrag, -Allow Sales Invoice Creation Without Delivery Note,Ermöglichen Sie die Erstellung einer Verkaufsrechnung ohne Lieferschein, +Allow Sales Invoice Creation Without Sales Order,Ermöglichen Sie die Erstellung von Kundenrechnungen ohne Auftrag, +Allow Sales Invoice Creation Without Delivery Note,Ermöglichen Sie die Erstellung einer Ausgangsrechnung ohne Lieferschein, Default Price List,Standardpreisliste, Primary Address and Contact Detail,Primäre Adresse und Kontaktdetails, "Select, to make the customer searchable with these fields","Wählen Sie, um den Kunden mit diesen Feldern durchsuchbar zu machen", @@ -7665,7 +7665,7 @@ Commission Rate,Provisionssatz, Sales Team Details,Verkaufsteamdetails, Customer POS id,Kunden-POS-ID, Customer Credit Limit,Kundenkreditlimit, -Bypass Credit Limit Check at Sales Order,Kreditlimitprüfung im Kundenauftrag umgehen, +Bypass Credit Limit Check at Sales Order,Kreditlimitprüfung im Auftrag umgehen, Industry Type,Wirtschaftsbranche, MAT-INS-.YYYY.-,MAT-INS-.YYYY.-, Installation Date,Datum der Installation, @@ -7701,16 +7701,16 @@ Against Docname,Zu Dokumentenname, Additional Notes,Zusätzliche Bemerkungen, SAL-ORD-.YYYY.-,SAL-ORD-.YYYY.-, Skip Delivery Note,Lieferschein überspringen, -In Words will be visible once you save the Sales Order.,"""In Worten"" wird sichtbar, sobald Sie den Kundenauftrag speichern.", -Track this Sales Order against any Project,Diesen Kundenauftrag in jedem Projekt nachverfolgen, +In Words will be visible once you save the Sales Order.,"""In Worten"" wird sichtbar, sobald Sie den Auftrag speichern.", +Track this Sales Order against any Project,Diesen Auftrag in jedem Projekt nachverfolgen, Billing and Delivery Status,Abrechnungs- und Lieferstatus, Not Delivered,Nicht geliefert, Fully Delivered,Komplett geliefert, Partly Delivered,Teilweise geliefert, Not Applicable,Nicht andwendbar, % Delivered,% geliefert, -% of materials delivered against this Sales Order,% der für diesen Kundenauftrag gelieferten Materialien, -% of materials billed against this Sales Order,% der Materialien welche zu diesem Kundenauftrag gebucht wurden, +% of materials delivered against this Sales Order,% der für diesen Auftrag gelieferten Materialien, +% of materials billed against this Sales Order,% der Materialien welche zu diesem Auftrag gebucht wurden, Not Billed,Nicht abgerechnet, Fully Billed,Voll berechnet, Partly Billed,Teilweise abgerechnet, @@ -7845,13 +7845,13 @@ Bank Balance,Kontostand, Bank Credit Balance,Bankguthaben, Receivables,Forderungen, Payables,Verbindlichkeiten, -Sales Orders to Bill,Kundenaufträge an Rechnung, +Sales Orders to Bill,Aufträge an Rechnung, Purchase Orders to Bill,Bestellungen an Rechnung, -New Sales Orders,Neue Kundenaufträge, +New Sales Orders,Neue Aufträge, New Purchase Orders,Neue Bestellungen an Lieferanten, -Sales Orders to Deliver,Kundenaufträge zu liefern, -Purchase Orders to Receive,Bestellungen zu empfangen, -New Purchase Invoice,Neue Kaufrechnung, +Sales Orders to Deliver,Auszuliefernde Aufträge, +Purchase Orders to Receive,Anzuliefernde Bestellungen, +New Purchase Invoice,Neue Eingangsrechnung, New Quotations,Neue Angebote, Open Quotations,Angebote öffnen, Open Issues,Offene Punkte, @@ -7972,7 +7972,7 @@ MAT-DN-.YYYY.-,MAT-DN-.YYYY.-, Is Return,Ist Rückgabe, Issue Credit Note,Gutschrift ausgeben, Return Against Delivery Note,Zurück zum Lieferschein, -Customer's Purchase Order No,Kundenauftragsnr., +Customer's Purchase Order No,Bestellnummer des Kunden, Billing Address Name,Name der Rechnungsadresse, Required only for sample item.,Nur erforderlich für Probeartikel., "If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Wenn eine Standardvorlage unter den Vorlagen ""Steuern und Abgaben beim Verkauf"" erstellt wurde, bitte eine Vorlage auswählen und auf die Schaltfläche unten klicken.", @@ -7989,8 +7989,8 @@ Installation Status,Installationsstatus, Excise Page Number,Seitenzahl entfernen, Instructions,Anweisungen, From Warehouse,Ab Lager, -Against Sales Order,Zu Kundenauftrag, -Against Sales Order Item,Zu Kundenauftrags-Position, +Against Sales Order,Zu Auftrag, +Against Sales Order Item,Zu Auftragsposition, Against Sales Invoice,Zu Ausgangsrechnung, Against Sales Invoice Item,Zu Ausgangsrechnungs-Position, Available Batch Qty at From Warehouse,Verfügbare Chargenmenge im Ausgangslager, @@ -8063,7 +8063,7 @@ Serial Number Series,Serie der Seriennummer, "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.","Beispiel: ABCD.##### \n Wenn ""Serie"" eingestellt ist und ""Seriennummer"" in den Transaktionen nicht aufgeführt ist, dann wird eine Seriennummer automatisch auf der Grundlage dieser Serie erstellt. Wenn immer explizit Seriennummern für diesen Artikel aufgeführt werden sollen, muss das Feld leer gelassen werden.", Variants,Varianten, Has Variants,Hat Varianten, -"If this item has variants, then it cannot be selected in sales orders etc.","Wenn dieser Artikel Varianten hat, dann kann er bei den Kundenaufträgen, etc. nicht ausgewählt werden", +"If this item has variants, then it cannot be selected in sales orders etc.","Wenn dieser Artikel Varianten hat, dann kann er bei den Aufträgen, etc. nicht ausgewählt werden", Variant Based On,Variante basierend auf, Item Attribute,Artikelattribut, "Sales, Purchase, Accounting Defaults","Verkauf, Einkauf, Buchhaltungsvorgaben", @@ -8529,7 +8529,7 @@ Open Work Orders,Arbeitsaufträge öffnen, Qty to Deliver,Zu liefernde Menge, Patient Appointment Analytics,Analyse von Patiententerminen, Payment Period Based On Invoice Date,Zahlungszeitraum basierend auf Rechnungsdatum, -Pending SO Items For Purchase Request,Ausstehende Artikel aus Kundenaufträgen für Lieferantenanfrage, +Pending SO Items For Purchase Request,Ausstehende Artikel aus Aufträgen für Lieferantenanfrage, Procurement Tracker,Beschaffungs-Tracker, Product Bundle Balance,Produkt-Bundle-Balance, Production Analytics,Produktions-Analysen, @@ -8544,7 +8544,7 @@ Purchase Invoice Trends,Trendanalyse Eingangsrechnungen, Qty to Receive,Anzunehmende Menge, Received Qty Amount,Erhaltene Menge Menge, Billed Qty,Rechnungsmenge, -Purchase Order Trends,Entwicklung Lieferantenaufträge, +Purchase Order Trends,Entwicklung Bestellungen, Purchase Receipt Trends,Trendanalyse Kaufbelege, Purchase Register,Übersicht über Einkäufe, Quotation Trends,Trendanalyse Angebote, @@ -8555,7 +8555,7 @@ Qty to Transfer,Zu versendende Menge, Salary Register,Gehalt Register, Sales Analytics,Vertriebsanalyse, Sales Invoice Trends,Ausgangsrechnung-Trendanalyse, -Sales Order Trends,Trendanalyse Kundenaufträge, +Sales Order Trends,Trendanalyse Aufträge, Sales Partner Commission Summary,Zusammenfassung der Vertriebspartnerprovision, Sales Partner Target Variance based on Item Group,Zielabweichung des Vertriebspartners basierend auf Artikelgruppe, Sales Partner Transaction Summary,Sales Partner Transaction Summary, @@ -8706,7 +8706,7 @@ Dunning Letter,Mahnbrief, Reference Detail No,Referenz Detail Nr, Custom Remarks,Benutzerdefinierte Bemerkungen, Please select a Company first.,Bitte wählen Sie zuerst eine Firma aus., -"Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice, Journal Entry or Dunning","Zeile # {0}: Der Referenzdokumenttyp muss Kundenauftrag, Verkaufsrechnung, Journaleintrag oder Mahnwesen sein", +"Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice, Journal Entry or Dunning","Zeile # {0}: Der Referenzdokumenttyp muss Auftrag, Ausgangsrechnung, Journaleintrag oder Mahnwesen sein", POS Closing Entry,POS Closing Entry, POS Opening Entry,POS-Eröffnungseintrag, POS Transactions,POS-Transaktionen, @@ -8716,7 +8716,7 @@ Closing Amount,Schlussbetrag, POS Closing Entry Taxes,POS Closing Entry Taxes, POS Invoice,POS-Rechnung, ACC-PSINV-.YYYY.-,ACC-PSINV-.YYYY.-, -Consolidated Sales Invoice,Konsolidierte Verkaufsrechnung, +Consolidated Sales Invoice,Konsolidierte Ausgangsrechnung, Return Against POS Invoice,Gegen POS-Rechnung zurücksenden, Consolidated,Konsolidiert, POS Invoice Item,POS-Rechnungsposition, @@ -8826,7 +8826,7 @@ Depreciation Posting Date,Buchungsdatum der Abschreibung, "By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a ","Standardmäßig wird der Lieferantenname gemäß dem eingegebenen Lieferantennamen festgelegt. Wenn Sie möchten, dass Lieferanten von a benannt werden", choose the 'Naming Series' option.,Wählen Sie die Option "Naming Series"., Configure the default Price List when creating a new Purchase transaction. Item prices will be fetched from this Price List.,Konfigurieren Sie die Standardpreisliste beim Erstellen einer neuen Kauftransaktion. Artikelpreise werden aus dieser Preisliste abgerufen., -"If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice or Receipt without creating a Purchase Order first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Order' checkbox in the Supplier master.","Wenn diese Option auf "Ja" konfiguriert ist, verhindert ERPNext, dass Sie eine Kaufrechnung oder einen Beleg erstellen können, ohne zuvor eine Bestellung zu erstellen. Diese Konfiguration kann für einen bestimmten Lieferanten überschrieben werden, indem das Kontrollkästchen "Erstellung von Einkaufsrechnungen ohne Bestellung zulassen" im Lieferantenstamm aktiviert wird.", +"If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice or Receipt without creating a Purchase Order first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Order' checkbox in the Supplier master.","Wenn diese Option auf "Ja" konfiguriert ist, verhindert ERPNext, dass Sie eine Kaufrechnung oder einen Beleg erstellen können, ohne zuvor eine Bestellung zu erstellen. Diese Konfiguration kann für einen bestimmten Lieferanten überschrieben werden, indem das Kontrollkästchen "Erstellung von Eingangsrechnungen ohne Bestellung zulassen" im Lieferantenstamm aktiviert wird.", "If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Receipt' checkbox in the Supplier master.","Wenn diese Option auf "Ja" konfiguriert ist, verhindert ERPNext, dass Sie eine Kaufrechnung erstellen können, ohne zuvor einen Kaufbeleg zu erstellen. Diese Konfiguration kann für einen bestimmten Lieferanten überschrieben werden, indem das Kontrollkästchen "Erstellung von Kaufrechnungen ohne Kaufbeleg zulassen" im Lieferantenstamm aktiviert wird.", Quantity & Stock,Menge & Lager, Call Details,Anrufdetails, @@ -8903,7 +8903,7 @@ Set the Out Patient Consulting Charge for this Practitioner.,Legen Sie die Gebü "If checked, a customer will be created for every Patient. Patient Invoices will be created against this Customer. You can also select existing Customer while creating a Patient. This field is checked by default.","Wenn diese Option aktiviert ist, wird für jeden Patienten ein Kunde erstellt. Für diesen Kunden werden Patientenrechnungen erstellt. Sie können beim Erstellen eines Patienten auch einen vorhandenen Kunden auswählen. Dieses Feld ist standardmäßig aktiviert.", Collect Registration Fee,Registrierungsgebühr sammeln, "If your Healthcare facility bills registrations of Patients, you can check this and set the Registration Fee in the field below. Checking this will create new Patients with a Disabled status by default and will only be enabled after invoicing the Registration Fee.","Wenn Ihre Gesundheitseinrichtung die Registrierung von Patienten in Rechnung stellt, können Sie dies überprüfen und die Registrierungsgebühr im Feld unten festlegen. Wenn Sie dies aktivieren, werden standardmäßig neue Patienten mit dem Status "Deaktiviert" erstellt und erst nach Rechnungsstellung der Registrierungsgebühr aktiviert.", -Checking this will automatically create a Sales Invoice whenever an appointment is booked for a Patient.,"Wenn Sie dies aktivieren, wird automatisch eine Verkaufsrechnung erstellt, wenn ein Termin für einen Patienten gebucht wird.", +Checking this will automatically create a Sales Invoice whenever an appointment is booked for a Patient.,"Wenn Sie dies aktivieren, wird automatisch eine Ausgangsrechnung erstellt, wenn ein Termin für einen Patienten gebucht wird.", Healthcare Service Items,Artikel im Gesundheitswesen, "You can create a service item for Inpatient Visit Charge and set it here. Similarly, you can set up other Healthcare Service Items for billing in this section. Click ",Sie können ein Serviceelement für die Gebühr für stationäre Besuche erstellen und hier festlegen. Ebenso können Sie in diesem Abschnitt andere Gesundheitsposten für die Abrechnung einrichten. Klicken, Set up default Accounts for the Healthcare Facility,Richten Sie Standardkonten für die Gesundheitseinrichtung ein, @@ -8958,7 +8958,7 @@ Lab Test Group Template,Labortestgruppenvorlage, Add New Line,Neue Zeile hinzufügen, Secondary UOM,Sekundäre UOM, "Single: Results which require only a single input.\n
    \nCompound: Results which require multiple event inputs.\n
    \nDescriptive: Tests which have multiple result components with manual result entry.\n
    \nGrouped: Test templates which are a group of other test templates.\n
    \nNo Result: Tests with no results, can be ordered and billed but no Lab Test will be created. e.g.. Sub Tests for Grouped results","Single : Ergebnisse, die nur eine einzige Eingabe erfordern.
    Verbindung : Ergebnisse, die mehrere Ereigniseingaben erfordern.
    Beschreibend : Tests mit mehreren Ergebniskomponenten mit manueller Ergebniseingabe.
    Gruppiert : Testvorlagen, die eine Gruppe anderer Testvorlagen sind.
    Kein Ergebnis : Tests ohne Ergebnisse können bestellt und in Rechnung gestellt werden, es wird jedoch kein Labortest erstellt. z.B. Untertests für gruppierte Ergebnisse", -"If unchecked, the item will not be available in Sales Invoices for billing but can be used in group test creation. ","Wenn diese Option deaktiviert ist, ist der Artikel in den Verkaufsrechnungen nicht zur Abrechnung verfügbar, kann jedoch für die Erstellung von Gruppentests verwendet werden.", +"If unchecked, the item will not be available in Sales Invoices for billing but can be used in group test creation. ","Wenn diese Option deaktiviert ist, ist der Artikel in den Ausgangsrechnungen nicht zur Abrechnung verfügbar, kann jedoch für die Erstellung von Gruppentests verwendet werden.", Description ,Beschreibung, Descriptive Test,Beschreibender Test, Group Tests,Gruppentests, @@ -9084,8 +9084,8 @@ Feedback By,Feedback von, Manufacturing Section,Fertigungsabteilung, "By default, the Customer Name is set as per the Full Name entered. If you want Customers to be named by a ","Standardmäßig wird der Kundenname gemäß dem eingegebenen vollständigen Namen festgelegt. Wenn Sie möchten, dass Kunden von a benannt werden", Configure the default Price List when creating a new Sales transaction. Item prices will be fetched from this Price List.,Konfigurieren Sie die Standardpreisliste beim Erstellen einer neuen Verkaufstransaktion. Artikelpreise werden aus dieser Preisliste abgerufen., -"If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice or Delivery Note without creating a Sales Order first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Sales Order' checkbox in the Customer master.","Wenn diese Option auf "Ja" konfiguriert ist, verhindert ERPNext, dass Sie eine Verkaufsrechnung oder einen Lieferschein erstellen, ohne zuvor einen Kundenauftrag zu erstellen. Diese Konfiguration kann für einen bestimmten Kunden überschrieben werden, indem das Kontrollkästchen "Erstellung von Verkaufsrechnungen ohne Kundenauftrag zulassen" im Kundenstamm aktiviert wird.", -"If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice without creating a Delivery Note first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Delivery Note' checkbox in the Customer master.","Wenn diese Option auf "Ja" konfiguriert ist, verhindert ERPNext, dass Sie eine Verkaufsrechnung erstellen, ohne zuvor einen Lieferschein zu erstellen. Diese Konfiguration kann für einen bestimmten Kunden überschrieben werden, indem das Kontrollkästchen "Erstellung von Verkaufsrechnungen ohne Lieferschein zulassen" im Kundenstamm aktiviert wird.", +"If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice or Delivery Note without creating a Sales Order first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Sales Order' checkbox in the Customer master.","Wenn diese Option auf "Ja" konfiguriert ist, verhindert ERPNext, dass Sie eine Ausgangsrechnung oder einen Lieferschein erstellen, ohne zuvor einen Auftrag zu erstellen. Diese Konfiguration kann für einen bestimmten Kunden überschrieben werden, indem das Kontrollkästchen "Erstellung von Ausgangsrechnung ohne Auftrag zulassen" im Kundenstamm aktiviert wird.", +"If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice without creating a Delivery Note first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Delivery Note' checkbox in the Customer master.","Wenn diese Option auf "Ja" konfiguriert ist, verhindert ERPNext, dass Sie eine Ausgangsrechnung erstellen, ohne zuvor einen Lieferschein zu erstellen. Diese Konfiguration kann für einen bestimmten Kunden überschrieben werden, indem das Kontrollkästchen "Erstellung von Ausgangsrechnungen ohne Lieferschein zulassen" im Kundenstamm aktiviert wird.", Default Warehouse for Sales Return,Standardlager für Retouren, Default In Transit Warehouse,Standard im Transit Warehouse, Enable Perpetual Inventory For Non Stock Items,Aktivieren Sie das ewige Inventar für nicht vorrätige Artikel, @@ -9114,7 +9114,7 @@ Set a Default Warehouse for Inventory Transactions. This will be fetched into th Choose between FIFO and Moving Average Valuation Methods. Click ,Wählen Sie zwischen FIFO- und Moving Average-Bewertungsmethoden. Klicken, to know more about them.,um mehr über sie zu erfahren., Show 'Scan Barcode' field above every child table to insert Items with ease.,"Zeigen Sie das Feld "Barcode scannen" über jeder untergeordneten Tabelle an, um Elemente problemlos einzufügen.", -"Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.","Seriennummern für Lagerbestände werden automatisch basierend auf den Artikeln festgelegt, die basierend auf First-In-First-Out in Transaktionen wie Kauf- / Verkaufsrechnungen, Lieferscheinen usw. eingegeben wurden.", +"Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.","Seriennummern für Lagerbestände werden automatisch basierend auf den Artikeln festgelegt, die basierend auf First-In-First-Out in Transaktionen wie Ein- und Ausgangsrechnungen, Lieferscheinen usw. eingegeben wurden.", "If blank, parent Warehouse Account or company default will be considered in transactions","Wenn leer, wird das übergeordnete Lagerkonto oder der Firmenstandard bei Transaktionen berücksichtigt", Service Level Agreement Details,Details zum Service Level Agreement, Service Level Agreement Status,Status des Service Level Agreements, @@ -9263,10 +9263,10 @@ Salary Payments via ECS,Gehaltszahlungen über ECS, Account No,Konto Nr, IFSC,IFSC, MICR,MICR, -Sales Order Analysis,Kundenauftragsanalyse, +Sales Order Analysis,Auftragsanalyse, Amount Delivered,Gelieferter Betrag, Delay (in Days),Verzögerung (in Tagen), -Group by Sales Order,Nach Kundenauftrag gruppieren, +Group by Sales Order,Nach Auftrag gruppieren, Sales Value,Verkaufswert, Stock Qty vs Serial No Count,Lagermenge vs Seriennummer, Serial No Count,Seriennummer nicht gezählt, @@ -9456,8 +9456,8 @@ Total Forecast (Future Data),Gesamtprognose (zukünftige Daten), Based On Document,Basierend auf Dokument, Based On Data ( in years ),Basierend auf Daten (in Jahren), Smoothing Constant,Glättungskonstante, -Please fill the Sales Orders table,Bitte füllen Sie die Tabelle Kundenaufträge aus, -Sales Orders Required,Kundenaufträge erforderlich, +Please fill the Sales Orders table,Bitte füllen Sie die Tabelle Aufträge aus, +Sales Orders Required,Aufträge erforderlich, Please fill the Material Requests table,Bitte füllen Sie die Materialanforderungstabelle aus, Material Requests Required,Materialanforderungen erforderlich, Items to Manufacture are required to pull the Raw Materials associated with it.,"Zu fertigende Gegenstände sind erforderlich, um die damit verbundenen Rohstoffe zu ziehen.", @@ -9486,7 +9486,7 @@ To date can not be greater than employee's relieving date.,Bisher kann das Entla Payroll date can not be greater than employee's relieving date.,Das Abrechnungsdatum darf nicht größer sein als das Entlastungsdatum des Mitarbeiters., Row #{0}: Please enter the result value for {1},Zeile # {0}: Bitte geben Sie den Ergebniswert für {1} ein, Mandatory Results,Obligatorische Ergebnisse, -Sales Invoice or Patient Encounter is required to create Lab Tests,Für die Erstellung von Labortests ist eine Verkaufsrechnung oder eine Patientenbegegnung erforderlich, +Sales Invoice or Patient Encounter is required to create Lab Tests,Für die Erstellung von Labortests ist eine Ausgangsrechnung oder eine Patientenbegegnung erforderlich, Insufficient Data,Unzureichende Daten, Lab Test(s) {0} created successfully,Labortest (e) {0} erfolgreich erstellt, Test :,Prüfung :, @@ -9634,16 +9634,16 @@ Time Between Operations (Mins),Zeit zwischen Operationen (Minuten), Default: 10 mins,Standard: 10 Minuten, Overproduction for Sales and Work Order,Überproduktion für Kunden- und Arbeitsauftrag, "Update BOM cost automatically via scheduler, based on the latest Valuation Rate/Price List Rate/Last Purchase Rate of raw materials","Aktualisieren Sie die Stücklistenkosten automatisch über den Planer, basierend auf der neuesten Bewertungsrate / Preislistenrate / letzten Kaufrate der Rohstoffe", -Purchase Order already created for all Sales Order items,Bestellung bereits für alle Kundenauftragspositionen angelegt, +Purchase Order already created for all Sales Order items,Bestellung bereits für alle Auftragspositionen angelegt, Select Items,Gegenstände auswählen, Against Default Supplier,Gegen Standardlieferanten, Auto close Opportunity after the no. of days mentioned above,Gelegenheit zum automatischen Schließen nach der Nr. der oben genannten Tage, -Is Sales Order Required for Sales Invoice & Delivery Note Creation?,Ist ein Kundenauftrag für die Erstellung von Kundenrechnungen und Lieferscheinen erforderlich?, -Is Delivery Note Required for Sales Invoice Creation?,Ist für die Erstellung der Verkaufsrechnung ein Lieferschein erforderlich?, +Is Sales Order Required for Sales Invoice & Delivery Note Creation?,Ist ein Auftrag für die Erstellung von Kundenrechnungen und Lieferscheinen erforderlich?, +Is Delivery Note Required for Sales Invoice Creation?,Ist für die Erstellung der Ausgangsrechnung ein Lieferschein erforderlich?, How often should Project and Company be updated based on Sales Transactions?,Wie oft sollten Projekt und Unternehmen basierend auf Verkaufstransaktionen aktualisiert werden?, Allow User to Edit Price List Rate in Transactions,Benutzer darf Preisliste in Transaktionen bearbeiten, Allow Item to Be Added Multiple Times in a Transaction,"Zulassen, dass ein Element in einer Transaktion mehrmals hinzugefügt wird", -Allow Multiple Sales Orders Against a Customer's Purchase Order,Erlauben Sie mehrere Kundenaufträge für die Bestellung eines Kunden, +Allow Multiple Sales Orders Against a Customer's Purchase Order,Erlauben Sie mehrere Aufträge für die Bestellung eines Kunden, Validate Selling Price for Item Against Purchase Rate or Valuation Rate,Überprüfen Sie den Verkaufspreis für den Artikel anhand der Kauf- oder Bewertungsrate, Hide Customer's Tax ID from Sales Transactions,Steuer-ID des Kunden vor Verkaufstransaktionen ausblenden, "The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.","Der Prozentsatz, den Sie mehr gegen die bestellte Menge erhalten oder liefern dürfen. Wenn Sie beispielsweise 100 Einheiten bestellt haben und Ihre Zulage 10% beträgt, können Sie 110 Einheiten erhalten.", @@ -9653,8 +9653,8 @@ Automatically Set Serial Nos Based on FIFO,Seriennummern basierend auf FIFO auto Set Qty in Transactions Based on Serial No Input,Stellen Sie die Menge in Transaktionen basierend auf Seriennummer ohne Eingabe ein, Raise Material Request When Stock Reaches Re-order Level,"Erhöhen Sie die Materialanforderung, wenn der Lagerbestand die Nachbestellmenge erreicht", Notify by Email on Creation of Automatic Material Request,Benachrichtigen Sie per E-Mail über die Erstellung einer automatischen Materialanforderung, -Allow Material Transfer from Delivery Note to Sales Invoice,Materialübertragung vom Lieferschein zur Verkaufsrechnung zulassen, -Allow Material Transfer from Purchase Receipt to Purchase Invoice,Materialübertragung vom Kaufbeleg zur Kaufrechnung zulassen, +Allow Material Transfer from Delivery Note to Sales Invoice,Materialübertragung vom Lieferschein zur Ausgangsrechnung zulassen, +Allow Material Transfer from Purchase Receipt to Purchase Invoice,Materialübertragung vom Kaufbeleg zur Eingangsrechnung zulassen, Freeze Stocks Older Than (Days),Aktien einfrieren älter als (Tage), Role Allowed to Edit Frozen Stock,Rolle darf eingefrorenes Material bearbeiten, The unallocated amount of Payment Entry {0} is greater than the Bank Transaction's unallocated amount,Der nicht zugewiesene Betrag der Zahlungseingabe {0} ist größer als der nicht zugewiesene Betrag der Banküberweisung, @@ -9694,7 +9694,7 @@ You had {} errors while creating opening invoices. Check {} for more details,Bei Error Occured,Fehler aufgetreten, Opening Invoice Creation In Progress,Öffnen der Rechnungserstellung läuft, Creating {} out of {} {},{} Aus {} {} erstellen, -(Serial No: {0}) cannot be consumed as it's reserverd to fullfill Sales Order {1}.,"(Seriennummer: {0}) kann nicht verwendet werden, da es zum Ausfüllen des Kundenauftrags {1} reserviert ist.", +(Serial No: {0}) cannot be consumed as it's reserverd to fullfill Sales Order {1}.,"(Seriennummer: {0}) kann nicht verwendet werden, da es zum Ausfüllen des Auftrags {1} reserviert ist.", Item {0} {1},Gegenstand {0} {1}, Last Stock Transaction for item {0} under warehouse {1} was on {2}.,Die letzte Lagertransaktion für Artikel {0} unter Lager {1} war am {2}., Stock Transactions for Item {0} under warehouse {1} cannot be posted before this time.,Lagertransaktionen für Artikel {0} unter Lager {1} können nicht vor diesem Zeitpunkt gebucht werden., @@ -9822,8 +9822,8 @@ Invalid Parent Account,Ungültiges übergeordnetes Konto, "If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.","Wenn Sie {0} {1} Gegenstand {2} wert sind, wird das Schema {3} auf den Gegenstand angewendet.", "As the field {0} is enabled, the field {1} is mandatory.","Da das Feld {0} aktiviert ist, ist das Feld {1} obligatorisch.", "As the field {0} is enabled, the value of the field {1} should be more than 1.","Wenn das Feld {0} aktiviert ist, sollte der Wert des Feldes {1} größer als 1 sein.", -Cannot deliver Serial No {0} of item {1} as it is reserved to fullfill Sales Order {2},"Die Seriennummer {0} von Artikel {1} kann nicht geliefert werden, da sie für die Erfüllung des Kundenauftrags {2} reserviert ist.", -"Sales Order {0} has reservation for the item {1}, you can only deliver reserved {1} against {0}.","Kundenauftrag {0} hat eine Reservierung für den Artikel {1}, Sie können reservierte {1} nur gegen {0} liefern.", +Cannot deliver Serial No {0} of item {1} as it is reserved to fullfill Sales Order {2},"Die Seriennummer {0} von Artikel {1} kann nicht geliefert werden, da sie für die Erfüllung des Auftrags {2} reserviert ist.", +"Sales Order {0} has reservation for the item {1}, you can only deliver reserved {1} against {0}.","Auftrag {0} hat eine Reservierung für den Artikel {1}, Sie können reservierte {1} nur gegen {0} liefern.", {0} Serial No {1} cannot be delivered,{0} Seriennummer {1} kann nicht zugestellt werden, Row {0}: Subcontracted Item is mandatory for the raw material {1},Zeile {0}: Unterauftragsartikel sind für den Rohstoff {1} obligatorisch., "As there are sufficient raw materials, Material Request is not required for Warehouse {0}.","Da genügend Rohstoffe vorhanden sind, ist für Warehouse {0} keine Materialanforderung erforderlich.", diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 1d8b3a8db6..feea2284b7 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -181,8 +181,6 @@ class TransactionBase(StatusUpdater): if len(child_table_values) > 1: self.set(default_field, None) - else: - self.set(default_field, list(child_table_values)[0]) def delete_events(ref_type, ref_name): events = frappe.db.sql_list(""" SELECT diff --git a/erpnext/utilities/workspace/utilities/utilities.json b/erpnext/utilities/workspace/utilities/utilities.json index 02a8af5d6c..5b81e039b1 100644 --- a/erpnext/utilities/workspace/utilities/utilities.json +++ b/erpnext/utilities/workspace/utilities/utilities.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Video\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Video\",\"col\":4}}]", "creation": "2020-09-10 12:21:22.335307", "docstatus": 0, "doctype": "Workspace", @@ -40,7 +40,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:03.350805", + "modified": "2022-01-13 17:50:10.067510", "modified_by": "Administrator", "module": "Utilities", "name": "Utilities", @@ -49,7 +49,7 @@ "public": 1, "restrict_to_domain": "", "roles": [], - "sequence_id": 30, + "sequence_id": 30.0, "shortcuts": [], "title": "Utilities" } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index faefb77a9c..f447fac736 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # frappe # https://github.com/frappe/frappe is installed during bench-init gocardless-pro~=1.22.0 -googlemaps # used in ERPNext, but dependency is defined in Frappe +googlemaps pandas~=1.1.5 plaid-python~=7.2.1 pycountry~=20.7.3