Merge pull request #25805 from ankush/refactor_item
refactor: item doctype
diff --git a/.github/helper/semgrep_rules/translate.py b/.github/helper/semgrep_rules/translate.py
index bd6cd91..9de6aa9 100644
--- a/.github/helper/semgrep_rules/translate.py
+++ b/.github/helper/semgrep_rules/translate.py
@@ -51,3 +51,11 @@
_("")
# ruleid: frappe-translation-empty-string
_('')
+
+
+class Test:
+ # ok: frappe-translation-python-splitting
+ def __init__(
+ args
+ ):
+ pass
diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml
index fa4ec9e..5f03fb9 100644
--- a/.github/helper/semgrep_rules/translate.yml
+++ b/.github/helper/semgrep_rules/translate.yml
@@ -44,8 +44,8 @@
pattern-either:
- pattern: _(...) + _(...)
- pattern: _("..." + "...")
- - pattern-regex: '_\([^\)]*\\\s*' # lines broken by `\`
- - pattern-regex: '_\(\s*\n' # line breaks allowed by python for using ( )
+ - pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\`
+ - pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( )
message: |
Do not split strings inside translate function. Do not concatenate using translate functions.
Please refer: https://frappeframework.com/docs/user/en/translations
diff --git a/.github/helper/semgrep_rules/ux.js b/.github/helper/semgrep_rules/ux.js
new file mode 100644
index 0000000..ae73f9c
--- /dev/null
+++ b/.github/helper/semgrep_rules/ux.js
@@ -0,0 +1,9 @@
+
+// ok: frappe-missing-translate-function-js
+frappe.msgprint('{{ _("Both login and password required") }}');
+
+// ruleid: frappe-missing-translate-function-js
+frappe.msgprint('What');
+
+// ok: frappe-missing-translate-function-js
+frappe.throw(' {{ _("Both login and password required") }}. ');
diff --git a/.github/helper/semgrep_rules/ux.py b/.github/helper/semgrep_rules/ux.py
index 4a74457..a00d3cd 100644
--- a/.github/helper/semgrep_rules/ux.py
+++ b/.github/helper/semgrep_rules/ux.py
@@ -2,30 +2,30 @@
from frappe import msgprint, throw, _
-# ruleid: frappe-missing-translate-function
+# ruleid: frappe-missing-translate-function-python
throw("Error Occured")
-# ruleid: frappe-missing-translate-function
+# ruleid: frappe-missing-translate-function-python
frappe.throw("Error Occured")
-# ruleid: frappe-missing-translate-function
+# ruleid: frappe-missing-translate-function-python
frappe.msgprint("Useful message")
-# ruleid: frappe-missing-translate-function
+# ruleid: frappe-missing-translate-function-python
msgprint("Useful message")
-# ok: frappe-missing-translate-function
+# ok: frappe-missing-translate-function-python
translatedmessage = _("Hello")
-# ok: frappe-missing-translate-function
+# ok: frappe-missing-translate-function-python
throw(translatedmessage)
-# ok: frappe-missing-translate-function
+# ok: frappe-missing-translate-function-python
msgprint(translatedmessage)
-# ok: frappe-missing-translate-function
+# ok: frappe-missing-translate-function-python
msgprint(_("Helpful message"))
-# ok: frappe-missing-translate-function
+# ok: frappe-missing-translate-function-python
frappe.throw(_("Error occured"))
diff --git a/.github/helper/semgrep_rules/ux.yml b/.github/helper/semgrep_rules/ux.yml
index ed06a6a..dd667f3 100644
--- a/.github/helper/semgrep_rules/ux.yml
+++ b/.github/helper/semgrep_rules/ux.yml
@@ -1,15 +1,30 @@
rules:
-- id: frappe-missing-translate-function
+- id: frappe-missing-translate-function-python
pattern-either:
- patterns:
- pattern: frappe.msgprint("...", ...)
- pattern-not: frappe.msgprint(_("..."), ...)
- - pattern-not: frappe.msgprint(__("..."), ...)
- patterns:
- pattern: frappe.throw("...", ...)
- pattern-not: frappe.throw(_("..."), ...)
- - pattern-not: frappe.throw(__("..."), ...)
message: |
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
- languages: [python, javascript, json]
+ languages: [python]
+ severity: ERROR
+
+- id: frappe-missing-translate-function-js
+ pattern-either:
+ - patterns:
+ - pattern: frappe.msgprint("...", ...)
+ - pattern-not: frappe.msgprint(__("..."), ...)
+ # ignore microtemplating e.g. msgprint("{{ _("server side translation") }}")
+ - pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...)
+ - patterns:
+ - pattern: frappe.throw("...", ...)
+ - pattern-not: frappe.throw(__("..."), ...)
+ # ignore microtemplating
+ - pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...)
+ message: |
+ All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
+ languages: [javascript]
severity: ERROR
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
deleted file mode 100644
index 84ecfb1..0000000
--- a/.github/workflows/ci-tests.yml
+++ /dev/null
@@ -1,108 +0,0 @@
-name: CI
-
-on: [pull_request, workflow_dispatch, push]
-
-jobs:
- test:
- runs-on: ubuntu-18.04
-
- strategy:
- fail-fast: false
-
- matrix:
- include:
- - TYPE: "server"
- JOB_NAME: "Server"
- RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --coverage
- - TYPE: "patch"
- JOB_NAME: "Patch"
- RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate
-
- name: ${{ matrix.JOB_NAME }}
-
- services:
- mysql:
- image: mariadb:10.3
- env:
- MYSQL_ALLOW_EMPTY_PASSWORD: YES
- ports:
- - 3306:3306
- options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
-
- steps:
- - name: Clone
- uses: actions/checkout@v2
-
- - name: Setup Python
- uses: actions/setup-python@v2
- with:
- python-version: 3.6
-
- - 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
-
- - name: Run Tests
- run: ${{ matrix.RUN_COMMAND }}
- env:
- TYPE: ${{ matrix.TYPE }}
-
- - name: Coverage - Pull Request
- if: matrix.TYPE == 'server' && github.event_name == 'pull_request'
- run: |
- cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
- cd ${GITHUB_WORKSPACE}
- pip install coveralls==2.2.0
- pip install coverage==4.5.4
- coveralls --service=github
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
- COVERALLS_SERVICE_NAME: github
-
- - name: Coverage - Push
- if: matrix.TYPE == 'server' && github.event_name == 'push'
- run: |
- cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
- cd ${GITHUB_WORKSPACE}
- pip install coveralls==2.2.0
- pip install coverage==4.5.4
- coveralls --service=github-actions
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
- COVERALLS_SERVICE_NAME: github-actions
-
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
new file mode 100644
index 0000000..7c9e027
--- /dev/null
+++ b/.github/workflows/patch.yml
@@ -0,0 +1,69 @@
+name: Patch
+
+on: [pull_request, workflow_dispatch]
+
+jobs:
+ test:
+ runs-on: ubuntu-18.04
+
+ name: Patch Test
+
+ services:
+ mysql:
+ image: mariadb:10.3
+ env:
+ MYSQL_ALLOW_EMPTY_PASSWORD: YES
+ ports:
+ - 3306:3306
+ options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
+
+ steps:
+ - name: Clone
+ uses: actions/checkout@v2
+
+ - name: Setup Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.6
+
+ - 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
+
+ - name: Run Patch Tests
+ run: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate
diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml
new file mode 100644
index 0000000..92685e2
--- /dev/null
+++ b/.github/workflows/server-tests.yml
@@ -0,0 +1,110 @@
+name: Server
+
+on: [pull_request, workflow_dispatch]
+
+jobs:
+ test:
+ runs-on: ubuntu-18.04
+
+ strategy:
+ fail-fast: false
+
+ matrix:
+ container: [1, 2, 3]
+
+ name: Python Unit Tests
+
+ services:
+ mysql:
+ image: mariadb:10.3
+ env:
+ MYSQL_ALLOW_EMPTY_PASSWORD: YES
+ ports:
+ - 3306:3306
+ options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
+
+ steps:
+ - name: Clone
+ uses: actions/checkout@v2
+
+ - name: Setup Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.7
+
+ - 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
+
+ - name: Run Tests
+ run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
+ env:
+ TYPE: server
+ CI_BUILD_ID: ${{ github.run_id }}
+ ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
+
+ - name: Upload Coverage Data
+ run: |
+ cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
+ cd ${GITHUB_WORKSPACE}
+ pip3 install coverage==5.5
+ pip3 install coveralls==3.0.1
+ coveralls
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COVERALLS_FLAG_NAME: run-${{ matrix.container }}
+ COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
+ COVERALLS_PARALLEL: true
+
+ coveralls:
+ name: Coverage Wrap Up
+ needs: test
+ container: python:3-slim
+ runs-on: ubuntu-18.04
+ steps:
+ - name: Clone
+ uses: actions/checkout@v2
+
+ - name: Coveralls Finished
+ run: |
+ cd ${GITHUB_WORKSPACE}
+ pip3 install coverage==5.5
+ pip3 install coveralls==3.0.1
+ coveralls --finish
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index 4b2ea0a..0000000
--- a/.pylintrc
+++ /dev/null
@@ -1 +0,0 @@
-disable=access-member-before-definition
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 0ebf0eb..7cd1e77 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -27,7 +27,7 @@
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
if exists and self.is_new():
- frappe.throw("Document Type already used as a dimension")
+ frappe.throw(_("Document Type already used as a dimension"))
if not self.is_new():
self.validate_document_type_change()
diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
index fc1d7e3..e657a9a 100644
--- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
@@ -7,7 +7,8 @@
import unittest
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
-from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import delete_accounting_dimension
+
+test_dependencies = ['Cost Center', 'Location', 'Warehouse', 'Department']
class TestAccountingDimension(unittest.TestCase):
def setUp(self):
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
index 7877abd..7f6254f 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
@@ -9,6 +9,8 @@
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
+test_dependencies = ['Location', 'Cost Center', 'Department']
+
class TestAccountingDimensionFilter(unittest.TestCase):
def setUp(self):
create_dimension()
diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
index 10cd939..dc472c7 100644
--- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
@@ -10,6 +10,8 @@
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+test_dependencies = ['Item']
+
class TestAccountingPeriod(unittest.TestCase):
def test_overlap(self):
ap1 = create_accounting_period(start_date = "2018-04-01",
@@ -38,7 +40,7 @@
accounting_period.start_date = args.start_date or nowdate()
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
accounting_period.company = args.company or "_Test Company"
- accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
+ accounting_period.period_name = args.period_name or "_Test_Period_Name_1"
accounting_period.append("closed_documents", {
"document_type": 'Sales Invoice', "closed": 1
})
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index 4d33880..ac4a2d6 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -5,6 +5,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.utils import cint
from frappe.model.document import Document
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
@@ -24,7 +25,7 @@
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
frappe.msgprint(
- "Stale Days should start from 1.", title='Error', indicator='red',
+ _("Stale Days should start from 1."), title='Error', indicator='red',
raise_exception=1)
def enable_payment_schedule_in_print(self):
diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 059e1d3..19041a3 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -120,4 +120,4 @@
plaid_success(token, response) {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
}
-};
+};
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index c5ec23c..603e21e 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -11,6 +11,8 @@
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
+test_dependencies = ['Monthly Distribution']
+
class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero(nowdate(), "cost_center")
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index f96f591..3b764aa 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -22,7 +22,7 @@
'allow_account_creation_against_child_company'])
if parent_company and (not allow_account_creation_against_child_company):
- msg = _("{} is a child company. ").format(frappe.bold(company))
+ msg = _("{} is a child company.").format(frappe.bold(company)) + " "
msg += _("Please import accounts against parent company or enable {} in company master.").format(
frappe.bold('Allow Account Creation Against Child Company'))
frappe.throw(msg, title=_('Wrong Company'))
@@ -56,7 +56,7 @@
extension = extension.lstrip(".")
if extension not in ('csv', 'xlsx', 'xls'):
- frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")
+ frappe.throw(_("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload"))
return file_doc, extension
@@ -293,7 +293,7 @@
accounts_dict = {}
for account in accounts:
accounts_dict.setdefault(account["account_name"], account)
- if not hasattr(account, "parent_account"):
+ if "parent_account" not in account:
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
msg += "<br><br>"
msg += _("Alternatively, you can download the template and fill your data in.")
diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py
index c5ce514..e2d4d82 100644
--- a/erpnext/accounts/doctype/dunning/test_dunning.py
+++ b/erpnext/accounts/doctype/dunning/test_dunning.py
@@ -29,7 +29,7 @@
self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
-
+
def test_gl_entries(self):
dunning = create_dunning()
dunning.submit()
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index fefab82..ed1bd28 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -39,7 +39,11 @@
self.validate_multi_currency()
self.set_amounts_in_company_currency()
self.validate_debit_credit_amount()
- self.validate_total_debit_and_credit()
+
+ # Do not validate while importing via data import
+ if not frappe.flags.in_import:
+ self.validate_total_debit_and_credit()
+
self.validate_against_jv()
self.validate_reference_doc()
self.set_against_account()
diff --git a/erpnext/accounts/doctype/journal_entry/regional/india.js b/erpnext/accounts/doctype/journal_entry/regional/india.js
new file mode 100644
index 0000000..75a69ac
--- /dev/null
+++ b/erpnext/accounts/doctype/journal_entry/regional/india.js
@@ -0,0 +1,17 @@
+frappe.ui.form.on("Journal Entry", {
+ refresh: function(frm) {
+ frm.set_query('company_address', function(doc) {
+ if(!doc.company) {
+ frappe.throw(__('Please set Company'));
+ }
+
+ return {
+ query: 'frappe.contacts.doctype.address.address.address_query',
+ filters: {
+ link_doctype: 'Company',
+ link_name: doc.company
+ }
+ };
+ });
+ }
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 8c5a34a..6418d73 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -107,7 +107,7 @@
frm.set_value("taxes", []);
for (let row of frm.doc.payment_reconciliation) {
- row.expected_amount = 0;
+ row.expected_amount = row.opening_amount;
}
for (let row of frm.doc.pos_transactions) {
@@ -154,6 +154,9 @@
function refresh_payments(d, frm) {
d.payments.forEach(p => {
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
+ if (p.account == d.account_for_change_amount) {
+ p.amount -= flt(d.change_amount);
+ }
if (payment) {
payment.expected_amount += flt(p.amount);
payment.difference = payment.closing_amount - payment.expected_amount;
diff --git a/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json
index 6e7768d..bbf1ba0 100644
--- a/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json
+++ b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json
@@ -46,6 +46,7 @@
"reqd": 1
},
{
+ "default": "0",
"fieldname": "closing_amount",
"fieldtype": "Currency",
"in_list_view": 1,
@@ -57,7 +58,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-10-23 16:45:43.662034",
+ "modified": "2021-05-19 20:08:44.523861",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry Detail",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 473db56..8ec4ef2 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -140,6 +140,7 @@
return
available_stock = get_stock_availability(d.item_code, d.warehouse)
+
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
if flt(available_stock) <= 0:
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
@@ -213,8 +214,9 @@
for d in self.get("items"):
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
if not is_stock_item:
- frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ")
- .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
+ if not frappe.db.exists('Product Bundle', d.item_code):
+ frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice.")
+ .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
def validate_mode_of_payment(self):
if len(self.payments) == 0:
@@ -455,32 +457,47 @@
@frappe.whitelist()
def get_stock_availability(item_code, warehouse):
- latest_sle = frappe.db.sql("""select qty_after_transaction
- from `tabStock Ledger Entry`
+ if frappe.db.get_value('Item', item_code, 'is_stock_item'):
+ bin_qty = get_bin_qty(item_code, warehouse)
+ pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
+ return bin_qty - pos_sales_qty
+ else:
+ if frappe.db.exists('Product Bundle', item_code):
+ return get_bundle_availability(item_code, warehouse)
+
+def get_bundle_availability(bundle_item_code, warehouse):
+ product_bundle = frappe.get_doc('Product Bundle', bundle_item_code)
+
+ bundle_bin_qty = 1000000
+ for item in product_bundle.items:
+ item_bin_qty = get_bin_qty(item.item_code, warehouse)
+ item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
+ available_qty = item_bin_qty - item_pos_reserved_qty
+
+ max_available_bundles = available_qty / item.qty
+ if bundle_bin_qty > max_available_bundles:
+ bundle_bin_qty = max_available_bundles
+
+ pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
+ return bundle_bin_qty - pos_sales_qty
+
+def get_bin_qty(item_code, warehouse):
+ bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
where item_code = %s and warehouse = %s
- order by posting_date desc, posting_time desc
limit 1""", (item_code, warehouse), as_dict=1)
- pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
-
- sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
-
- if sle_qty and pos_sales_qty:
- return sle_qty - pos_sales_qty
- else:
- return sle_qty
+ return bin_qty[0].actual_qty or 0 if bin_qty else 0
def get_pos_reserved_qty(item_code, warehouse):
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
where p.name = p_item.parent
- and p.consolidated_invoice is NULL
- and p.docstatus = 1
+ and ifnull(p.consolidated_invoice, '') = ''
and p_item.docstatus = 1
and p_item.item_code = %s
and p_item.warehouse = %s
""", (item_code, warehouse), as_dict=1)
-
+
return reserved_qty[0].qty or 0 if reserved_qty else 0
@frappe.whitelist()
@@ -528,4 +545,4 @@
mode_of_payment = pos_payment_method.mode_of_payment
if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]:
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
- append_payment(payment_mode[0])
\ No newline at end of file
+ append_payment(payment_mode[0])
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index bc78743..08e072e 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -42,8 +42,9 @@
if return_against_status != "Consolidated":
# if return entry is not getting merged in the current pos closing and if it is not consolidated
bold_unconsolidated = frappe.bold("not Consolidated")
- msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}. ")
+ msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}.")
.format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated))
+ msg += " "
msg += _("Original invoice should be consolidated before or along with the return invoice.")
msg += "<br><br>"
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
@@ -56,12 +57,12 @@
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
sales_invoice, credit_note = "", ""
- if sales:
- sales_invoice = self.process_merging_into_sales_invoice(sales)
-
if returns:
credit_note = self.process_merging_into_credit_note(returns)
+ if sales:
+ sales_invoice = self.process_merging_into_sales_invoice(sales)
+
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
@@ -274,9 +275,9 @@
closing_entry.db_set('error_message', '')
closing_entry.update_opening_entry()
- except Exception:
+ except Exception as e:
frappe.db.rollback()
- message_log = frappe.message_log.pop()
+ message_log = frappe.message_log.pop() if frappe.message_log else str(e)
error_message = safe_load_json(message_log)
if closing_entry:
@@ -300,9 +301,9 @@
closing_entry.db_set('error_message', '')
closing_entry.update_opening_entry(for_cancel=True)
- except Exception:
+ except Exception as e:
frappe.db.rollback()
- message_log = frappe.message_log.pop()
+ message_log = frappe.message_log.pop() if frappe.message_log else str(e)
error_message = safe_load_json(message_log)
if closing_entry:
@@ -348,11 +349,9 @@
return True
def safe_load_json(message):
- JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
-
try:
json_message = json.loads(message).get('message')
- except JSONDecodeError:
+ except Exception:
json_message = message
return json_message
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index aedf1c6..556f49d 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -152,7 +152,7 @@
frappe.throw(_("Valid from date must be less than valid upto date"))
def validate_condition(self):
- if self.condition and ("=" in self.condition) and re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", self.condition):
+ if self.condition and ("=" in self.condition) and re.match(r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+', self.condition):
frappe.throw(_("Invalid condition expression"))
#--------------------------------------------------------------------------------
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index f61aacb..7328f16 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -1,24 +1,42 @@
-<h1 class="text-center" style="page-break-before:always">{{ filters.party[0] }}</h1>
-<h3 class="text-center">{{ _("Statement of Accounts") }}</h3>
+<div class="page-break">
+ <div id="header-html" class="hidden-pdf">
+ {% if letter_head %}
+ <div class="letter-head text-center">{{ letter_head.content }}</div>
+ <hr style="height:2px;border-width:0;color:black;background-color:black;">
+ {% endif %}
+ </div>
+ <div id="footer-html" class="visible-pdf">
+ {% if letter_head.footer %}
+ <div class="letter-head-footer">
+ <hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
+ {{ letter_head.footer }}
+ </div>
+ {% endif %}
+ </div>
+ <h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
+ <div>
+ <h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5>
+ <h5 style="float: right;">
+ {{ _("Date: ") }}
+ <b>{{ frappe.format(filters.from_date, 'Date')}}
+ {{ _("to") }}
+ {{ frappe.format(filters.to_date, 'Date')}}</b>
+ </h5>
+ </div>
+ <br>
-<h5 class="text-center">
- {{ frappe.format(filters.from_date, 'Date')}}
- {{ _("to") }}
- {{ frappe.format(filters.to_date, 'Date')}}
-</h5>
-
-<table class="table table-bordered">
- <thead>
- <tr>
- <th style="width: 12%">{{ _("Date") }}</th>
- <th style="width: 15%">{{ _("Ref") }}</th>
- <th style="width: 25%">{{ _("Party") }}</th>
- <th style="width: 15%">{{ _("Debit") }}</th>
- <th style="width: 15%">{{ _("Credit") }}</th>
- <th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th>
- </tr>
- </thead>
- <tbody>
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th style="width: 12%">{{ _("Date") }}</th>
+ <th style="width: 15%">{{ _("Reference") }}</th>
+ <th style="width: 25%">{{ _("Remarks") }}</th>
+ <th style="width: 15%">{{ _("Debit") }}</th>
+ <th style="width: 15%">{{ _("Credit") }}</th>
+ <th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th>
+ </tr>
+ </thead>
+ <tbody>
{% for row in data %}
<tr>
{% if(row.posting_date) %}
@@ -58,32 +76,34 @@
</tr>
{% endfor %}
</tbody>
-</table>
-<br><br>
-{% if ageing %}
-<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}</h3>
-<h5 class="text-center">
- {{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
-</h5>
-<br>
-
-<table class="table table-bordered">
- <thead>
- <tr>
- <th style="width: 12%">30 Days</th>
- <th style="width: 15%">60 Days</th>
- <th style="width: 25%">90 Days</th>
- <th style="width: 15%">120 Days</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>{{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}</td>
- <td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
- <td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
- <td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
- </tr>
- </tbody>
-</table>
-{% endif %}
-<p class="text-right text-muted">Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}</p>
\ No newline at end of file
+ </table>
+ <br>
+ {% if ageing %}
+ <h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
+ {{ _("up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
+ </h4>
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th style="width: 25%">30 Days</th>
+ <th style="width: 25%">60 Days</th>
+ <th style="width: 25%">90 Days</th>
+ <th style="width: 25%">120 Days</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>{{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}</td>
+ <td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
+ <td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
+ <td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
+ </tr>
+ </tbody>
+ </table>
+ {% endif %}
+ {% if terms_and_conditions %}
+ <div>
+ {{ terms_and_conditions }}
+ </div>
+ {% endif %}
+</div>
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
index 6dc4643..088c190 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
@@ -19,7 +19,7 @@
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
}
else{
- frappe.msgprint('No Records for these settings.')
+ frappe.msgprint(__('No Records for these settings.'))
}
}
});
@@ -33,7 +33,7 @@
type: 'GET',
success: function(result) {
if(jQuery.isEmptyObject(result)){
- frappe.msgprint('No Records for these settings.');
+ frappe.msgprint(__('No Records for these settings.'));
}
else{
window.location = url;
@@ -92,7 +92,7 @@
frm.refresh_field('customers');
}
else{
- frappe.throw('No Customers found with selected options.');
+ frappe.throw(__('No Customers found with selected options.'));
}
}
}
@@ -129,4 +129,4 @@
}
})
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
index 4be0e2e..27a5f50 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
@@ -1,6 +1,5 @@
{
"actions": [],
- "allow_workflow": 1,
"autoname": "Prompt",
"creation": "2020-05-22 16:46:18.712954",
"doctype": "DocType",
@@ -28,9 +27,11 @@
"customers",
"preferences",
"orientation",
- "section_break_14",
"include_ageing",
"ageing_based_on",
+ "section_break_14",
+ "letter_head",
+ "terms_and_conditions",
"section_break_1",
"enable_auto_email",
"section_break_18",
@@ -270,10 +271,22 @@
"fieldname": "body",
"fieldtype": "Text Editor",
"label": "Body"
+ },
+ {
+ "fieldname": "letter_head",
+ "fieldtype": "Link",
+ "label": "Letter Head",
+ "options": "Letter Head"
+ },
+ {
+ "fieldname": "terms_and_conditions",
+ "fieldtype": "Link",
+ "label": "Terms and Conditions",
+ "options": "Terms and Conditions"
}
],
"links": [],
- "modified": "2020-08-08 08:47:09.185728",
+ "modified": "2021-05-21 10:14:22.426672",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
index a0dbff3..0b0ee90 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -64,6 +64,9 @@
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \
or doc.currency or get_company_currency(doc.company)
+ if doc.letter_head:
+ from frappe.www.printview import get_letter_head
+ letter_head = get_letter_head(doc, 0)
filters= frappe._dict({
'from_date': doc.from_date,
@@ -91,7 +94,10 @@
continue
html = frappe.render_template(template_path, \
- {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None})
+ {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None,
+ "letter_head": letter_head if doc.letter_head else None,
+ "terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms')
+ if doc.terms_and_conditions else None})
html = frappe.render_template(base_template_path, {"body": html, \
"css": get_print_style(), "title": "Statement For " + entry.customer})
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 66be11f..53db689 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -636,8 +636,8 @@
def test_rejected_serial_no(self):
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
- rejected_qty=1, rate=500, update_stock=1,
- rejected_warehouse = "_Test Rejected Warehouse - _TC")
+ rejected_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC",
+ allow_zero_valuation_rate=1)
self.assertEqual(frappe.db.get_value("Serial No", pi.get("items")[0].serial_no, "warehouse"),
pi.get("items")[0].warehouse)
@@ -994,7 +994,8 @@
"project": args.project,
"rejected_warehouse": args.rejected_warehouse or "",
"rejected_serial_no": args.rejected_serial_no or "",
- "asset_location": args.location or ""
+ "asset_location": args.location or "",
+ "allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0
})
if args.get_taxes_and_charges:
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index a008742..023f4b0 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1137,7 +1137,6 @@
"""
self.set_serial_no_against_delivery_note()
self.validate_serial_against_delivery_note()
- self.validate_serial_against_sales_invoice()
def set_serial_no_against_delivery_note(self):
for item in self.items:
@@ -1168,26 +1167,6 @@
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
item.idx, item.qty, item.item_code, len(si_serial_nos)))
- def validate_serial_against_sales_invoice(self):
- """ check if serial number is already used in other sales invoice """
- for item in self.items:
- if not item.serial_no:
- continue
-
- for serial_no in item.serial_no.split("\n"):
- serial_no_details = frappe.db.get_value("Serial No", serial_no,
- ["sales_invoice", "item_code"], as_dict=1)
-
- if not serial_no_details:
- continue
-
- if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \
- and self.name != serial_no_details.sales_invoice:
- sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company")
- if sales_invoice_company == self.company:
- frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}")
- .format(serial_no, serial_no_details.sales_invoice))
-
def update_project(self):
if self.project:
project = frappe.get_doc("Project", self.project)
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 9059d0b..df6d483 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -933,12 +933,6 @@
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0],
"delivery_document_no"), si.name)
- self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"),
- si.name)
-
- # check if the serial number is already linked with any other Sales Invoice
- _si = frappe.copy_doc(si.as_dict())
- self.assertRaises(frappe.ValidationError, _si.insert)
return si
diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
index fe2bc72..ff87276 100644
--- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
@@ -165,7 +165,7 @@
if profit_data:
profit_data.update({
"indent": 1,
- "parent_account": get_mapper_for(light_mappers, position=0)['section_header']
+ "parent_account": get_mapper_for(light_mappers, position=1)['section_header']
})
data.append(profit_data)
section_data.append(profit_data)
@@ -312,10 +312,10 @@
def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper):
data = []
- operating_activities_mapper = get_mapper_for(light_mappers, position=0)
+ operating_activities_mapper = get_mapper_for(light_mappers, position=1)
other_mappers = [
- get_mapper_for(light_mappers, position=1),
- get_mapper_for(light_mappers, position=2)
+ get_mapper_for(light_mappers, position=2),
+ get_mapper_for(light_mappers, position=3)
]
if operating_activities_mapper:
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index fb0d359..84f7868 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -166,6 +166,11 @@
"fieldname": "show_cancelled_entries",
"label": __("Show Cancelled Entries"),
"fieldtype": "Check"
+ },
+ {
+ "fieldname": "show_net_values_in_party_account",
+ "label": __("Show Net Values in Party Account"),
+ "fieldtype": "Check"
}
]
}
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index b5d7992..562df4f 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -344,6 +344,9 @@
consolidated_gle = OrderedDict()
group_by = group_by_field(filters.get('group_by'))
+ if filters.get('show_net_values_in_party_account'):
+ account_type_map = get_account_type_map(filters.get('company'))
+
def update_value_in_dict(data, key, gle):
data[key].debit += flt(gle.debit)
data[key].credit += flt(gle.credit)
@@ -351,6 +354,24 @@
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
+ if filters.get('show_net_values_in_party_account') and \
+ account_type_map.get(data[key].account) in ('Receivable', 'Payable'):
+ net_value = flt(data[key].debit) - flt(data[key].credit)
+ net_value_in_account_currency = flt(data[key].debit_in_account_currency) \
+ - flt(data[key].credit_in_account_currency)
+
+ if net_value < 0:
+ dr_or_cr = 'credit'
+ rev_dr_or_cr = 'debit'
+ else:
+ dr_or_cr = 'debit'
+ rev_dr_or_cr = 'credit'
+
+ data[key][dr_or_cr] = abs(net_value)
+ data[key][dr_or_cr+'_in_account_currency'] = abs(net_value_in_account_currency)
+ data[key][rev_dr_or_cr] = 0
+ data[key][rev_dr_or_cr+'_in_account_currency'] = 0
+
if data[key].against_voucher and gle.against_voucher:
data[key].against_voucher += ', ' + gle.against_voucher
@@ -388,6 +409,12 @@
return totals, entries
+def get_account_type_map(company):
+ account_type_map = frappe._dict(frappe.get_all('Account', fields=['name', 'account_type'],
+ filters={'company': company}, as_list=1))
+
+ return account_type_map
+
def get_result_as_list(data, filters):
balance, balance_in_account_currency = 0, 0
inv_details = get_supplier_invoice_details()
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index 9de8d19..b020d0a 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -81,8 +81,7 @@
presentation_currency = currency_info['presentation_currency']
company_currency = currency_info['company_currency']
- pl_accounts = [d.name for d in frappe.get_list('Account',
- filters={'report_type': 'Profit and Loss', 'company': company})]
+ account_currencies = list(set(entry['account_currency'] for entry in gl_entries))
for entry in gl_entries:
account = entry['account']
@@ -92,10 +91,15 @@
credit_in_account_currency = flt(entry['credit_in_account_currency'])
account_currency = entry['account_currency']
- if account_currency != presentation_currency:
- value = debit or credit
+ if len(account_currencies) == 1 and account_currency == presentation_currency:
+ if entry.get('debit'):
+ entry['debit'] = debit_in_account_currency
- date = entry['posting_date'] if account in pl_accounts else currency_info['report_date']
+ if entry.get('credit'):
+ entry['credit'] = credit_in_account_currency
+ else:
+ value = debit or credit
+ date = currency_info['report_date']
converted_value = convert(value, presentation_currency, company_currency, date)
if entry.get('debit'):
@@ -104,13 +108,6 @@
if entry.get('credit'):
entry['credit'] = converted_value
- elif account_currency == presentation_currency:
- if entry.get('debit'):
- entry['debit'] = debit_in_account_currency
-
- if entry.get('credit'):
- entry['credit'] = credit_in_account_currency
-
converted_gl_list.append(entry)
return converted_gl_list
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 30a270c..3cd4b80 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -566,7 +566,7 @@
doc = make_invoice(pr.name)
self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
-
+
def test_asset_cwip_toggling_cases(self):
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
index 6900938..c1fc6fb 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
@@ -9,12 +9,12 @@
from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import execute
import json, frappe, unittest
-class TestSubcontractedItemToBeReceived(unittest.TestCase):
+class TestSubcontractedItemToBeTransferred(unittest.TestCase):
- def test_pending_and_received_qty(self):
+ def test_pending_and_transferred_qty(self):
po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')
- make_stock_entry(item_code='_Test Item', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100)
- make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100)
+ make_stock_entry(item_code='_Test Item', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
+ make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
transfer_subcontracted_raw_materials(po.name)
col, data = execute(filters=frappe._dict({'supplier': po.supplier,
'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
@@ -38,7 +38,8 @@
'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}]
rm_item_string = json.dumps(rm_item)
se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string))
+ se.from_warehouse = '_Test Warehouse 1 - _TC'
se.to_warehouse = '_Test Warehouse 1 - _TC'
se.stock_entry_type = 'Send to Subcontractor'
se.save()
- se.submit()
\ No newline at end of file
+ se.submit()
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 996c4ed..401dfdf 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -225,7 +225,7 @@
def validate_date_with_fiscal_year(self):
if self.meta.get_field("fiscal_year"):
- date_field = ""
+ date_field = None
if self.meta.get_field("posting_date"):
date_field = "posting_date"
elif self.meta.get_field("transaction_date"):
@@ -1011,7 +1011,6 @@
else:
grand_total -= self.get("total_advance")
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
- print(grand_total, base_grand_total)
if total != flt(grand_total, self.precision("grand_total")) or \
base_total != flt(base_grand_total, self.precision("base_grand_total")):
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
@@ -1450,6 +1449,7 @@
for d in deleted_children:
update_bin_on_delete(d, parent.doctype)
+
@frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
def check_doc_permissions(doc, perm_type='create'):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 46301b7..638503e 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -204,7 +204,7 @@
if "description" in searchfields:
searchfields.remove("description")
-
+
columns = ''
extra_searchfields = [field for field in searchfields
if not field in ["name", "item_group", "description"]]
@@ -216,9 +216,10 @@
if not field in searchfields]
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
- if filters.get('supplier'):
- item_group_list = frappe.get_all('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
-
+ if filters and isinstance(filters, dict) and filters.get('supplier'):
+ item_group_list = frappe.get_all('Supplier Item Group',
+ filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
+
item_groups = []
for i in item_group_list:
item_groups.append(i.item_group)
@@ -227,7 +228,7 @@
if item_groups:
filters['item_group'] = ['in', item_groups]
-
+
description_cond = ''
if frappe.db.count('Item', cache=True) < 50000:
# scan description only if items are less than 50000
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index ed3aee5..83d4c33 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -76,12 +76,12 @@
["Stopped", "eval:self.status == 'Stopped'"],
["Cancelled", "eval:self.docstatus == 2"],
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
- ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
+ ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
],
"Bank Transaction": [
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 41ca404..0da723d 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -1,17 +1,21 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-import frappe, erpnext
-from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
-from frappe import _
-import frappe.defaults
+import json
from collections import defaultdict
-from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
+
+import frappe
+import frappe.defaults
+from frappe import _
+from frappe.utils import cint, cstr, flt, get_link_to_form, getdate
+
+import erpnext
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
+from erpnext.accounts.utils import check_if_stock_and_account_balance_synced, get_fiscal_year
from erpnext.controllers.accounts_controller import AccountsController
-from erpnext.stock.stock_ledger import get_valuation_rate
from erpnext.stock import get_warehouse_account_map
+from erpnext.stock.stock_ledger import get_valuation_rate
+
class QualityInspectionRequiredError(frappe.ValidationError): pass
class QualityInspectionRejectedError(frappe.ValidationError): pass
@@ -189,7 +193,6 @@
if hasattr(self, "items"):
item_doclist = self.get("items")
elif self.doctype == "Stock Reconciliation":
- import json
item_doclist = []
data = json.loads(self.reconciliation_json)
for row in data[data.index(self.head_row)+1:]:
@@ -319,7 +322,7 @@
return serialized_items
def validate_warehouse(self):
- from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse
+ from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company
warehouses = list(set([d.warehouse for d in
self.get("items") if getattr(d, "warehouse", None)]))
@@ -498,6 +501,39 @@
check_if_stock_and_account_balance_synced(self.posting_date,
self.company, self.doctype, self.name)
+
+@frappe.whitelist()
+def make_quality_inspections(doctype, docname, items):
+ if isinstance(items, str):
+ items = json.loads(items)
+
+ inspections = []
+ for item in items:
+ if flt(item.get("sample_size")) > flt(item.get("qty")):
+ frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format(
+ item_name=item.get("item_name"),
+ sample_size=item.get("sample_size"),
+ accepted_quantity=item.get("qty")
+ ))
+
+ quality_inspection = frappe.get_doc({
+ "doctype": "Quality Inspection",
+ "inspection_type": "Incoming",
+ "inspected_by": frappe.session.user,
+ "reference_type": doctype,
+ "reference_name": docname,
+ "item_code": item.get("item_code"),
+ "description": item.get("description"),
+ "sample_size": flt(item.get("sample_size")),
+ "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None,
+ "batch_no": item.get("batch_no")
+ }).insert()
+ quality_inspection.save()
+ inspections.append(quality_inspection.name)
+
+ return inspections
+
+
def is_reposting_pending():
return frappe.db.exists("Repost Item Valuation",
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 2009ebf..df73f09 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -38,7 +38,7 @@
number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents')
if not number_of_agents == 0:
if (number_of_appointments_in_same_slot >= number_of_agents):
- frappe.throw('Time slot is not available')
+ frappe.throw(_('Time slot is not available'))
# Link lead
if not self.party:
lead = self.find_lead_by_email()
@@ -75,10 +75,10 @@
subject=_('Appointment Confirmation'))
if frappe.session.user == "Guest":
frappe.msgprint(
- 'Please check your email to confirm the appointment')
+ _('Please check your email to confirm the appointment'))
else :
frappe.msgprint(
- 'Appointment was created. But no lead was found. Please check the email to confirm')
+ _('Appointment was created. But no lead was found. Please check the email to confirm'))
def on_change(self):
# Sync Calendar
@@ -91,7 +91,7 @@
def set_verified(self, email):
if not email == self.customer_email:
- frappe.throw('Email verification failed.')
+ frappe.throw(_('Email verification failed.'))
# Create new lead
self.create_lead_and_link()
# Remove unverified status
@@ -184,7 +184,7 @@
appointment_event.insert(ignore_permissions=True)
self.calendar_event = appointment_event.name
self.save(ignore_permissions=True)
-
+
def _get_verify_url(self):
verify_route = '/book_appointment/verify'
params = {
diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
index 6a0dcf4..0f2ea96 100644
--- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
+++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
@@ -75,7 +75,7 @@
"""Validates if Course Start Date is greater than Course End Date"""
if self.course_start_date > self.course_end_date:
frappe.throw(
- "Course Start Date cannot be greater than Course End Date.")
+ _("Course Start Date cannot be greater than Course End Date."))
def delete_course_schedule(self, rescheduled, reschedule_errors):
"""Delete all course schedule within the Date range and specified filters"""
diff --git a/erpnext/education/doctype/fees/test_fees.py b/erpnext/education/doctype/fees/test_fees.py
index eedc2ae..c6bb704 100644
--- a/erpnext/education/doctype/fees/test_fees.py
+++ b/erpnext/education/doctype/fees/test_fees.py
@@ -9,8 +9,7 @@
from frappe.utils.make_random import get_random
from erpnext.education.doctype.program.test_program import make_program_and_linked_courses
-# test_records = frappe.get_test_records('Fees')
-
+test_dependencies = ['Company']
class TestFees(unittest.TestCase):
def test_fees(self):
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index 2dc0f63..6be9e71 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -74,7 +74,6 @@
student_user.flags.ignore_permissions = True
student_user.add_roles("Student")
student_user.save()
- update_password_link = student_user.reset_password()
def update_applicant_status(self):
"""Updates Student Applicant status to Admitted"""
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
index f713684..7fd3b34 100755
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
@@ -7,6 +7,7 @@
from __future__ import unicode_literals
import urllib
+from urllib.parse import quote
import hashlib
import hmac
import base64
@@ -68,8 +69,9 @@
"""
md = hashlib.md5()
md.update(string)
- return base64.encodestring(md.digest()).strip('\n') if six.PY2 \
- else base64.encodebytes(md.digest()).decode().strip()
+ return base64.encodebytes(md.digest()).decode().strip()
+
+
def remove_empty(d):
"""
@@ -177,7 +179,6 @@
'SignatureMethod': 'HmacSHA256',
}
params.update(extra_data)
- quote = urllib.quote if six.PY2 else urllib.parse.quote
request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)])
signature = self.calc_signature(method, request_description)
url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature))
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
index d370fbc..3c2e59a 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
@@ -81,7 +81,7 @@
integration_request.reload()
self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
self.assertEqual(integration_request.status, "Completed")
-
+
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
integration_request.delete()
pr.reload()
@@ -139,7 +139,7 @@
pr.cancel()
pr.delete()
pos_invoice.delete()
-
+
def test_processing_of_only_one_succes_callback_payload(self):
create_mpesa_settings(payment_gateway_name="Payment")
mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
index 5f990cd..42d4b9b 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
@@ -99,5 +99,7 @@
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
transactions.extend(response["transactions"])
return transactions
+ except ItemError as e:
+ raise e
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
index bbc2ca8..37bf282 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
@@ -16,6 +16,10 @@
new erpnext.integrations.plaidLink(frm);
});
+ frm.add_custom_button(__('Reset Plaid Link'), () => {
+ new erpnext.integrations.plaidLink(frm);
+ });
+
frm.add_custom_button(__("Sync Now"), () => {
frappe.call({
method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index 21f1db6..3ef069b 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -12,6 +12,7 @@
from frappe.model.document import Document
from frappe.utils import add_months, formatdate, getdate, today
+from plaid.errors import ItemError
class PlaidSettings(Document):
@staticmethod
@@ -51,7 +52,7 @@
})
bank.insert()
except Exception:
- frappe.throw(frappe.get_traceback())
+ frappe.log_error(frappe.get_traceback(), title=_('Plaid Link Error'))
else:
bank = frappe.get_doc("Bank", response["institution"]["name"])
bank.plaid_access_token = access_token
@@ -83,7 +84,12 @@
if not acc_subtype:
add_account_subtype(account["subtype"])
- if not frappe.db.exists("Bank Account", dict(integration_id=account["id"])):
+ existing_bank_account = frappe.db.exists("Bank Account", {
+ 'account_name': account["name"],
+ 'bank': bank["bank_name"]
+ })
+
+ if not existing_bank_account:
try:
new_account = frappe.get_doc({
"doctype": "Bank Account",
@@ -103,10 +109,27 @@
except frappe.UniqueValidationError:
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
except Exception:
- frappe.throw(frappe.get_traceback())
+ frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error"))
+ frappe.throw(_("There was an error creating Bank Account while linking with Plaid."),
+ title=_("Plaid Link Failed"))
else:
- result.append(frappe.db.get_value("Bank Account", dict(integration_id=account["id"]), "name"))
+ try:
+ existing_account = frappe.get_doc('Bank Account', existing_bank_account)
+ existing_account.update({
+ "bank": bank["bank_name"],
+ "account_name": account["name"],
+ "account_type": account.get("type", ""),
+ "account_subtype": account.get("subtype", ""),
+ "mask": account.get("mask", ""),
+ "integration_id": account["id"]
+ })
+ existing_account.save()
+ result.append(existing_bank_account)
+ except Exception:
+ frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error"))
+ frappe.throw(_("There was an error updating Bank Account {} while linking with Plaid.").format(
+ existing_bank_account), title=_("Plaid Link Failed"))
return result
@@ -172,9 +195,16 @@
account_id = None
plaid = PlaidConnector(access_token)
- transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
- return transactions
+ try:
+ transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
+ except ItemError as e:
+ if e.code == "ITEM_LOGIN_REQUIRED":
+ msg = _("There was an error syncing transactions.") + " "
+ msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
+ frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
+
+ return transactions or []
def new_bank_transaction(transaction):
@@ -183,11 +213,11 @@
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
if float(transaction["amount"]) >= 0:
- debit = float(transaction["amount"])
- credit = 0
- else:
debit = 0
- credit = abs(float(transaction["amount"]))
+ credit = float(transaction["amount"])
+ else:
+ debit = abs(float(transaction["amount"]))
+ credit = 0
status = "Pending" if transaction["pending"] == "True" else "Settled"
diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
index d079bed..113fa51 100644
--- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
@@ -29,7 +29,7 @@
self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
patient, medical_department, practitioner = create_healthcare_docs()
- appointment = create_appointment(patient, practitioner, nowdate())
+ appointment = create_appointment(patient, practitioner, nowdate())
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
session = frappe.get_doc(session)
session.submit()
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 0bf551e..cee6f37 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -4,8 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
- comma_or, get_fullname, add_days, nowdate, get_datetime_str
+from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate
from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
@@ -85,7 +84,7 @@
def validate_dates(self):
if frappe.db.get_single_value("HR Settings", "restrict_backdated_leave_application"):
- if self.from_date and self.from_date < frappe.utils.today():
+ if self.from_date and getdate(self.from_date) < getdate():
allowed_role = frappe.db.get_single_value("HR Settings", "role_allowed_to_create_backdated_leave_application")
if allowed_role not in frappe.get_roles():
frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role))
@@ -248,9 +247,9 @@
self.throw_overlap_error(d)
def throw_overlap_error(self, d):
- msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
- d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
- + """ <b><a href="/app/Form/Leave Application/{0}">{0}</a></b>""".format(d["name"])
+ form_link = get_link_to_form("Leave Application", d.name)
+ msg = _("Employee {0} has already applied for {1} between {2} and {3} : {4}").format(self.employee,
+ d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date']), form_link)
frappe.throw(msg, OverlapError)
def get_total_leaves_on_half_day(self):
@@ -356,7 +355,7 @@
sender = dict()
sender['email'] = frappe.get_doc('User', frappe.session.user).email
- sender['full_name'] = frappe.utils.get_fullname(sender['email'])
+ sender['full_name'] = get_fullname(sender['email'])
try:
frappe.sendmail(
@@ -823,4 +822,4 @@
leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
- return leave_approver
\ No newline at end of file
+ return leave_approver
diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js
index 12bc920..b7d34b1 100644
--- a/erpnext/hr/doctype/training_event/training_event.js
+++ b/erpnext/hr/doctype/training_event/training_event.js
@@ -2,23 +2,41 @@
// For license information, please see license.txt
frappe.ui.form.on('Training Event', {
- onload_post_render: function(frm) {
+ onload_post_render: function (frm) {
frm.get_field("employees").grid.set_multiple_add("employee");
},
- refresh: function(frm) {
- if(!frm.doc.__islocal) {
- frm.add_custom_button(__("Training Result"), function() {
+ refresh: function (frm) {
+ if (!frm.doc.__islocal) {
+ frm.add_custom_button(__("Training Result"), function () {
frappe.route_options = {
training_event: frm.doc.name
- }
+ };
frappe.set_route("List", "Training Result");
});
- frm.add_custom_button(__("Training Feedback"), function() {
+ frm.add_custom_button(__("Training Feedback"), function () {
frappe.route_options = {
training_event: frm.doc.name
- }
+ };
frappe.set_route("List", "Training Feedback");
});
}
}
});
+
+frappe.ui.form.on("Training Event Employee", {
+ employee: function (frm) {
+ let emp = [];
+ for (let d in frm.doc.employees) {
+ if (frm.doc.employees[d].employee) {
+ emp.push(frm.doc.employees[d].employee);
+ }
+ }
+ frm.set_query("employee", "employees", function () {
+ return {
+ filters: {
+ name: ["NOT IN", emp]
+ }
+ };
+ });
+ }
+});
diff --git a/erpnext/hr/doctype/training_event_employee/training_event_employee.json b/erpnext/hr/doctype/training_event_employee/training_event_employee.json
index e3a4064..2d313e9 100644
--- a/erpnext/hr/doctype/training_event_employee/training_event_employee.json
+++ b/erpnext/hr/doctype/training_event_employee/training_event_employee.json
@@ -1,241 +1,80 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-08-08 05:33:39.965305",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-08-08 05:33:39.965305",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "employee",
+ "employee_name",
+ "department",
+ "column_break_3",
+ "status",
+ "attendance",
+ "is_mandatory"
+ ],
"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": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Employee",
+ "options": "Employee"
+ },
{
- "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": 0,
- "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",
+ "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,
- "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": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Open",
- "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": 1,
- "options": "Open\nInvited\nCompleted\nFeedback Submitted",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "default": "Open",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Open\nInvited\nCompleted\nFeedback Submitted"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "attendance",
- "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": "Attendance",
- "length": 0,
- "no_copy": 0,
- "options": "Mandatory\nOptional",
- "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": "attendance",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Attendance",
+ "options": "Present\nAbsent"
+ },
+ {
+ "columns": 2,
+ "default": "1",
+ "fieldname": "is_mandatory",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Is Mandatory"
}
- ],
- "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": "2019-01-30 11:28:16.170333",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Training Event Employee",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-21 12:41:59.336237",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Training Event Employee",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
index 6e151d0..03b0cf3 100644
--- a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
@@ -5,11 +5,18 @@
import frappe
import unittest
+import erpnext
from frappe.utils import getdate
from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data
from erpnext.hr.doctype.employee.test_employee import make_employee
+test_dependencies = ['Holiday List']
+
class TestUploadAttendance(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List')
+
def test_date_range(self):
employee = make_employee("test_employee@company.com")
employee_doc = frappe.get_doc("Employee", employee)
diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
index cf0048c..ed52c4e 100644
--- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
@@ -5,7 +5,7 @@
import frappe
import unittest
-from frappe.utils import nowdate,flt, cstr,random_string
+from frappe.utils import nowdate, flt, cstr, random_string
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
@@ -18,23 +18,13 @@
self.employee_id = make_employee("testdriver@example.com", company="_Test Company")
self.license_plate = get_vehicle(self.employee_id)
-
+
def tearDown(self):
frappe.delete_doc("Vehicle", self.license_plate, force=1)
frappe.delete_doc("Employee", self.employee_id, force=1)
def test_make_vehicle_log_and_syncing_of_odometer_value(self):
- vehicle_log = frappe.get_doc({
- "doctype": "Vehicle Log",
- "license_plate": cstr(self.license_plate),
- "employee": self.employee_id,
- "date":frappe.utils.nowdate(),
- "odometer":5010,
- "fuel_qty":frappe.utils.flt(50),
- "price": frappe.utils.flt(500)
- })
- vehicle_log.save()
- vehicle_log.submit()
+ vehicle_log = make_vehicle_log(self.license_plate, self.employee_id)
#checking value of vehicle odometer value on submit.
vehicle = frappe.get_doc("Vehicle", self.license_plate)
@@ -51,19 +41,9 @@
self.assertEqual(vehicle.last_odometer, current_odometer - distance_travelled)
vehicle_log.delete()
-
+
def test_vehicle_log_fuel_expense(self):
- vehicle_log = frappe.get_doc({
- "doctype": "Vehicle Log",
- "license_plate": cstr(self.license_plate),
- "employee": self.employee_id,
- "date": frappe.utils.nowdate(),
- "odometer":5010,
- "fuel_qty":frappe.utils.flt(50),
- "price": frappe.utils.flt(500)
- })
- vehicle_log.save()
- vehicle_log.submit()
+ vehicle_log = make_vehicle_log(self.license_plate, self.employee_id)
expense_claim = make_expense_claim(vehicle_log.name)
fuel_expense = expense_claim.expenses[0].amount
@@ -73,6 +53,18 @@
frappe.delete_doc("Expense Claim", expense_claim.name)
frappe.delete_doc("Vehicle Log", vehicle_log.name)
+ def test_vehicle_log_with_service_expenses(self):
+ vehicle_log = make_vehicle_log(self.license_plate, self.employee_id, with_services=True)
+
+ expense_claim = make_expense_claim(vehicle_log.name)
+ expenses = expense_claim.expenses[0].amount
+ self.assertEqual(expenses, 27000)
+
+ vehicle_log.cancel()
+ frappe.delete_doc("Expense Claim", expense_claim.name)
+ frappe.delete_doc("Vehicle Log", vehicle_log.name)
+
+
def get_vehicle(employee_id):
license_plate=random_string(10).upper()
vehicle = frappe.get_doc({
@@ -81,15 +73,46 @@
"make": "Maruti",
"model": "PCM",
"employee": employee_id,
- "last_odometer":5000,
- "acquisition_date":frappe.utils.nowdate(),
+ "last_odometer": 5000,
+ "acquisition_date": nowdate(),
"location": "Mumbai",
"chassis_no": "1234ABCD",
"uom": "Litre",
- "vehicle_value":frappe.utils.flt(500000)
+ "vehicle_value": flt(500000)
})
try:
vehicle.insert()
except frappe.DuplicateEntryError:
pass
- return license_plate
\ No newline at end of file
+ return license_plate
+
+
+def make_vehicle_log(license_plate, employee_id, with_services=False):
+ vehicle_log = frappe.get_doc({
+ "doctype": "Vehicle Log",
+ "license_plate": cstr(license_plate),
+ "employee": employee_id,
+ "date": nowdate(),
+ "odometer": 5010,
+ "fuel_qty": flt(50),
+ "price": flt(500)
+ })
+
+ if with_services:
+ vehicle_log.append("service_detail", {
+ "service_item": "Oil Change",
+ "type": "Inspection",
+ "frequency": "Mileage",
+ "expense_amount": flt(500)
+ })
+ vehicle_log.append("service_detail", {
+ "service_item": "Wheels",
+ "type": "Change",
+ "frequency": "Half Yearly",
+ "expense_amount": flt(1500)
+ })
+
+ vehicle_log.save()
+ vehicle_log.submit()
+
+ return vehicle_log
\ No newline at end of file
diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.json b/erpnext/hr/doctype/vehicle_log/vehicle_log.json
index 619e295..4ea9045 100644
--- a/erpnext/hr/doctype/vehicle_log/vehicle_log.json
+++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2016-09-03 14:14:51.788550",
"doctype": "DocType",
@@ -10,7 +11,6 @@
"naming_series",
"license_plate",
"employee",
- "column_break_4",
"column_break_7",
"model",
"make",
@@ -66,10 +66,6 @@
"reqd": 1
},
{
- "fieldname": "column_break_4",
- "fieldtype": "Column Break"
- },
- {
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
@@ -142,7 +138,6 @@
{
"fieldname": "service_detail",
"fieldtype": "Table",
- "label": "Service Detail",
"options": "Vehicle Service"
},
{
@@ -158,7 +153,7 @@
"fetch_from": "license_plate.last_odometer",
"fieldname": "last_odometer",
"fieldtype": "Int",
- "label": "last Odometer Value ",
+ "label": "Last Odometer Value ",
"read_only": 1,
"reqd": 1
},
@@ -168,7 +163,8 @@
}
],
"is_submittable": 1,
- "modified": "2020-03-18 16:45:45.060761",
+ "links": [],
+ "modified": "2021-05-17 00:10:21.188352",
"modified_by": "Administrator",
"module": "HR",
"name": "Vehicle Log",
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.json b/erpnext/hr/notification/training_scheduled/training_scheduled.json
index 966b887..e49541e 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.json
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.json
@@ -11,16 +11,18 @@
"event": "Submit",
"idx": 0,
"is_standard": 1,
- "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{ doc.introduction }}</li>\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n {% endif %}\n </ul>\n {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
- "modified": "2019-11-29 15:38:31.805409",
+ "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n {{ doc.introduction }}\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n {% endif %}\n <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n {% if doc.is_mandatory %}\n <li>Note: This Training Event is mandatory</li>\n {% endif %}\n </ul>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
+ "modified": "2021-05-24 16:29:13.165930",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Scheduled",
"owner": "Administrator",
"recipients": [
{
- "email_by_document_field": "employee_emails"
+ "receiver_by_document_field": "employee_emails"
}
],
+ "send_system_notification": 0,
+ "send_to_all_assignees": 0,
"subject": "Training Scheduled: {{ doc.name }}"
}
\ No newline at end of file
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.md b/erpnext/hr/notification/training_scheduled/training_scheduled.md
index 374038a..418fd49 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.md
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.md
@@ -35,6 +35,9 @@
</li>
{% endif %}
<li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
+ {% if doc.is_mandatory %}
+ <li>Note: This Training Event is mandatory</li>
+ {% endif %}
</ul>
</div>
</td>
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js
index 05728a2..8bb3457 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js
@@ -37,5 +37,22 @@
"fieldtype": "Link",
"options": "Employee",
}
- ]
+ ],
+
+ onload: () => {
+ frappe.call({
+ type: "GET",
+ method: "erpnext.hr.utils.get_leave_period",
+ args: {
+ "from_date": frappe.defaults.get_default("year_start_date"),
+ "to_date": frappe.defaults.get_default("year_end_date"),
+ "company": frappe.defaults.get_user_default("Company")
+ },
+ freeze: true,
+ callback: (data) => {
+ frappe.query_report.set_filter_value("from_date", data.message[0].from_date);
+ frappe.query_report.set_filter_value("to_date", data.message[0].to_date);
+ }
+ });
+ }
}
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 06f9160..4dd4570 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -6,15 +6,16 @@
from frappe.utils import flt, add_days
from frappe import _
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on
+from itertools import groupby
def execute(filters=None):
if filters.to_date <= filters.from_date:
- frappe.throw(_('"From date" can not be greater than or equal to "To date"'))
+ frappe.throw(_('"From Date" can not be greater than or equal to "To Date"'))
columns = get_columns()
data = get_data(filters)
-
- return columns, data
+ charts = get_chart_data(data)
+ return columns, data, None, charts
def get_columns():
columns = [{
@@ -31,9 +32,10 @@
'options': 'Employee'
}, {
'label': _('Employee Name'),
- 'fieldtype': 'Data',
+ 'fieldtype': 'Dynamic Link',
'fieldname': 'employee_name',
'width': 100,
+ 'options': 'employee'
}, {
'label': _('Opening Balance'),
'fieldtype': 'float',
@@ -64,8 +66,7 @@
return columns
def get_data(filters):
- leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC")
-
+ leave_types = frappe.db.get_list('Leave Type', pluck='name', order_by='name')
conditions = get_conditions(filters)
user = frappe.session.user
@@ -113,12 +114,8 @@
# not be shown on the basis of days left it create in user mind for carry_forward leave
row.closing_balance = (new_allocation + opening - (row.leaves_expired + leaves_taken))
-
-
row.indent = 1
data.append(row)
- new_leaves_allocated = 0
-
return data
@@ -129,27 +126,37 @@
if filters.get('employee'):
conditions['name'] = filters.get('employee')
- if filters.get('employee'):
- conditions['name'] = filters.get('employee')
-
if filters.get('company'):
conditions['company'] = filters.get('company')
+ if filters.get('department'):
+ conditions['department'] = filters.get('department')
+
return conditions
def get_department_leave_approver_map(department=None):
- conditions=''
- if department:
- conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
# get current department and all its child
- department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
-
+ department_list = frappe.get_list('Department',
+ filters={
+ 'disabled': 0
+ },
+ or_filters={
+ 'name': department,
+ 'parent_department': department
+ },
+ fields=['name'],
+ pluck='name'
+ )
# retrieve approvers list from current department and from its subsequent child departments
- approver_list = frappe.get_all('Department Approver', filters={
- 'parentfield': 'leave_approvers',
- 'parent': ('in', department_list)
- }, fields=['parent', 'approver'], as_list=1)
+ approver_list = frappe.get_all('Department Approver',
+ filters={
+ 'parentfield': 'leave_approvers',
+ 'parent': ('in', department_list)
+ },
+ fields=['parent', 'approver'],
+ as_list=1
+ )
approvers = {}
@@ -190,3 +197,40 @@
new_allocation += record.leaves
return new_allocation, expired_leaves
+
+def get_chart_data(data):
+ labels = []
+ datasets = []
+ employee_data = data
+
+ if data and data[0].get('employee_name'):
+ get_dataset_for_chart(employee_data, datasets, labels)
+
+ chart = {
+ 'data': {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ 'type': 'bar',
+ 'colors': ['#456789', '#EE8888', '#7E77BF']
+ }
+
+ return chart
+
+def get_dataset_for_chart(employee_data, datasets, labels):
+ leaves = []
+ employee_data = sorted(employee_data, key=lambda k: k['employee_name'])
+
+ for key, group in groupby(employee_data, lambda x: x['employee_name']):
+ for grp in group:
+ if grp.closing_balance:
+ leaves.append(frappe._dict({
+ 'leave_type': grp.leave_type,
+ 'closing_balance': grp.closing_balance
+ }))
+
+ if leaves:
+ labels.append(key)
+
+ for leave in leaves:
+ datasets.append({'name': leave.leave_type, 'values': [leave.closing_balance]})
diff --git a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
new file mode 100644
index 0000000..26e0f26
--- /dev/null
+++ b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import unittest
+import frappe
+from frappe.utils import getdate
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
+from erpnext.hr.doctype.vehicle_log.test_vehicle_log import get_vehicle, make_vehicle_log
+from erpnext.hr.report.vehicle_expenses.vehicle_expenses import execute
+from erpnext.accounts.utils import get_fiscal_year
+
+class TestVehicleExpenses(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ frappe.db.sql('delete from `tabVehicle Log`')
+
+ employee_id = frappe.db.sql('''select name from `tabEmployee` where name="testdriver@example.com"''')
+ self.employee_id = employee_id[0][0] if employee_id else None
+ if not self.employee_id:
+ self.employee_id = make_employee('testdriver@example.com', company='_Test Company')
+
+ self.license_plate = get_vehicle(self.employee_id)
+
+ def test_vehicle_expenses_based_on_fiscal_year(self):
+ vehicle_log = make_vehicle_log(self.license_plate, self.employee_id, with_services=True)
+ expense_claim = make_expense_claim(vehicle_log.name)
+
+ # Based on Fiscal Year
+ filters = {
+ 'filter_based_on': 'Fiscal Year',
+ 'fiscal_year': get_fiscal_year(getdate())[0]
+ }
+
+ report = execute(filters)
+
+ expected_data = [{
+ 'vehicle': self.license_plate,
+ 'make': 'Maruti',
+ 'model': 'PCM',
+ 'location': 'Mumbai',
+ 'log_name': vehicle_log.name,
+ 'odometer': 5010,
+ 'date': getdate(),
+ 'fuel_qty': 50.0,
+ 'fuel_price': 500.0,
+ 'fuel_expense': 25000.0,
+ 'service_expense': 2000.0,
+ 'employee': self.employee_id
+ }]
+
+ self.assertEqual(report[1], expected_data)
+
+ # Based on Date Range
+ fiscal_year = get_fiscal_year(getdate(), as_dict=True)
+ filters = {
+ 'filter_based_on': 'Date Range',
+ 'from_date': fiscal_year.year_start_date,
+ 'to_date': fiscal_year.year_end_date
+ }
+
+ report = execute(filters)
+ self.assertEqual(report[1], expected_data)
+
+ # clean up
+ vehicle_log.cancel()
+ frappe.delete_doc('Expense Claim', expense_claim.name)
+ frappe.delete_doc('Vehicle Log', vehicle_log.name)
+
+ def tearDown(self):
+ frappe.delete_doc('Vehicle', self.license_plate, force=1)
+ frappe.delete_doc('Employee', self.employee_id, force=1)
diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js
index b66bebb..879acd1 100644
--- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js
+++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js
@@ -1,31 +1,52 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.require("assets/erpnext/js/financial_statements.js", function() {
- frappe.query_reports["Vehicle Expenses"] = {
- "filters": [
- {
- "fieldname": "fiscal_year",
- "label": __("Fiscal Year"),
- "fieldtype": "Link",
- "options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1,
- "on_change": function(query_report) {
- var fiscal_year = query_report.get_values().fiscal_year;
- if (!fiscal_year) {
- return;
- }
- frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
- var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
-
- frappe.query_report.set_filter({
- from_date: fy.year_start_date,
- to_date: fy.year_end_date
- });
- });
- }
- }
- ]
- }
-});
+frappe.query_reports["Vehicle Expenses"] = {
+ "filters": [
+ {
+ "fieldname": "filter_based_on",
+ "label": __("Filter Based On"),
+ "fieldtype": "Select",
+ "options": ["Fiscal Year", "Date Range"],
+ "default": ["Fiscal Year"],
+ "reqd": 1
+ },
+ {
+ "fieldname": "fiscal_year",
+ "label": __("Fiscal Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
+ "reqd": 1
+ },
+ {
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "depends_on": "eval: doc.filter_based_on == 'Date Range'",
+ "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12)
+ },
+ {
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "depends_on": "eval: doc.filter_based_on == 'Date Range'",
+ "default": frappe.datetime.nowdate()
+ },
+ {
+ "fieldname": "vehicle",
+ "label": __("Vehicle"),
+ "fieldtype": "Link",
+ "options": "Vehicle"
+ },
+ {
+ "fieldname": "employee",
+ "label": __("Employee"),
+ "fieldtype": "Link",
+ "options": "Employee"
+ }
+ ]
+};
diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json
index 2ab0c14..1a3e5a9 100644
--- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json
+++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json
@@ -1,20 +1,23 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2016-09-09 03:33:40.605734",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 2,
- "is_standard": "Yes",
- "modified": "2017-02-24 19:59:18.641284",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Vehicle Expenses",
- "owner": "Administrator",
- "ref_doctype": "Vehicle",
- "report_name": "Vehicle Expenses",
- "report_type": "Script Report",
+ "add_total_row": 1,
+ "columns": [],
+ "creation": "2016-09-09 03:33:40.605734",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 2,
+ "is_standard": "Yes",
+ "modified": "2021-05-16 22:48:22.767535",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Vehicle Expenses",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Vehicle",
+ "report_name": "Vehicle Expenses",
+ "report_type": "Script Report",
"roles": [
{
"role": "Fleet Manager"
diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
index eab58ff..d847cbb 100644
--- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
+++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
@@ -5,86 +5,209 @@
import frappe
import erpnext
from frappe import _
-from frappe.utils import flt,cstr
+from frappe.utils import flt
from erpnext.accounts.report.financial_statements import get_period_list
def execute(filters=None):
- columns, data, chart = [], [], []
- if filters.get('fiscal_year'):
- company = erpnext.get_default_company()
- period_list = get_period_list(filters.get('fiscal_year'), filters.get('fiscal_year'),
- '', '', 'Fiscal Year', 'Monthly', company=company)
- columns=get_columns()
- data=get_log_data(filters)
- chart=get_chart_data(data,period_list)
+ filters = frappe._dict(filters or {})
+
+ columns = get_columns()
+ data = get_vehicle_log_data(filters)
+ chart = get_chart_data(data, filters)
+
return columns, data, None, chart
def get_columns():
- columns = [_("License") + ":Link/Vehicle:100", _('Create') + ":data:50",
- _("Model") + ":data:50", _("Location") + ":data:100",
- _("Log") + ":Link/Vehicle Log:100", _("Odometer") + ":Int:80",
- _("Date") + ":Date:100", _("Fuel Qty") + ":Float:80",
- _("Fuel Price") + ":Float:100",_("Fuel Expense") + ":Float:100",
- _("Service Expense") + ":Float:100"
+ return [
+ {
+ 'fieldname': 'vehicle',
+ 'fieldtype': 'Link',
+ 'label': _('Vehicle'),
+ 'options': 'Vehicle',
+ 'width': 150
+ },
+ {
+ 'fieldname': 'make',
+ 'fieldtype': 'Data',
+ 'label': _('Make'),
+ 'width': 100
+ },
+ {
+ 'fieldname': 'model',
+ 'fieldtype': 'Data',
+ 'label': _('Model'),
+ 'width': 80
+ },
+ {
+ 'fieldname': 'location',
+ 'fieldtype': 'Data',
+ 'label': _('Location'),
+ 'width': 100
+ },
+ {
+ 'fieldname': 'log_name',
+ 'fieldtype': 'Link',
+ 'label': _('Vehicle Log'),
+ 'options': 'Vehicle Log',
+ 'width': 100
+ },
+ {
+ 'fieldname': 'odometer',
+ 'fieldtype': 'Int',
+ 'label': _('Odometer Value'),
+ 'width': 120
+ },
+ {
+ 'fieldname': 'date',
+ 'fieldtype': 'Date',
+ 'label': _('Date'),
+ 'width': 100
+ },
+ {
+ 'fieldname': 'fuel_qty',
+ 'fieldtype': 'Float',
+ 'label': _('Fuel Qty'),
+ 'width': 80
+ },
+ {
+ 'fieldname': 'fuel_price',
+ 'fieldtype': 'Float',
+ 'label': _('Fuel Price'),
+ 'width': 100
+ },
+ {
+ 'fieldname': 'fuel_expense',
+ 'fieldtype': 'Currency',
+ 'label': _('Fuel Expense'),
+ 'width': 150
+ },
+ {
+ 'fieldname': 'service_expense',
+ 'fieldtype': 'Currency',
+ 'label': _('Service Expense'),
+ 'width': 150
+ },
+ {
+ 'fieldname': 'employee',
+ 'fieldtype': 'Link',
+ 'label': _('Employee'),
+ 'options': 'Employee',
+ 'width': 150
+ }
]
+
return columns
-def get_log_data(filters):
- fy = frappe.db.get_value('Fiscal Year', filters.get('fiscal_year'), ['year_start_date', 'year_end_date'], as_dict=True)
- data = frappe.db.sql("""select
- vhcl.license_plate as "License", vhcl.make as "Make", vhcl.model as "Model",
- vhcl.location as "Location", log.name as "Log", log.odometer as "Odometer",
- log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price",
- log.fuel_qty * log.price as "Fuel Expense"
- from
+
+def get_vehicle_log_data(filters):
+ start_date, end_date = get_period_dates(filters)
+ conditions, values = get_conditions(filters)
+
+ data = frappe.db.sql("""
+ SELECT
+ vhcl.license_plate as vehicle, vhcl.make, vhcl.model,
+ vhcl.location, log.name as log_name, log.odometer,
+ log.date, log.employee, log.fuel_qty,
+ log.price as fuel_price,
+ log.fuel_qty * log.price as fuel_expense
+ FROM
`tabVehicle` vhcl,`tabVehicle Log` log
- where
- vhcl.license_plate = log.license_plate and log.docstatus = 1 and date between %s and %s
- order by date""" ,(fy.year_start_date, fy.year_end_date), as_dict=1)
- dl=list(data)
- for row in dl:
- row["Service Expense"]= get_service_expense(row["Log"])
- return dl
+ WHERE
+ vhcl.license_plate = log.license_plate
+ and log.docstatus = 1
+ and date between %(start_date)s and %(end_date)s
+ {0}
+ ORDER BY date""".format(conditions), values, as_dict=1)
+
+ for row in data:
+ row['service_expense'] = get_service_expense(row.log_name)
+
+ return data
+
+
+def get_conditions(filters):
+ conditions = ''
+
+ start_date, end_date = get_period_dates(filters)
+ values = {
+ 'start_date': start_date,
+ 'end_date': end_date
+ }
+
+ if filters.employee:
+ conditions += ' and log.employee = %(employee)s'
+ values['employee'] = filters.employee
+
+ if filters.vehicle:
+ conditions += ' and vhcl.license_plate = %(vehicle)s'
+ values['vehicle'] = filters.vehicle
+
+ return conditions, values
+
+
+def get_period_dates(filters):
+ if filters.filter_based_on == 'Fiscal Year' and filters.fiscal_year:
+ fy = frappe.db.get_value('Fiscal Year', filters.fiscal_year,
+ ['year_start_date', 'year_end_date'], as_dict=True)
+ return fy.year_start_date, fy.year_end_date
+ else:
+ return filters.from_date, filters.to_date
+
def get_service_expense(logname):
- expense_amount = frappe.db.sql("""select sum(expense_amount)
- from `tabVehicle Log` log,`tabVehicle Service` ser
- where ser.parent=log.name and log.name=%s""",logname)
- return flt(expense_amount[0][0]) if expense_amount else 0
+ expense_amount = frappe.db.sql("""
+ SELECT sum(expense_amount)
+ FROM
+ `tabVehicle Log` log, `tabVehicle Service` service
+ WHERE
+ service.parent=log.name and log.name=%s
+ """, logname)
-def get_chart_data(data,period_list):
- fuel_exp_data,service_exp_data,fueldata,servicedata = [],[],[],[]
- service_exp_data = []
- fueldata = []
+ return flt(expense_amount[0][0]) if expense_amount else 0.0
+
+
+def get_chart_data(data, filters):
+ period_list = get_period_list(filters.fiscal_year, filters.fiscal_year,
+ filters.from_date, filters.to_date, filters.filter_based_on, 'Monthly')
+
+ fuel_data, service_data = [], []
+
for period in period_list:
- total_fuel_exp=0
- total_ser_exp=0
- for row in data:
- if row["Date"] <= period.to_date and row["Date"] >= period.from_date:
- total_fuel_exp+=flt(row["Fuel Expense"])
- total_ser_exp+=flt(row["Service Expense"])
- fueldata.append([period.key,total_fuel_exp])
- servicedata.append([period.key,total_ser_exp])
+ total_fuel_exp = 0
+ total_service_exp = 0
- labels = [period.key for period in period_list]
- fuel_exp_data= [row[1] for row in fueldata]
- service_exp_data= [row[1] for row in servicedata]
+ for row in data:
+ if row.date <= period.to_date and row.date >= period.from_date:
+ total_fuel_exp += flt(row.fuel_expense)
+ total_service_exp += flt(row.service_expense)
+
+ fuel_data.append([period.key, total_fuel_exp])
+ service_data.append([period.key, total_service_exp])
+
+ labels = [period.label for period in period_list]
+ fuel_exp_data= [row[1] for row in fuel_data]
+ service_exp_data= [row[1] for row in service_data]
+
datasets = []
if fuel_exp_data:
datasets.append({
- 'name': 'Fuel Expenses',
+ 'name': _('Fuel Expenses'),
'values': fuel_exp_data
})
+
if service_exp_data:
datasets.append({
- 'name': 'Service Expenses',
+ 'name': _('Service Expenses'),
'values': service_exp_data
})
+
chart = {
- "data": {
+ 'data': {
'labels': labels,
'datasets': datasets
- }
+ },
+ 'type': 'line',
+ 'fieldtype': 'Currency'
}
- chart["type"] = "line"
+
return chart
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 80189e8..ebb1734 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -269,6 +269,7 @@
total_exemption_amount = sum([flt(d.total_exemption_amount) for d in exemptions.values()])
return total_exemption_amount
+@frappe.whitelist()
def get_leave_period(from_date, to_date, company):
leave_period = frappe.db.sql("""
select name, from_date, to_date
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 230475f..69d11a8 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -264,7 +264,7 @@
pending_amount = amounts['pending_principal_amount']
if amount and (amount > pending_amount):
- frappe.throw('Write Off amount cannot be greater than pending loan amount')
+ frappe.throw(_('Write Off amount cannot be greater than pending loan amount'))
if not amount:
amount = pending_amount
diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json
index 18559dc..d0b67f7 100644
--- a/erpnext/loan_management/workspace/loan_management/loan_management.json
+++ b/erpnext/loan_management/workspace/loan_management/loan_management.json
@@ -12,7 +12,7 @@
"idx": 0,
"is_default": 0,
"is_standard": 1,
- "label": "Loan Management",
+ "label": "Loans",
"links": [
{
"hidden": 0,
@@ -220,10 +220,10 @@
"type": "Link"
}
],
- "modified": "2021-02-18 17:31:53.586508",
+ "modified": "2021-05-25 17:31:53.586508",
"modified_by": "Administrator",
"module": "Loan Management",
- "name": "Loan Management",
+ "name": "Loans",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
index ddbcdfd..44712d5 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
@@ -2,40 +2,36 @@
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext.maintenance");
-
frappe.ui.form.on('Maintenance Schedule', {
- setup: function(frm) {
+ 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);
-
- frm.add_fetch('item_code', 'item_name', 'item_name');
- frm.add_fetch('item_code', 'description', 'description');
},
- onload: function(frm) {
+ onload: function (frm) {
if (!frm.doc.status) {
- frm.set_value({status:'Draft'});
+ frm.set_value({ status: 'Draft' });
}
if (frm.doc.__islocal) {
- frm.set_value({transaction_date: frappe.datetime.get_today()});
+ frm.set_value({ transaction_date: frappe.datetime.get_today() });
}
},
- refresh: function(frm) {
+ refresh: function (frm) {
setTimeout(() => {
frm.toggle_display('generate_schedule', !(frm.is_new()));
frm.toggle_display('schedule', !(frm.is_new()));
- },10);
+ }, 10);
},
- customer: function(frm) {
+ customer: function (frm) {
erpnext.utils.get_party_details(frm)
},
- customer_address: function(frm) {
+ customer_address: function (frm) {
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display');
},
- contact_person: function(frm) {
+ contact_person: function (frm) {
erpnext.utils.get_contact_details(frm);
},
- generate_schedule: function(frm) {
+ generate_schedule: function (frm) {
if (frm.is_new()) {
frappe.msgprint(__('Please save first'));
} else {
@@ -46,14 +42,14 @@
// TODO commonify this code
erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({
- refresh: function() {
- frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
+ refresh: function () {
+ frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' };
var me = this;
if (this.frm.doc.docstatus === 0) {
this.frm.add_custom_button(__('Sales Order'),
- function() {
+ function () {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_schedule",
source_doctype: "Sales Order",
@@ -68,52 +64,107 @@
});
}, __("Get Items From"));
} else if (this.frm.doc.docstatus === 1) {
- this.frm.add_custom_button(__('Create Maintenance Visit'), function() {
- frappe.model.open_mapped_doc({
- method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
- source_name: me.frm.doc.name,
- frm: me.frm
+ let schedules = me.frm.doc.schedules;
+ let flag = schedules.some(schedule => schedule.completion_status === "Pending");
+ if (flag) {
+ this.frm.add_custom_button(__('Maintenance Visit'), function () {
+ let options = "";
+
+ me.frm.call('get_pending_data', {data_type: "items"}).then(r => {
+ options = r.message;
+
+ let schedule_id = "";
+ let d = new frappe.ui.Dialog({
+ title: __("Enter Visit Details"),
+ fields: [{
+ fieldtype: "Select",
+ fieldname: "item_name",
+ label: __("Item Name"),
+ options: options,
+ reqd: 1,
+ onchange: function () {
+ let field = d.get_field("scheduled_date");
+ me.frm.call('get_pending_data',
+ {
+ item_name: this.value,
+ data_type: "date"
+ }).then(r => {
+ field.df.options = r.message;
+ field.refresh();
+ });
+ }
+ },
+ {
+ label: __('Scheduled Date'),
+ fieldname: 'scheduled_date',
+ fieldtype: 'Select',
+ options: "",
+ reqd: 1,
+ onchange: function () {
+ let field = d.get_field('item_name');
+ me.frm.call(
+ 'get_pending_data',
+ {
+ item_name: field.value,
+ s_date: this.value,
+ data_type: "id"
+ }).then(r => {
+ schedule_id = r.message;
+ });
+ }
+ },
+ ],
+ primary_action_label: 'Create Visit',
+ primary_action(values) {
+ frappe.call({
+ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
+ args: {
+ item_name: values.item_name,
+ s_id: schedule_id,
+ source_name: me.frm.doc.name,
+ },
+ callback: function (r) {
+ if (!r.exc) {
+ frappe.model.sync(r.message);
+ frappe.set_route("Form", r.message.doctype, r.message.name);
+ }
+ }
+ });
+ d.hide();
+ }
+ });
+ d.show();
});
- }, __('Create'));
+ }, __('Create'));
+ }
}
},
- start_date: function(doc, cdt, cdn) {
+ start_date: function (doc, cdt, cdn) {
this.set_no_of_visits(doc, cdt, cdn);
},
- end_date: function(doc, cdt, cdn) {
+ end_date: function (doc, cdt, cdn) {
this.set_no_of_visits(doc, cdt, cdn);
},
- periodicity: function(doc, cdt, cdn) {
+ periodicity: function (doc, cdt, cdn) {
+ this.set_no_of_visits(doc, cdt, cdn);
+
+ },
+ no_of_visits: function (doc, cdt, cdn) {
this.set_no_of_visits(doc, cdt, cdn);
},
- set_no_of_visits: function(doc, cdt, cdn) {
+ set_no_of_visits: function (doc, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn);
-
- if (item.start_date && item.end_date && item.periodicity) {
- if(item.start_date > item.end_date) {
- frappe.msgprint(__("Row {0}:Start Date must be before End Date", [item.idx]));
- return;
- }
-
- var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1;
-
- var days_in_period = {
- "Weekly": 7,
- "Monthly": 30,
- "Quarterly": 91,
- "Half Yearly": 182,
- "Yearly": 365
- }
-
- var no_of_visits = cint(date_diff / days_in_period[item.periodicity]);
- frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits);
+ let me = this;
+ if (item.start_date && item.periodicity) {
+ me.frm.call('validate_end_date_visits');
+
}
},
});
-$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({frm: cur_frm}));
+$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({ frm: cur_frm }));
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
index 606d22f..4f89a67 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
@@ -1,852 +1,264 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2013-01-10 16:34:30",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
+ "actions": [],
+ "autoname": "naming_series:",
+ "creation": "2013-01-10 16:34:30",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "customer_details",
+ "naming_series",
+ "customer",
+ "column_break0",
+ "status",
+ "transaction_date",
+ "items_section",
+ "items",
+ "schedule",
+ "generate_schedule",
+ "schedules",
+ "contact_info",
+ "customer_name",
+ "contact_person",
+ "contact_mobile",
+ "contact_email",
+ "contact_display",
+ "column_break_17",
+ "customer_address",
+ "address_display",
+ "territory",
+ "customer_group",
+ "company",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer_details",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-user",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "customer_details",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-user"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Series",
- "length": 0,
- "no_copy": 1,
- "options": "MAT-MSH-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "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": 1,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "no_copy": 1,
+ "options": "MAT-MSH-.YYYY.-",
+ "print_hide": 1,
+ "reqd": 1,
+ "set_only_once": 1
+ },
{
- "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": 1,
- "label": "Customer",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "customer",
- "oldfieldtype": "Link",
- "options": "Customer",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Customer",
+ "oldfieldname": "customer",
+ "oldfieldtype": "Link",
+ "options": "Customer",
+ "print_hide": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break0",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Column Break",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "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_break0",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Draft",
- "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": 1,
- "label": "Status",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "status",
- "oldfieldtype": "Select",
- "options": "\nDraft\nSubmitted\nCancelled",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "oldfieldname": "status",
+ "oldfieldtype": "Select",
+ "options": "\nDraft\nSubmitted\nCancelled",
+ "read_only": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "transaction_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": "Transaction Date",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "transaction_date",
- "oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "transaction_date",
+ "fieldtype": "Date",
+ "label": "Transaction Date",
+ "oldfieldname": "transaction_date",
+ "oldfieldtype": "Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 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": "",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "items_section",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-shopping-cart"
+ },
{
- "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,
- "oldfieldname": "item_maintenance_detail",
- "oldfieldtype": "Table",
- "options": "Maintenance Schedule Item",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "label": "Items",
+ "oldfieldname": "item_maintenance_detail",
+ "oldfieldtype": "Table",
+ "options": "Maintenance Schedule Item",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "schedule",
- "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": "Schedule",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-time",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "schedule",
+ "fieldtype": "Section Break",
+ "label": "Schedule",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-time"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "generate_schedule",
- "fieldtype": "Button",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Generate Schedule",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Button",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "generate_schedule",
+ "fieldtype": "Button",
+ "label": "Generate Schedule",
+ "oldfieldtype": "Button"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "schedules",
- "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": "Schedules",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "schedules",
- "oldfieldtype": "Table",
- "options": "Maintenance Schedule Detail",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "schedules",
+ "fieldtype": "Table",
+ "label": "Schedules",
+ "oldfieldname": "schedules",
+ "oldfieldtype": "Table",
+ "options": "Maintenance Schedule Detail"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_info",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact Info",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "contact_info",
+ "fieldtype": "Section Break",
+ "label": "Contact Info"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "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": 0,
- "label": "Customer Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "customer_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "bold": 1,
+ "depends_on": "customer",
+ "fieldname": "customer_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "label": "Customer Name",
+ "oldfieldname": "customer_name",
+ "oldfieldtype": "Data",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "contact_person",
- "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": "Contact Person",
- "length": 0,
- "no_copy": 0,
- "options": "Contact",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "customer",
+ "fieldname": "contact_person",
+ "fieldtype": "Link",
+ "label": "Contact Person",
+ "options": "Contact",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "contact_mobile",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Mobile No",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "customer",
+ "fieldname": "contact_mobile",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Mobile No",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "contact_email",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact Email",
- "length": 0,
- "no_copy": 0,
- "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
- },
+ "depends_on": "customer",
+ "fieldname": "contact_email",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Contact Email",
+ "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": "contact_display",
- "fieldtype": "Small Text",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "contact_display",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "in_global_search": 1,
+ "label": "Contact",
+ "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_17",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_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_17",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "customer_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": "Customer Address",
- "length": 0,
- "no_copy": 0,
- "options": "Address",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "customer",
+ "fieldname": "customer_address",
+ "fieldtype": "Link",
+ "label": "Customer Address",
+ "options": "Address",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_display",
- "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": "Address",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "address_display",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Address",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "description": "",
- "fieldname": "territory",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Territory",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "territory",
- "oldfieldtype": "Link",
- "options": "Territory",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "customer",
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "label": "Territory",
+ "oldfieldname": "territory",
+ "oldfieldtype": "Link",
+ "options": "Territory"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "description": "",
- "fieldname": "customer_group",
- "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 Group",
- "length": 0,
- "no_copy": 0,
- "options": "Customer Group",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "customer",
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "label": "Customer Group",
+ "options": "Customer Group"
+ },
{
- "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": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "company",
- "oldfieldtype": "Link",
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "oldfieldname": "company",
+ "oldfieldtype": "Link",
+ "options": "Company",
+ "remember_last_selected_value": 1,
+ "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": "Maintenance Schedule",
- "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": "Maintenance Schedule",
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-calendar",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Maintenance",
- "name": "Maintenance Schedule",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-calendar",
+ "idx": 1,
+ "is_submittable": 1,
+ "links": [
+ {
+ "group": "Visits",
+ "link_doctype": "Maintenance Visit",
+ "link_fieldname": "maintenance_schedule"
+ }
+ ],
+ "modified": "2021-05-27 16:05:10.746465",
+ "modified_by": "Administrator",
+ "module": "Maintenance",
+ "name": "Maintenance Schedule",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Maintenance Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Maintenance Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "status,customer,customer_name",
- "show_name_in_global_search": 0,
- "sort_order": "DESC",
- "timeline_field": "customer",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "search_fields": "status,customer,customer_name",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "timeline_field": "customer"
}
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index 0aefe19..d6e42f3 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -4,12 +4,13 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import add_days, getdate, cint, cstr
+from frappe.utils import add_days, getdate, cint, cstr, date_diff, formatdate
from frappe import throw, _
from erpnext.utilities.transaction_base import TransactionBase, delete_events
from erpnext.stock.utils import get_valid_serial_nos
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class MaintenanceSchedule(TransactionBase):
@frappe.whitelist()
@@ -32,8 +33,40 @@
child.idx = count
count = count + 1
child.sales_person = d.sales_person
+ child.completion_status = "Pending"
+ child.item_reference = d.name
- self.save()
+ @frappe.whitelist()
+ def validate_end_date_visits(self):
+ days_in_period = {
+ "Weekly": 7,
+ "Monthly": 30,
+ "Quarterly": 91,
+ "Half Yearly": 182,
+ "Yearly": 365
+ }
+ for item in self.items:
+ if item.periodicity and item.start_date:
+ if not item.end_date:
+ if item.no_of_visits:
+ item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
+ else:
+ item.end_date = add_days(item.start_date, days_in_period[item.periodicity])
+
+ diff = date_diff(item.end_date, item.start_date) + 1
+ no_of_visits = cint(diff / days_in_period[item.periodicity])
+
+ if not item.no_of_visits or item.no_of_visits == 0:
+ item.end_date = add_days(item.start_date, days_in_period[item.periodicity])
+ diff = date_diff(item.end_date, item.start_date) + 1
+ item.no_of_visits = cint(diff / days_in_period[item.periodicity])
+
+ elif item.no_of_visits > no_of_visits:
+ item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
+
+ elif item.no_of_visits < no_of_visits:
+ item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
+
def on_submit(self):
if not self.get('schedules'):
@@ -58,9 +91,10 @@
if no_email_sp:
frappe.msgprint(
- frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format(
+ _("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format(
self.owner, "<br>" + "<br>".join(no_email_sp)
- ))
+ )
+ )
scheduled_date = frappe.db.sql("""select scheduled_date from
`tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and
@@ -106,7 +140,7 @@
if employee:
holiday_list = get_holiday_list_for_employee(employee)
else:
- holiday_list = frappe.get_cached_value('Company', self.company, "default_holiday_list")
+ holiday_list = frappe.get_cached_value('Company', self.company, "default_holiday_list")
holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%s''', holiday_list)
@@ -135,8 +169,7 @@
}
if date_diff < days_in_period[d.periodicity]:
- throw(_("Row {0}: To set {1} periodicity, difference between from and to date \
- must be greater than or equal to {2}")
+ throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}")
.format(d.idx, d.periodicity, days_in_period[d.periodicity]))
def validate_maintenance_detail(self):
@@ -166,13 +199,15 @@
throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order))
def validate(self):
+ self.validate_end_date_visits()
self.validate_maintenance_detail()
self.validate_dates_with_periodicity()
self.validate_sales_order()
+ self.generate_schedule()
def on_update(self):
frappe.db.set(self, 'status', 'Draft')
-
+
def update_amc_date(self, serial_nos, amc_expiry_date=None):
for serial_no in serial_nos:
serial_no_doc = frappe.get_doc("Serial No", serial_no)
@@ -202,8 +237,8 @@
if not sr_details.warehouse and sr_details.delivery_date and \
getdate(sr_details.delivery_date) >= getdate(amc_start_date):
- throw(_("Maintenance start date can not be before delivery date for Serial No {0}")
- .format(serial_no))
+ throw(_("Maintenance start date can not be before delivery date for Serial No {0}")
+ .format(serial_no))
def validate_schedule(self):
item_lst1 =[]
@@ -245,13 +280,50 @@
def on_trash(self):
delete_events(self.doctype, self.name)
+ @frappe.whitelist()
+ def get_pending_data(self, data_type, s_date=None, item_name=None):
+ if data_type == "date":
+ dates = ""
+ for schedule in self.schedules:
+ if schedule.item_name == item_name and schedule.completion_status == "Pending":
+ dates = dates + "\n" + formatdate(schedule.scheduled_date, "dd-MM-yyyy")
+ return dates
+ elif data_type == "items":
+ items = ""
+ for item in self.items:
+ for schedule in self.schedules:
+ if item.item_name == schedule.item_name and schedule.completion_status == "Pending":
+ items = items + "\n" + item.item_name
+ break
+ return items
+ elif data_type == "id":
+ for schedule in self.schedules:
+ if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date, "dd-mm-yyyy"):
+ return schedule.name
+
@frappe.whitelist()
-def make_maintenance_visit(source_name, target_doc=None):
+def update_serial_nos(s_id):
+ serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no')
+ if serial_nos:
+ serial_nos = get_serial_nos(serial_nos)
+ return serial_nos
+ else:
+ return False
+
+@frappe.whitelist()
+def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None):
from frappe.model.mapper import get_mapped_doc
- def update_status(source, target, parent):
+ 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(source, target, parent):
+ sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person')
+ target.service_person = sales_person
+ target.serial_no = ''
+
doclist = get_mapped_doc("Maintenance Schedule", source_name, {
"Maintenance Schedule": {
"doctype": "Maintenance Visit",
@@ -261,15 +333,12 @@
"validation": {
"docstatus": ["=", 1]
},
- "postprocess": update_status
+ "postprocess": update_status_and_detail
},
"Maintenance Schedule Item": {
"doctype": "Maintenance Visit Purpose",
- "field_map": {
- "parent": "prevdoc_docname",
- "parenttype": "prevdoc_doctype",
- "sales_person": "service_person"
- }
+ "condition": lambda doc: doc.item_name == item_name,
+ "postprocess": update_sales
}
}, 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 3c307e9..09981ba 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
@@ -2,7 +2,8 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
-from frappe.utils.data import get_datetime, add_days
+from frappe.utils.data import add_days, today, formatdate
+from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import make_maintenance_visit
import frappe
import unittest
@@ -21,7 +22,57 @@
ms.cancel()
events_after_cancel = get_events(ms)
self.assertTrue(len(events_after_cancel) == 0)
+
+ def test_make_schedule(self):
+ ms = make_maintenance_schedule()
+ ms.save()
+ i = ms.items[0]
+ expected_dates = []
+ expected_end_date = add_days(i.start_date, i.no_of_visits * 7)
+ self.assertEqual(i.end_date, expected_end_date)
+ i.no_of_visits = 2
+ ms.save()
+ expected_end_date = add_days(i.start_date, i.no_of_visits * 7)
+ self.assertEqual(i.end_date, expected_end_date)
+
+ items = ms.get_pending_data(data_type = "items")
+ items = items.split('\n')
+ items.pop(0)
+ expected_items = ['_Test Item']
+ self.assertTrue(items, expected_items)
+
+ # "dates" contains all generated schedule dates
+ dates = ms.get_pending_data(data_type = "date", item_name = i.item_name)
+ dates = dates.split('\n')
+ dates.pop(0)
+ expected_dates.append(formatdate(add_days(i.start_date, 7), "dd-MM-yyyy"))
+ expected_dates.append(formatdate(add_days(i.start_date, 14), "dd-MM-yyyy"))
+
+ # test for generated schedule dates
+ self.assertEqual(dates, expected_dates)
+
+ ms.submit()
+ s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1])
+ test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id)
+ visit = frappe.new_doc('Maintenance Visit')
+ visit = test
+ visit.maintenance_schedule = ms.name
+ visit.maintenance_schedule_detail = s_id
+ visit.completion_status = "Partially Completed"
+ visit.set('purposes', [{
+ 'item_code': i.item_code,
+ 'description': "test",
+ 'work_done': "test",
+ 'service_person': "Sales Team",
+ }])
+ visit.save()
+ visit.submit()
+ ms = frappe.get_doc('Maintenance Schedule', ms.name)
+
+ #checks if visit status is back updated in schedule
+ self.assertTrue(ms.schedules[1].completion_status, "Partially Completed")
+
def get_events(ms):
return frappe.get_all("Event Participants", filters={
"reference_doctype": ms.doctype,
@@ -33,12 +84,11 @@
ms = frappe.new_doc("Maintenance Schedule")
ms.company = "_Test Company"
ms.customer = "_Test Customer"
- ms.transaction_date = get_datetime()
+ ms.transaction_date = today()
ms.append("items", {
"item_code": "_Test Item",
- "start_date": get_datetime(),
- "end_date": add_days(get_datetime(), 32),
+ "start_date": today(),
"periodicity": "Weekly",
"no_of_visits": 4,
"sales_person": "Sales Team",
diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json
index 7cd3086..8ccef6a 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json
+++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json
@@ -1,222 +1,137 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-02-22 01:28:05",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:28:05",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_code",
+ "item_name",
+ "column_break_3",
+ "scheduled_date",
+ "actual_date",
+ "section_break_6",
+ "sales_person",
+ "column_break_8",
+ "completion_status",
+ "section_break_10",
+ "serial_no",
+ "item_reference"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_code",
- "oldfieldtype": "Link",
- "options": "Item",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "oldfieldname": "item_code",
+ "oldfieldtype": "Link",
+ "options": "Item",
+ "read_only": 1,
+ "search_index": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_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": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "label": "Item Name",
+ "oldfieldname": "item_name",
+ "oldfieldtype": "Data",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "scheduled_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": "Scheduled Date",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "scheduled_date",
- "oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 1,
- "set_only_once": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "scheduled_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Scheduled Date",
+ "oldfieldname": "scheduled_date",
+ "oldfieldtype": "Date",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "actual_date",
- "fieldtype": "Date",
- "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": "Actual Date",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "actual_date",
- "oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "actual_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Actual Date",
+ "no_copy": 1,
+ "oldfieldname": "actual_date",
+ "oldfieldtype": "Date",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_person",
- "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": "Sales Person",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "incharge_name",
- "oldfieldtype": "Link",
- "options": "Sales Person",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "columns": 2,
+ "fieldname": "sales_person",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Sales Person",
+ "oldfieldname": "incharge_name",
+ "oldfieldtype": "Link",
+ "options": "Sales Person",
+ "read_only_depends_on": "eval:doc.completion_status != \"Pending\""
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "serial_no",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Serial No",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "serial_no",
- "oldfieldtype": "Small Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "160px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "serial_no",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Serial No",
+ "oldfieldname": "serial_no",
+ "oldfieldtype": "Small Text",
+ "print_width": "160px",
+ "read_only": 1,
"width": "160px"
+ },
+ {
+ "columns": 2,
+ "fieldname": "completion_status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Completion Status",
+ "options": "Pending\nPartially Completed\nFully Completed",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_10",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "item_reference",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Item Reference",
+ "options": "Maintenance Schedule Item",
+ "read_only": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-02-17 17:05:44.644663",
- "modified_by": "Administrator",
- "module": "Maintenance",
- "name": "Maintenance Schedule Detail",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-27 16:07:25.905015",
+ "modified_by": "Administrator",
+ "module": "Maintenance",
+ "name": "Maintenance Schedule Detail",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json
index b371dfc..3dacdea 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json
+++ b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json
@@ -1,431 +1,160 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-02-22 01:28:05",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:28:05",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_code",
+ "item_name",
+ "description",
+ "column_break_4",
+ "start_date",
+ "end_date",
+ "periodicity",
+ "schedule_details",
+ "no_of_visits",
+ "column_break_10",
+ "sales_person",
+ "reference",
+ "serial_no",
+ "sales_order"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_code",
- "oldfieldtype": "Link",
- "options": "Item",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "oldfieldname": "item_code",
+ "oldfieldtype": "Link",
+ "options": "Item",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "columns": 1,
"fetch_from": "item_code.item_name",
- "fieldname": "item_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": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_name",
- "oldfieldtype": "Data",
- "options": "",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "label": "Item Name",
+ "oldfieldname": "item_name",
+ "oldfieldtype": "Data",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "item_code.description",
- "fieldname": "description",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "description",
- "oldfieldtype": "Data",
- "options": "",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "300px",
- "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": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Data",
+ "print_width": "300px",
+ "read_only": 1,
"width": "300px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "schedule_details",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "schedule_details",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 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,
- "oldfieldname": "start_date",
- "oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "start_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Start Date",
+ "oldfieldname": "start_date",
+ "oldfieldtype": "Date",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "end_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": "End Date",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "end_date",
- "oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "end_date",
+ "fieldtype": "Date",
+ "label": "End Date",
+ "oldfieldname": "end_date",
+ "oldfieldtype": "Date",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "periodicity",
- "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": "Periodicity",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "periodicity",
- "oldfieldtype": "Select",
- "options": "\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly\nRandom",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 1,
+ "fieldname": "periodicity",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Periodicity",
+ "oldfieldname": "periodicity",
+ "oldfieldtype": "Select",
+ "options": "\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly\nRandom"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "no_of_visits",
- "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": "No of Visits",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "no_of_visits",
- "oldfieldtype": "Int",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 1,
+ "fieldname": "no_of_visits",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "No of Visits",
+ "oldfieldname": "no_of_visits",
+ "oldfieldtype": "Int",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_person",
- "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": "Sales Person",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "incharge_name",
- "oldfieldtype": "Link",
- "options": "Sales Person",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "sales_person",
+ "fieldtype": "Link",
+ "label": "Sales Person",
+ "oldfieldname": "incharge_name",
+ "oldfieldtype": "Link",
+ "options": "Sales Person"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reference",
- "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": "Reference",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "reference",
+ "fieldtype": "Section Break",
+ "label": "Reference"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "serial_no",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Serial No",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "serial_no",
- "oldfieldtype": "Small Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "serial_no",
+ "fieldtype": "Small Text",
+ "label": "Serial No",
+ "oldfieldname": "serial_no",
+ "oldfieldtype": "Small Text"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_order",
- "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": "Sales Order",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "prevdoc_docname",
- "oldfieldtype": "Data",
- "options": "Sales Order",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "label": "Sales Order",
+ "no_copy": 1,
+ "oldfieldname": "prevdoc_docname",
+ "oldfieldtype": "Data",
+ "options": "Sales Order",
+ "print_hide": 1,
+ "print_width": "150px",
+ "read_only": 1,
+ "search_index": 1,
"width": "150px"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-05-16 22:43:14.260729",
- "modified_by": "Administrator",
- "module": "Maintenance",
- "name": "Maintenance Schedule Item",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-15 16:09:47.311994",
+ "modified_by": "Administrator",
+ "module": "Maintenance",
+ "name": "Maintenance Schedule Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
index 4cbb02a..d6105c6 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
@@ -2,39 +2,62 @@
// 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) {
+ refresh: function (frm) {
//filters for serial_no based on item_code
- frm.set_query('serial_no', 'purposes', function(frm, cdt, cdn) {
+ frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) {
let item = locals[cdt][cdn];
- return {
- filters: {
- 'item_code': item.item_code
- }
- };
+ 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) {
+ 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) {
+ onload: function (frm, cdt, cdn) {
+ let item = locals[cdt][cdn];
+ if (frm.maintenance_type == 'Scheduled') {
+ let schedule_id = item.purposes[0].prevdoc_detail_docname;
+ frappe.call({
+ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos",
+ args: {
+ s_id: schedule_id
+ },
+ callback: function (r) {
+ serial_nos = r.message;
+ }
+ });
+ }
+
if (!frm.doc.status) {
- frm.set_value({status:'Draft'});
+ frm.set_value({ status: 'Draft' });
}
if (frm.doc.__islocal) {
- frm.set_value({mntc_date: frappe.datetime.get_today()});
+ frm.set_value({ mntc_date: frappe.datetime.get_today() });
}
},
- customer: function(frm) {
+ customer: function (frm) {
erpnext.utils.get_party_details(frm);
},
- customer_address: function(frm) {
+ customer_address: function (frm) {
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display');
},
- contact_person: function(frm) {
+ contact_person: function (frm) {
erpnext.utils.get_contact_details(frm);
}
@@ -42,14 +65,14 @@
// TODO commonify this code
erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({
- refresh: function() {
- frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
+ refresh: function () {
+ frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' };
var me = this;
- if (this.frm.doc.docstatus===0) {
+ if (this.frm.doc.docstatus === 0) {
this.frm.add_custom_button(__('Maintenance Schedule'),
- function() {
+ function () {
erpnext.utils.map_current_doc({
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
source_doctype: "Maintenance Schedule",
@@ -64,7 +87,7 @@
})
}, __("Get Items From"));
this.frm.add_custom_button(__('Warranty Claim'),
- function() {
+ function () {
erpnext.utils.map_current_doc({
method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
source_doctype: "Warranty Claim",
@@ -80,7 +103,7 @@
})
}, __("Get Items From"));
this.frm.add_custom_button(__('Sales Order'),
- function() {
+ function () {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",
source_doctype: "Sales Order",
@@ -99,4 +122,4 @@
},
});
-$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({frm: cur_frm}));
\ No newline at end of file
+$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({ frm: cur_frm }));
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
index 32bfa0e..ec32239 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
@@ -1,1042 +1,324 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2013-01-10 16:34:31",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
+ "actions": [],
+ "autoname": "naming_series:",
+ "creation": "2013-01-10 16:34:31",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "customer_details",
+ "column_break0",
+ "naming_series",
+ "customer",
+ "customer_name",
+ "address_display",
+ "contact_display",
+ "contact_mobile",
+ "contact_email",
+ "maintenance_schedule",
+ "maintenance_schedule_detail",
+ "column_break1",
+ "mntc_date",
+ "mntc_time",
+ "maintenance_details",
+ "completion_status",
+ "column_break_14",
+ "maintenance_type",
+ "section_break0",
+ "purposes",
+ "more_info",
+ "customer_feedback",
+ "col_break3",
+ "status",
+ "amended_from",
+ "company",
+ "contact_info_section",
+ "customer_address",
+ "contact_person",
+ "col_break4",
+ "territory",
+ "customer_group"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer_details",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-user",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "customer_details",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-user"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break0",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Column Break",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "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_break0",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Series",
- "length": 0,
- "no_copy": 1,
- "options": "MAT-MVS-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "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": 1,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "no_copy": 1,
+ "options": "MAT-MVS-.YYYY.-",
+ "print_hide": 1,
+ "reqd": 1,
+ "set_only_once": 1
+ },
{
- "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": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Customer",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "customer",
- "oldfieldtype": "Link",
- "options": "Customer",
- "permlevel": 0,
- "print_hide": 1,
- "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": "customer",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "label": "Customer",
+ "oldfieldname": "customer",
+ "oldfieldtype": "Link",
+ "options": "Customer",
+ "print_hide": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer_name",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Customer Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "bold": 1,
+ "fieldname": "customer_name",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "in_global_search": 1,
+ "label": "Customer Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_display",
- "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": "Address",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "address_display",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Address",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_display",
- "fieldtype": "Small Text",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "contact_display",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "in_global_search": 1,
+ "label": "Contact",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_mobile",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Mobile No",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "contact_mobile",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Mobile No",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_email",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact Email",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "contact_email",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Contact Email",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break1",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Column Break",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "fieldname": "column_break1",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break",
"width": "50%"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fieldname": "mntc_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": "Maintenance Date",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "mntc_date",
- "oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Today",
+ "fieldname": "mntc_date",
+ "fieldtype": "Date",
+ "label": "Maintenance Date",
+ "no_copy": 1,
+ "oldfieldname": "mntc_date",
+ "oldfieldtype": "Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "mntc_time",
- "fieldtype": "Time",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Maintenance Time",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "mntc_time",
- "oldfieldtype": "Time",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "mntc_time",
+ "fieldtype": "Time",
+ "label": "Maintenance Time",
+ "no_copy": 1,
+ "oldfieldname": "mntc_time",
+ "oldfieldtype": "Time"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maintenance_details",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-wrench",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "maintenance_details",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-wrench"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "completion_status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Completion Status",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "completion_status",
- "oldfieldtype": "Select",
- "options": "\nPartially Completed\nFully Completed",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "completion_status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Completion Status",
+ "oldfieldname": "completion_status",
+ "oldfieldtype": "Select",
+ "options": "\nPartially Completed\nFully Completed",
+ "reqd": 1
+ },
{
- "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,
- "print_hide": 0,
- "print_hide_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_14",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Unscheduled",
- "fieldname": "maintenance_type",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Maintenance Type",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "maintenance_type",
- "oldfieldtype": "Select",
- "options": "\nScheduled\nUnscheduled\nBreakdown",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Unscheduled",
+ "fieldname": "maintenance_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Maintenance Type",
+ "oldfieldname": "maintenance_type",
+ "oldfieldtype": "Select",
+ "options": "\nScheduled\nUnscheduled\nBreakdown",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break0",
- "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,
- "oldfieldtype": "Section Break",
- "options": "fa fa-wrench",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break0",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-wrench"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "purposes",
- "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": "Purposes",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "maintenance_visit_details",
- "oldfieldtype": "Table",
- "options": "Maintenance Visit Purpose",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "purposes",
+ "fieldtype": "Table",
+ "label": "Purposes",
+ "oldfieldname": "maintenance_visit_details",
+ "oldfieldtype": "Table",
+ "options": "Maintenance Visit Purpose",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "more_info",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "More Information",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-file-text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "more_info",
+ "fieldtype": "Section Break",
+ "label": "More Information",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-file-text"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer_feedback",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Customer Feedback",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "customer_feedback",
- "oldfieldtype": "Small Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "customer_feedback",
+ "fieldtype": "Small Text",
+ "label": "Customer Feedback",
+ "oldfieldname": "customer_feedback",
+ "oldfieldtype": "Small Text"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "col_break3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "col_break3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Draft",
- "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": 1,
- "oldfieldname": "status",
- "oldfieldtype": "Data",
- "options": "\nDraft\nCancelled\nSubmitted",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "no_copy": 1,
+ "oldfieldname": "status",
+ "oldfieldtype": "Data",
+ "options": "\nDraft\nCancelled\nSubmitted",
+ "read_only": 1,
+ "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": 1,
- "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,
- "oldfieldname": "amended_from",
- "oldfieldtype": "Data",
- "options": "Maintenance Visit",
- "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",
+ "ignore_user_permissions": 1,
+ "label": "Amended From",
+ "no_copy": 1,
+ "oldfieldname": "amended_from",
+ "oldfieldtype": "Data",
+ "options": "Maintenance Visit",
+ "print_hide": 1,
+ "read_only": 1,
"width": "150px"
- },
+ },
{
- "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": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "company",
- "oldfieldtype": "Select",
- "options": "Company",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "oldfieldname": "company",
+ "oldfieldtype": "Select",
+ "options": "Company",
+ "print_hide": 1,
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "contact_info_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": "Contact Info",
- "length": 0,
- "no_copy": 0,
- "options": "fa fa-bullhorn",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "customer",
+ "fieldname": "contact_info_section",
+ "fieldtype": "Section Break",
+ "label": "Contact Info",
+ "options": "fa fa-bullhorn"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer_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": "Customer Address",
- "length": 0,
- "no_copy": 0,
- "options": "Address",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "customer_address",
+ "fieldtype": "Link",
+ "label": "Customer Address",
+ "options": "Address",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_person",
- "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": "Contact Person",
- "length": 0,
- "no_copy": 0,
- "options": "Contact",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "contact_person",
+ "fieldtype": "Link",
+ "label": "Contact Person",
+ "options": "Contact",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "col_break4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "col_break4",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "territory",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Territory",
- "length": 0,
- "no_copy": 0,
- "options": "Territory",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "label": "Territory",
+ "options": "Territory",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "customer_group",
- "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 Group",
- "length": 0,
- "no_copy": 0,
- "options": "Customer Group",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "label": "Customer Group",
+ "options": "Customer Group",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "maintenance_schedule",
+ "fieldtype": "Link",
+ "label": "Maintenance Schedule",
+ "options": "Maintenance Schedule",
+ "read_only": 1
+ },
+ {
+ "fieldname": "maintenance_schedule_detail",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Maintenance Schedule Detail",
+ "options": "Maintenance Schedule Detail"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-file-text",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Maintenance",
- "name": "Maintenance Visit",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-file-text",
+ "idx": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-05-27 16:06:17.352572",
+ "modified_by": "Administrator",
+ "module": "Maintenance",
+ "name": "Maintenance Visit",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Maintenance User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Maintenance User",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "status,maintenance_type,customer,customer_name,mntc_date,company",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "timeline_field": "customer",
- "title_field": "customer_name",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "search_fields": "status,maintenance_type,customer,customer_name,mntc_date,company",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "timeline_field": "customer",
+ "title_field": "customer_name"
}
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
index 2f2ad00..7fffc94 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
+from frappe.utils import get_datetime
from erpnext.utilities.transaction_base import TransactionBase
@@ -16,44 +17,62 @@
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_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))
+
def validate(self):
self.validate_serial_no()
+ self.validate_maintenance_date()
+
+ def update_completion_status(self):
+ 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)
def update_customer_issue(self, flag):
- for d in self.get('purposes'):
- if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' :
- if flag==1:
- mntc_date = self.mntc_date
- service_person = d.service_person
- work_done = d.work_done
- status = "Open"
- if self.completion_status == 'Fully Completed':
- status = 'Closed'
- elif self.completion_status == 'Partially Completed':
- status = 'Work In Progress'
- else:
- nm = frappe.db.sql("select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1", (d.prevdoc_docname, self.name))
-
- if nm:
- status = 'Work In Progress'
- mntc_date = nm and nm[0][1] or ''
- service_person = nm and nm[0][2] or ''
- work_done = nm and nm[0][3] or ''
+ if not self.maintenance_schedule:
+ for d in self.get('purposes'):
+ if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' :
+ if flag==1:
+ mntc_date = self.mntc_date
+ service_person = d.service_person
+ work_done = d.work_done
+ status = "Open"
+ if self.completion_status == 'Fully Completed':
+ status = 'Closed'
+ elif self.completion_status == 'Partially Completed':
+ status = 'Work In Progress'
else:
- status = 'Open'
- mntc_date = None
- service_person = None
- work_done = None
+ nm = frappe.db.sql("select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1", (d.prevdoc_docname, self.name))
- wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname)
- wc_doc.update({
- 'resolution_date': mntc_date,
- 'resolved_by': service_person,
- 'resolution_details': work_done,
- 'status': status
- })
+ if nm:
+ status = 'Work In Progress'
+ mntc_date = nm and nm[0][1] or ''
+ service_person = nm and nm[0][2] or ''
+ work_done = nm and nm[0][3] or ''
+ else:
+ status = 'Open'
+ mntc_date = None
+ service_person = None
+ work_done = None
- wc_doc.db_update()
+ wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname)
+ wc_doc.update({
+ 'resolution_date': mntc_date,
+ 'resolved_by': service_person,
+ 'resolution_details': work_done,
+ 'status': status
+ })
+
+ wc_doc.db_update()
def check_if_last_visit(self):
"""check if last maintenance visit against same sales order/ Warranty Claim"""
@@ -77,6 +96,8 @@
def on_submit(self):
self.update_customer_issue(1)
frappe.db.set(self, 'status', 'Submitted')
+ self.update_completion_status()
+ self.update_actual_date()
def on_cancel(self):
self.check_if_last_visit()
diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
index 467441d..158f143 100644
--- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
+++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-02-22 01:28:06",
"doctype": "DocType",
@@ -8,14 +9,15 @@
"field_order": [
"item_code",
"item_name",
+ "column_break_3",
+ "service_person",
"serial_no",
+ "section_break_6",
"description",
"work_details",
- "service_person",
"work_done",
"prevdoc_doctype",
- "prevdoc_docname",
- "prevdoc_detail_docname"
+ "prevdoc_docname"
],
"fields": [
{
@@ -62,6 +64,8 @@
"fieldtype": "Section Break"
},
{
+ "fetch_from": "prevdoc_detail_docname.sales_person",
+ "fetch_if_empty": 1,
"fieldname": "service_person",
"fieldtype": "Link",
"in_list_view": 1,
@@ -83,49 +87,30 @@
{
"fieldname": "prevdoc_doctype",
"fieldtype": "Link",
+ "hidden": 1,
"label": "Document Type",
- "no_copy": 1,
- "oldfieldname": "prevdoc_doctype",
- "oldfieldtype": "Data",
- "options": "DocType",
- "print_hide": 1,
- "print_width": "150px",
- "read_only": 1,
- "report_hide": 1,
- "width": "150px"
+ "options": "DocType"
},
{
"fieldname": "prevdoc_docname",
"fieldtype": "Dynamic Link",
+ "hidden": 1,
"label": "Against Document No",
- "no_copy": 1,
- "oldfieldname": "prevdoc_docname",
- "oldfieldtype": "Data",
- "options": "prevdoc_doctype",
- "print_hide": 1,
- "print_width": "160px",
- "read_only": 1,
- "report_hide": 1,
- "width": "160px"
+ "options": "prevdoc_doctype"
},
{
- "fieldname": "prevdoc_detail_docname",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Against Document Detail No",
- "no_copy": 1,
- "oldfieldname": "prevdoc_detail_docname",
- "oldfieldtype": "Data",
- "print_hide": 1,
- "print_width": "160px",
- "read_only": 1,
- "report_hide": 1,
- "width": "160px"
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
}
],
"idx": 1,
"istable": 1,
- "modified": "2020-09-18 17:26:09.703215",
+ "links": [],
+ "modified": "2021-05-27 17:47:21.474282",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit Purpose",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 288c1d0..64d5841 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -211,16 +211,27 @@
});
},
- get_items: function(frm) {
+ get_items: function (frm) {
+ frm.clear_table('prod_plan_references');
+
frappe.call({
method: "get_items",
freeze: true,
doc: frm.doc,
- callback: function() {
+ callback: function () {
refresh_field('po_items');
}
});
},
+ combine_items: function (frm) {
+ frm.clear_table('prod_plan_references');
+
+ frappe.call({
+ method: "get_items",
+ freeze: true,
+ doc: frm.doc,
+ });
+ },
get_items_for_mr: function(frm) {
if (!frm.doc.for_warehouse) {
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index f114700..1c0dde2 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -28,7 +28,10 @@
"material_requests",
"select_items_to_manufacture_section",
"get_items",
+ "combine_items",
"po_items",
+ "section_break_25",
+ "prod_plan_references",
"material_request_planning",
"include_non_stock_items",
"include_subcontracted_items",
@@ -316,13 +319,31 @@
"fieldname": "include_safety_stock",
"fieldtype": "Check",
"label": "Include Safety Stock in Required Qty Calculation"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.get_items_from == 'Sales Order'",
+ "fieldname": "combine_items",
+ "fieldtype": "Check",
+ "label": "Consolidate Items"
+ },
+ {
+ "fieldname": "section_break_25",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "prod_plan_references",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "Production Plan Item Reference",
+ "options": "Production Plan Item Reference"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-03-08 11:17:25.470147",
+ "modified": "2021-05-24 16:59:03.643211",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index a3e23a6..46e0476 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -96,8 +96,10 @@
@frappe.whitelist()
def get_items(self):
+ self.set('po_items', [])
if self.get_items_from == "Sales Order":
- self.get_so_items()
+ self.get_so_items()
+
elif self.get_items_from == "Material Request":
self.get_mr_items()
@@ -165,9 +167,31 @@
self.calculate_total_planned_qty()
def add_items(self, items):
- self.set('po_items', [])
+ refs = {}
for data in items:
item_details = get_item_details(data.item_code)
+ if self.combine_items:
+ if item_details.bom_no in refs:
+ refs[item_details.bom_no]['so_details'].append({
+ 'sales_order': data.parent,
+ 'sales_order_item': data.name,
+ 'qty': data.pending_qty
+ })
+ refs[item_details.bom_no]['qty'] += data.pending_qty
+ continue
+
+ else:
+ refs[item_details.bom_no] = {
+ 'qty': data.pending_qty,
+ 'po_item_ref': data.name,
+ 'so_details': []
+ }
+ refs[item_details.bom_no]['so_details'].append({
+ 'sales_order': data.parent,
+ 'sales_order_item': data.name,
+ 'qty': data.pending_qty
+ })
+
pi = self.append('po_items', {
'include_exploded_items': 1,
'warehouse': data.warehouse,
@@ -185,11 +209,28 @@
pi.sales_order = data.parent
pi.sales_order_item = data.name
pi.description = data.description
-
+
elif self.get_items_from == "Material Request":
pi.material_request = data.parent
pi.material_request_item = data.name
pi.description = data.description
+
+ if refs:
+ for po_item in self.po_items:
+ po_item.planned_qty = refs[po_item.bom_no]['qty']
+ po_item.pending_qty = refs[po_item.bom_no]['qty']
+ po_item.sales_order = ''
+ self.add_pp_ref(refs)
+
+ def add_pp_ref(self, refs):
+ for bom_no in refs:
+ for so_detail in refs[bom_no]['so_details']:
+ self.append('prod_plan_references', {
+ 'item_reference': refs[bom_no]['po_item_ref'],
+ 'sales_order': so_detail['sales_order'],
+ 'sales_order_item': so_detail['sales_order_item'],
+ 'qty': so_detail['qty']
+ })
def calculate_total_planned_qty(self):
self.total_planned_qty = 0
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 27335aa..768f99e 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -100,7 +100,7 @@
def test_production_plan_sales_orders(self):
item = 'Test Production Item 1'
- so = make_sales_order(item_code=item, qty=5)
+ so = make_sales_order(item_code=item, qty=1)
sales_order = so.name
sales_order_item = so.items[0].name
@@ -124,8 +124,8 @@
wo_doc = frappe.get_doc('Work Order', work_order)
wo_doc.update({
- 'wip_warehouse': '_Test Warehouse 1 - _TC',
- 'fg_warehouse': '_Test Warehouse - _TC'
+ 'wip_warehouse': 'Work In Progress - _TC',
+ 'fg_warehouse': 'Finished Goods - _TC'
})
wo_doc.submit()
@@ -145,6 +145,58 @@
self.assertEqual(sales_orders, [])
+ def test_production_plan_combine_items(self):
+ item = 'Test Production Item 1'
+ so = make_sales_order(item_code=item, qty=1)
+
+ pln = frappe.new_doc('Production Plan')
+ pln.company = so.company
+ pln.get_items_from = 'Sales Order'
+ pln.append('sales_orders', {
+ 'sales_order': so.name,
+ 'sales_order_date': so.transaction_date,
+ 'customer': so.customer,
+ 'grand_total': so.grand_total
+ })
+ so = make_sales_order(item_code=item, qty=2)
+ pln.append('sales_orders', {
+ 'sales_order': so.name,
+ 'sales_order_date': so.transaction_date,
+ 'customer': so.customer,
+ 'grand_total': so.grand_total
+ })
+ pln.combine_items = 1
+ pln.get_items()
+ pln.submit()
+
+ self.assertTrue(pln.po_items[0].planned_qty, 3)
+
+ pln.make_work_order()
+ work_order = frappe.db.get_value('Work Order', {
+ 'production_plan_item': pln.po_items[0].name,
+ 'production_plan': pln.name
+ }, 'name')
+
+ wo_doc = frappe.get_doc('Work Order', work_order)
+ wo_doc.update({
+ 'wip_warehouse': 'Work In Progress - _TC',
+ })
+
+ wo_doc.submit()
+ so_items = []
+ for plan_reference in pln.prod_plan_references:
+ so_items.append(plan_reference.sales_order_item)
+ so_wo_qty = frappe.db.get_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty')
+ self.assertEqual(so_wo_qty, plan_reference.qty)
+
+ wo_doc.cancel()
+ for so_item in so_items:
+ so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
+ self.assertEqual(so_wo_qty, 0.0)
+
+ latest_plan = frappe.get_doc('Production Plan', pln.name)
+ latest_plan.cancel()
+
def test_pp_to_mr_customer_provided(self):
#Material Request from Production Plan for Customer Provided
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
index d0dce53..89ab7aa 100644
--- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
@@ -1,792 +1,229 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-02-22 01:27:49",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 1,
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:27:49",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "include_exploded_items",
+ "item_code",
+ "bom_no",
+ "planned_qty",
+ "column_break_6",
+ "make_work_order_for_sub_assembly_items",
+ "warehouse",
+ "planned_start_date",
+ "section_break_9",
+ "pending_qty",
+ "ordered_qty",
+ "produced_qty",
+ "column_break_17",
+ "description",
+ "stock_uom",
+ "reference_section",
+ "sales_order",
+ "sales_order_item",
+ "column_break_19",
+ "material_request",
+ "material_request_item",
+ "product_bundle_item",
+ "item_reference"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fetch_if_empty": 0,
- "fieldname": "include_exploded_items",
- "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": "Include Exploded 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
- },
+ "columns": 2,
+ "default": "0",
+ "fieldname": "include_exploded_items",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Include Exploded Items"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fetch_if_empty": 0,
- "fieldname": "item_code",
- "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 Code",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_code",
- "oldfieldtype": "Link",
- "options": "Item",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "columns": 2,
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "oldfieldname": "item_code",
+ "oldfieldtype": "Link",
+ "options": "Item",
+ "print_width": "150px",
+ "reqd": 1,
"width": "150px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fetch_if_empty": 0,
- "fieldname": "bom_no",
- "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": "BOM No",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "bom_no",
- "oldfieldtype": "Link",
- "options": "BOM",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "columns": 2,
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "BOM No",
+ "oldfieldname": "bom_no",
+ "oldfieldtype": "Link",
+ "options": "BOM",
+ "print_width": "100px",
+ "reqd": 1,
"width": "100px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "planned_qty",
- "fieldtype": "Float",
- "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": "Planned Qty",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "planned_qty",
- "oldfieldtype": "Currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "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": "planned_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Planned Qty",
+ "oldfieldname": "planned_qty",
+ "oldfieldtype": "Currency",
+ "print_width": "100px",
+ "reqd": 1,
"width": "100px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_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
- },
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "description": "If enabled, system will create the work order for the exploded items against which BOM is available.",
- "fetch_if_empty": 0,
- "fieldname": "make_work_order_for_sub_assembly_items",
- "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": "Make Work Order for Sub Assembly 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
- },
+ "default": "0",
+ "description": "If enabled, system will create the work order for the exploded items against which BOM is available.",
+ "fieldname": "make_work_order_for_sub_assembly_items",
+ "fieldtype": "Check",
+ "label": "Make Work Order for Sub Assembly Items"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "warehouse",
- "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": "For Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "For Warehouse",
+ "options": "Warehouse"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fetch_if_empty": 0,
- "fieldname": "planned_start_date",
- "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": "Planned 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
- },
+ "default": "Today",
+ "fieldname": "planned_start_date",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "Planned Start Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "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": "Quantity and 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
- },
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break",
+ "label": "Quantity and Description"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "pending_qty",
- "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": "Pending Qty",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "prevdoc_reqd_qty",
- "oldfieldtype": "Currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "default": "0",
+ "fieldname": "pending_qty",
+ "fieldtype": "Float",
+ "label": "Pending Qty",
+ "oldfieldname": "prevdoc_reqd_qty",
+ "oldfieldtype": "Currency",
+ "print_width": "100px",
+ "read_only": 1,
"width": "100px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "ordered_qty",
- "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": "Ordered Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "default": "0",
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "label": "Ordered Qty",
+ "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,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "produced_qty",
- "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": "Produced Qty",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "produced_qty",
+ "fieldtype": "Float",
+ "label": "Produced Qty",
+ "no_copy": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_17",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_17",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "description",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "description",
- "oldfieldtype": "Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "200px",
- "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": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Text",
+ "print_width": "200px",
+ "read_only": 1,
"width": "200px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "stock_uom",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "UOM",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "stock_uom",
- "oldfieldtype": "Data",
- "options": "UOM",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "80px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "UOM",
+ "oldfieldname": "stock_uom",
+ "oldfieldtype": "Data",
+ "options": "UOM",
+ "print_width": "80px",
+ "read_only": 1,
+ "reqd": 1,
"width": "80px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "reference_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": "Reference",
- "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": "reference_section",
+ "fieldtype": "Section Break",
+ "label": "Reference"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "sales_order",
- "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": "Sales Order",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "source_docname",
- "oldfieldtype": "Data",
- "options": "Sales Order",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "label": "Sales Order",
+ "oldfieldname": "source_docname",
+ "oldfieldtype": "Data",
+ "options": "Sales Order",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "sales_order_item",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sales Order Item",
- "length": 0,
- "no_copy": 1,
- "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,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "sales_order_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Sales Order Item",
+ "no_copy": 1,
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_19",
- "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_19",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "material_request",
- "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": "Material Request",
- "length": 0,
- "no_copy": 0,
- "options": "Material Request",
- "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
- },
+ "fieldname": "material_request",
+ "fieldtype": "Link",
+ "label": "Material Request",
+ "options": "Material Request",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "material_request_item",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "material_request_item",
- "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": "material_request_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "material_request_item"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "product_bundle_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": "Product Bundle Item",
- "length": 0,
- "no_copy": 1,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "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": "product_bundle_item",
+ "fieldtype": "Link",
+ "label": "Product Bundle Item",
+ "no_copy": 1,
+ "options": "Item",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "item_reference",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Item Reference"
}
- ],
- "has_web_view": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-04-08 23:09:57.199423",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Production Plan Item",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-28 19:14:57.772123",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Plan Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "ASC"
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py b/erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py
diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
new file mode 100644
index 0000000..84dee4a
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
@@ -0,0 +1,52 @@
+{
+ "actions": [],
+ "creation": "2021-04-22 10:32:58.896330",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_reference",
+ "sales_order",
+ "sales_order_item",
+ "qty"
+ ],
+ "fields": [
+ {
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Sales Order Reference",
+ "options": "Sales Order"
+ },
+ {
+ "fieldname": "sales_order_item",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Sales Order Item"
+ },
+ {
+ "fieldname": "qty",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "qty"
+ },
+ {
+ "fieldname": "item_reference",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Item Reference"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-07 17:03:49.707487",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Plan Item Reference",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py
new file mode 100644
index 0000000..51fbc36
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class ProductionPlanItemReference(Document):
+ pass
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index a6086fb..3e5a72d 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -76,9 +76,9 @@
frm.set_query("production_item", function() {
return {
query: "erpnext.controllers.queries.item_query",
- filters:[
- ['is_stock_item', '=',1]
- ]
+ filters: {
+ "is_stock_item": 1,
+ }
};
});
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 8507f5e..2600790 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -240,8 +240,12 @@
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
if not self.fg_warehouse:
frappe.throw(_("For Warehouse is required before Submit"))
+
+ if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
+ self.update_work_order_qty_in_combined_so()
+ else:
+ self.update_work_order_qty_in_so()
- self.update_work_order_qty_in_so()
self.update_reserved_qty_for_production()
self.update_completed_qty_in_material_request()
self.update_planned_qty()
@@ -250,9 +254,13 @@
def on_cancel(self):
self.validate_cancel()
-
frappe.db.set(self,'status', 'Cancelled')
- self.update_work_order_qty_in_so()
+
+ if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
+ self.update_work_order_qty_in_combined_so()
+ else:
+ self.update_work_order_qty_in_so()
+
self.delete_job_card()
self.update_completed_qty_in_material_request()
self.update_planned_qty()
@@ -357,7 +365,28 @@
work_order_qty = qty[0][0] if qty and qty[0][0] else 0
frappe.db.set_value('Sales Order Item',
self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2))
+
+ def update_work_order_qty_in_combined_so(self):
+ total_bundle_qty = 1
+ if self.product_bundle_item:
+ total_bundle_qty = frappe.db.sql(""" select sum(qty) from
+ `tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0]
+ if not total_bundle_qty:
+ # product bundle is 0 (product bundle allows 0 qty for items)
+ total_bundle_qty = 1
+
+ prod_plan = frappe.get_doc('Production Plan', self.production_plan)
+ item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item')
+
+ for plan_reference in prod_plan.prod_plan_references:
+ work_order_qty = 0.0
+ if plan_reference.item_reference == item_reference:
+ if self.docstatus == 1:
+ work_order_qty = flt(plan_reference.qty) / total_bundle_qty
+ frappe.db.set_value('Sales Order Item',
+ plan_reference.sales_order_item, 'work_order_qty', work_order_qty)
+
def update_completed_qty_in_material_request(self):
if self.material_request:
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index efc072e..30be585 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -28,7 +28,7 @@
def setup_subscription(self):
non_profit_settings = frappe.get_doc('Non Profit Settings')
if not non_profit_settings.enable_razorpay_for_memberships:
- frappe.throw('Please check Enable Razorpay for Memberships in {0} to setup subscription').format(
+ frappe.throw(_('Please check Enable Razorpay for Memberships in {0} to setup subscription')).format(
get_link_to_form('Non Profit Settings', 'Non Profit Settings'))
controller = get_payment_gateway_controller("Razorpay")
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index d4655e1..93689a0 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -779,4 +779,7 @@
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
+execute:frappe.rename_doc("Workspace", "Loan Management", "Loans", force=True)
erpnext.patches.v13_0.update_timesheet_changes
+erpnext.patches.v13_0.set_training_event_attendance
+erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
diff --git a/erpnext/patches/v12_0/purchase_receipt_status.py b/erpnext/patches/v12_0/purchase_receipt_status.py
index 1a99b31..459221e 100644
--- a/erpnext/patches/v12_0/purchase_receipt_status.py
+++ b/erpnext/patches/v12_0/purchase_receipt_status.py
@@ -19,6 +19,9 @@
logger.info("purchase_receipt_status: begin patch, PR count: {}"
.format(len(affected_purchase_receipts)))
+ frappe.reload_doc("stock", "doctype", "Purchase Receipt")
+ frappe.reload_doc("stock", "doctype", "Purchase Receipt Item")
+
for pr in affected_purchase_receipts:
pr_name = pr[0]
diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
new file mode 100644
index 0000000..48325fc
--- /dev/null
+++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ if frappe.db.exists('DocType', 'Issue'):
+ frappe.reload_doc("support", "doctype", "issue")
+ rename_status()
+
+def rename_status():
+ frappe.db.sql("""
+ UPDATE
+ `tabIssue`
+ SET
+ status = 'On Hold'
+ WHERE
+ status = 'Hold'
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/set_training_event_attendance.py b/erpnext/patches/v13_0/set_training_event_attendance.py
new file mode 100644
index 0000000..18cad8d
--- /dev/null
+++ b/erpnext/patches/v13_0/set_training_event_attendance.py
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc('hr', 'doctype', 'training_event')
+ frappe.reload_doc('hr', 'doctype', 'training_event_employee')
+
+ frappe.db.sql("update `tabTraining Event Employee` set `attendance` = 'Present'")
+ frappe.db.sql("update `tabTraining Event Employee` set `is_mandatory` = 1 where `attendance` = 'Mandatory'")
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json
index 5e17a5c..d9efe45 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.json
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json
@@ -7,25 +7,30 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "employee_details_section",
"naming_series",
"employee",
"employee_name",
- "salary_component",
- "type",
- "amount",
- "ref_doctype",
- "ref_docname",
- "amended_from",
"column_break_5",
"company",
"department",
+ "salary_details_section",
+ "salary_component",
+ "type",
"currency",
+ "amount",
+ "column_break_13",
+ "is_recurring",
+ "payroll_date",
"from_date",
"to_date",
- "payroll_date",
- "is_recurring",
+ "properties_and_references_section",
+ "deduct_full_tax_on_selected_payroll_date",
+ "ref_doctype",
+ "ref_docname",
+ "column_break_22",
"overwrite_salary_structure_amount",
- "deduct_full_tax_on_selected_payroll_date"
+ "amended_from"
],
"fields": [
{
@@ -81,7 +86,7 @@
},
{
"depends_on": "eval:(doc.is_recurring==0)",
- "description": "Date on which this component is applied",
+ "description": "The date on which Salary Component with Amount will contribute for Earnings/Deduction in Salary Slip. ",
"fieldname": "payroll_date",
"fieldtype": "Date",
"in_list_view": 1,
@@ -159,6 +164,7 @@
"fieldname": "ref_docname",
"fieldtype": "Dynamic Link",
"label": "Reference Document",
+ "no_copy": 1,
"options": "ref_doctype",
"read_only": 1
},
@@ -171,11 +177,34 @@
"print_hide": 1,
"read_only": 1,
"reqd": 1
+ },
+ {
+ "fieldname": "employee_details_section",
+ "fieldtype": "Section Break",
+ "label": "Employee Details"
+ },
+ {
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_22",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "salary_details_section",
+ "fieldtype": "Section Break",
+ "label": "Salary Details"
+ },
+ {
+ "fieldname": "properties_and_references_section",
+ "fieldtype": "Section Break",
+ "label": "Properties and References"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:33:59.098532",
+ "modified": "2021-05-26 11:10:00.812698",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Additional Salary",
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index 7528bf7..b80b320 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -15,7 +15,13 @@
from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry, create_loan_type, create_loan_accounts
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
+test_dependencies = ['Holiday List']
+
class TestPayrollEntry(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List')
+
def setUp(self):
for dt in ["Salary Slip", "Salary Component", "Salary Component Account",
"Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]:
diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py
index f2b9c25..ea6bdb5 100644
--- a/erpnext/projects/report/project_profitability/test_project_profitability.py
+++ b/erpnext/projects/report/project_profitability/test_project_profitability.py
@@ -8,7 +8,7 @@
from erpnext.projects.report.project_profitability.project_profitability import execute
class TestProjectProfitability(unittest.TestCase):
- @classmethod
+
def setUp(self):
emp = make_employee('test_employee_9@salary.com', company='_Test Company')
if not frappe.db.exists('Salary Component', 'Timesheet Component'):
@@ -21,7 +21,7 @@
self.sales_invoice.due_date = nowdate()
self.sales_invoice.submit()
- frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 8)
+ frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8)
def test_project_profitability(self):
filters = {
@@ -55,4 +55,4 @@
def tearDown(self):
frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel()
frappe.get_doc("Salary Slip", self.salary_slip.name).cancel()
- frappe.get_doc("Timesheet", self.timesheet.name).cancel()
\ No newline at end of file
+ frappe.get_doc("Timesheet", self.timesheet.name).cancel()
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index e153e6c..982b1fe 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -261,11 +261,19 @@
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
return;
}
- var me = this;
- var inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)
+
+ const me = this;
+ if (!this.frm.is_new() && this.frm.doc.docstatus === 0) {
+ this.frm.add_custom_button(__("Quality Inspection(s)"), () => {
+ me.make_quality_inspection();
+ }, __("Create"));
+ this.frm.page.set_inner_btn_group_as_primary(__('Create'));
+ }
+
+ const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)
? "Incoming" : "Outgoing";
- var quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
+ let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
quality_inspection_field.get_route_options_for_new_doc = function(row) {
if(me.frm.is_new()) return;
return {
@@ -280,7 +288,7 @@
}
this.frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
+ let d = locals[cdt][cdn];
return {
filters: {
docstatus: 1,
@@ -953,15 +961,15 @@
(this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length)) {
var message1 = "";
var message2 = "";
- var final_message = "Please clear the ";
+ var final_message = __("Please clear the") + " ";
if (this.frm.doc.payment_terms_template) {
- message1 = "selected Payment Terms Template";
+ message1 = __("selected Payment Terms Template");
final_message = final_message + message1;
}
if ((this.frm.doc.payment_schedule || []).length) {
- message2 = "Payment Schedule Table";
+ message2 = __("Payment Schedule Table");
if (message1.length !== 0) message2 = " and " + message2;
final_message = final_message + message2;
}
@@ -1949,6 +1957,130 @@
});
},
+ make_quality_inspection: function () {
+ let data = [];
+ const fields = [
+ {
+ label: "Items",
+ fieldtype: "Table",
+ fieldname: "items",
+ cannot_add_rows: true,
+ in_place_edit: true,
+ data: data,
+ get_data: () => {
+ return data;
+ },
+ fields: [
+ {
+ fieldtype: "Data",
+ fieldname: "docname",
+ hidden: true
+ },
+ {
+ fieldtype: "Read Only",
+ fieldname: "item_code",
+ label: __("Item Code"),
+ in_list_view: true
+ },
+ {
+ fieldtype: "Read Only",
+ fieldname: "item_name",
+ label: __("Item Name"),
+ in_list_view: true
+ },
+ {
+ fieldtype: "Float",
+ fieldname: "qty",
+ label: __("Accepted Quantity"),
+ in_list_view: true,
+ read_only: true
+ },
+ {
+ fieldtype: "Float",
+ fieldname: "sample_size",
+ label: __("Sample Size"),
+ reqd: true,
+ in_list_view: true
+ },
+ {
+ fieldtype: "Data",
+ fieldname: "description",
+ label: __("Description"),
+ hidden: true
+ },
+ {
+ fieldtype: "Data",
+ fieldname: "serial_no",
+ label: __("Serial No"),
+ hidden: true
+ },
+ {
+ fieldtype: "Data",
+ fieldname: "batch_no",
+ label: __("Batch No"),
+ hidden: true
+ }
+ ]
+ }
+ ];
+
+ const me = this;
+ const dialog = new frappe.ui.Dialog({
+ title: __("Select Items for Quality Inspection"),
+ fields: fields,
+ primary_action: function () {
+ const data = dialog.get_values();
+ frappe.call({
+ method: "erpnext.controllers.stock_controller.make_quality_inspections",
+ args: {
+ doctype: me.frm.doc.doctype,
+ docname: me.frm.doc.name,
+ items: data.items
+ },
+ freeze: true,
+ callback: function (r) {
+ if (r.message.length > 0) {
+ if (r.message.length === 1) {
+ frappe.set_route("Form", "Quality Inspection", r.message[0]);
+ } else {
+ frappe.route_options = {
+ "reference_type": me.frm.doc.doctype,
+ "reference_name": me.frm.doc.name
+ };
+ frappe.set_route("List", "Quality Inspection");
+ }
+ }
+ dialog.hide();
+ }
+ });
+ },
+ primary_action_label: __("Create")
+ });
+
+ this.frm.doc.items.forEach(item => {
+ if (!item.quality_inspection) {
+ let dialog_items = dialog.fields_dict.items;
+ dialog_items.df.data.push({
+ "docname": item.name,
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "qty": item.qty,
+ "description": item.description,
+ "serial_no": item.serial_no,
+ "batch_no": item.batch_no
+ });
+ dialog_items.grid.refresh();
+ }
+ });
+
+ data = dialog.fields_dict.items.df.data;
+ if (!data.length) {
+ frappe.msgprint(__("All items in this document already have a linked Quality Inspection."));
+ } else {
+ dialog.show();
+ }
+ },
+
get_method_for_payment: function(){
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
index e789923..aa9bba1 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -644,14 +644,14 @@
frappe.help.help_links["List/Asset"] = [
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
},
];
frappe.help.help_links["List/Asset Category"] = [
{
label: "Asset Category",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/asset/asset-category",
},
];
@@ -663,7 +663,7 @@
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
{
label: "Item Price",
- url: docsUrl + "user/manual/en/stock/item/item-price",
+ url: docsUrl + "user/manual/en/stock/item-price",
},
{
label: "Barcode",
@@ -672,25 +672,25 @@
},
{
label: "Item Wise Taxation",
- url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+ url: docsUrl + "user/manual/en/accounts/item-tax-template",
},
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
},
{
label: "Item Codification",
- url: docsUrl + "user/manual/en/stock/item/item-codification",
+ url: docsUrl + "user/manual/en/stock/articles/item-codification",
},
{
label: "Item Variants",
- url: docsUrl + "user/manual/en/stock/item/item-variants",
+ url: docsUrl + "user/manual/en/stock/item-variants",
},
{
label: "Item Valuation",
url:
docsUrl +
- "user/manual/en/stock/item/item-valuation-fifo-and-moving-average",
+ "user/manual/en/stock/articles/item-valuation-fifo-and-moving-average",
},
];
@@ -698,7 +698,7 @@
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
{
label: "Item Price",
- url: docsUrl + "user/manual/en/stock/item/item-price",
+ url: docsUrl + "user/manual/en/stock/item-price",
},
{
label: "Barcode",
@@ -707,19 +707,19 @@
},
{
label: "Item Wise Taxation",
- url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+ url: docsUrl + "user/manual/en/accounts/item-tax-template",
},
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
},
{
label: "Item Codification",
- url: docsUrl + "user/manual/en/stock/item/item-codification",
+ url: docsUrl + "user/manual/en/stock/articles/item-codification",
},
{
label: "Item Variants",
- url: docsUrl + "user/manual/en/stock/item/item-variants",
+ url: docsUrl + "user/manual/en/stock/item-variants",
},
{
label: "Item Valuation",
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 159a8a4..9402cf9 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -1,4 +1,3 @@
-@import "frappe/public/scss/desk/variables";
@import "frappe/public/scss/common/mixins";
body.product-page {
@@ -74,15 +73,6 @@
}
}
- // .card-body {
- // text-align: center;
- // }
-
- // .featured-item {
- // .card-body {
- // text-align: left;
- // }
- // }
.card-img-container {
height: 210px;
width: 100%;
@@ -217,12 +207,12 @@
border-color: var(--table-border-color) !important;
padding: 15px;
- @include media-breakpoint-between(xs, md) {
+ @media (max-width: var(--md-width)) {
height: 300px;
width: 300px;
}
- @include media-breakpoint-up(lg) {
+ @media (min-width: var(--lg-width)) {
height: 350px;
width: 350px;
}
@@ -233,11 +223,12 @@
}
.item-slideshow {
- @include media-breakpoint-between(xs, md) {
+
+ @media (max-width: var(--md-width)) {
max-height: 320px;
}
- @include media-breakpoint-up(lg) {
+ @media (min-width: var(--lg-width)) {
max-height: 430px;
}
@@ -254,7 +245,7 @@
cursor: pointer;
&:hover, &.active {
- border-color: $primary;
+ border-color: var(--primary);
}
}
@@ -316,12 +307,9 @@
}
.item-group-slideshow {
- .item-group-description {
- // max-width: 900px;
- }
.carousel-inner.rounded-carousel {
- border-radius: $card-border-radius;
+ border-radius: var(--card-border-radius);
}
}
diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss
index 56b717c..f4325c0 100644
--- a/erpnext/public/scss/website.scss
+++ b/erpnext/public/scss/website.scss
@@ -1,4 +1,3 @@
-@import "frappe/public/scss/website/variables";
.filter-options {
max-height: 300px;
@@ -14,7 +13,7 @@
}
&.active {
- border-color: $primary;
+ border-color: var(--primary);
.check {
display: inline-flex;
@@ -25,7 +24,7 @@
.check {
display: inline-flex;
padding: 0.25rem;
- background: $primary;
+ background: var(--primary);
color: white;
border-radius: 50%;
font-size: 12px;
@@ -38,12 +37,12 @@
}
.result {
- border-bottom: 1px solid $border-color;
+ border-bottom: 1px solid var(--border-color);
}
.transaction-list-item {
padding: 1rem 0;
- border-top: 1px solid $border-color;
+ border-top: 1px solid var(--border-color);
position: relative;
a.transaction-item-link {
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 3ddcc58..6415204 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -310,7 +310,7 @@
self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category')
- place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply', '00-Other Territory')
+ place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply') or '00-Other Territory'
if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \
self.gst_details.get("gst_state") != place_of_supply.split("-")[1]:
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
index 826d51f..122c15f 100644
--- a/erpnext/regional/germany/utils/datev/datev_csv.py
+++ b/erpnext/regional/germany/utils/datev/datev_csv.py
@@ -55,8 +55,7 @@
quoting=QUOTE_NONNUMERIC
)
- if not six.PY2:
- data = data.encode('latin_1', errors='replace')
+ data = data.encode('latin_1', errors='replace')
header = get_header(filters, csv_class)
header = ';'.join(header).encode('latin_1', errors='replace')
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 70e6d07..843fb01 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -43,7 +43,7 @@
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')
no_taxes_applied = not doc.get('taxes')
- has_non_gst_item = any(d for d in doc.get('items') if d.get('is_non_gst'))
+ 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
@@ -534,11 +534,9 @@
return einvoice
def safe_json_load(json_string):
- JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
-
try:
return json.loads(json_string)
- except JSONDecodeError as e:
+ 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)
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index fc227de..075c698 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -500,7 +500,7 @@
if not isinstance(docname, list):
# removes characters not allowed in a filename (https://stackoverflow.com/a/38766141/4767738)
- filename_prefix = re.sub('[^\w_.)( -]', '', docname)
+ filename_prefix = re.sub(r'[^\w_.)( -]', '', docname)
frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(filename_prefix, frappe.utils.random_string(5))
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 1e28a40..b7c0962 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -574,7 +574,7 @@
def get_json(filters, report_name, data):
filters = json.loads(filters)
report_data = json.loads(data)
- gstin = get_company_gstin_number(filters["company"])
+ gstin = get_company_gstin_number(filters["company"], filters["company_address"])
fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
@@ -810,23 +810,29 @@
return {"num": int(num), "itm_det": itm_det}
-def get_company_gstin_number(company):
- filters = [
- ["is_your_company_address", "=", 1],
- ["Dynamic Link", "link_doctype", "=", "Company"],
- ["Dynamic Link", "link_name", "=", company],
- ["Dynamic Link", "parenttype", "=", "Address"],
- ]
+def get_company_gstin_number(company, address=None):
+ if address:
+ gstin = frappe.db.get_value("Address", address, "gstin")
- gstin = frappe.get_all("Address", filters=filters, fields=["gstin"])
-
- if gstin:
- return gstin[0]["gstin"]
- else:
- frappe.throw(_("Please set valid GSTIN No. in Company Address for company {0}").format(
- frappe.bold(company)
+ if not gstin:
+ filters = [
+ ["is_your_company_address", "=", 1],
+ ["Dynamic Link", "link_doctype", "=", "Company"],
+ ["Dynamic Link", "link_name", "=", company],
+ ["Dynamic Link", "parenttype", "=", "Address"],
+ ]
+ gstin = frappe.get_all("Address", filters=filters, pluck="gstin")
+ if gstin:
+ gstin[0]
+
+ if not gstin:
+ address = frappe.bold(address) if address else ""
+ frappe.throw(_("Please set valid GSTIN No. in Company Address {} for company {}").format(
+ address, frappe.bold(company)
))
+ return gstin
+
@frappe.whitelist()
def download_json_file():
''' download json content in a file '''
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 750a1a6..cb811df 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -15,7 +15,6 @@
data = dict()
result = []
- allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
warehouse, hide_unavailable_items = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items'])
if not frappe.db.exists('Item Group', item_group):
@@ -62,7 +61,6 @@
`tabItem` item {bin_join_selection}
WHERE
item.disabled = 0
- AND item.is_stock_item = 1
AND item.has_variants = 0
AND item.is_sales_item = 1
AND item.is_fixed_asset = 0
@@ -84,6 +82,7 @@
), {'warehouse': warehouse}, as_dict=1)
if items_data:
+ items_data = filter_service_items(items_data)
items = [d.item_code for d in items_data]
item_prices_data = frappe.get_all("Item Price",
fields = ["item_code", "price_list_rate", "currency"],
@@ -96,10 +95,7 @@
for item in items_data:
item_code = item.item_code
item_price = item_prices.get(item_code) or {}
- if allow_negative_stock:
- item_stock_qty = frappe.db.sql("""select ifnull(sum(actual_qty), 0) from `tabBin` where item_code = %s""", item_code)[0][0]
- else:
- item_stock_qty = get_stock_availability(item_code, warehouse)
+ item_stock_qty = get_stock_availability(item_code, warehouse)
row = {}
row.update(item)
@@ -135,6 +131,14 @@
return {}
+def filter_service_items(items):
+ for item in items:
+ if not item['is_stock_item']:
+ if not frappe.db.exists('Product Bundle', item['item_code']):
+ items.remove(item)
+
+ return items
+
def get_conditions(item_code, serial_no, batch_no, barcode):
if serial_no or batch_no or barcode:
return "item.name = {0}".format(frappe.db.escape(item_code))
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 4f4f1b2..ae3f9e3 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -241,10 +241,8 @@
events: {
get_frm: () => this.frm,
- cart_item_clicked: (item_code, batch_no, uom) => {
- const search_field = batch_no ? 'batch_no' : 'item_code';
- const search_value = batch_no || item_code;
- const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom);
+ cart_item_clicked: (item_code, batch_no, uom, rate) => {
+ const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
this.item_details.toggle_item_details_section(item_row);
},
@@ -275,18 +273,25 @@
this.cart.toggle_numpad(minimize);
},
- form_updated: async (cdt, cdn, fieldname, value) => {
+ form_updated: (cdt, cdn, fieldname, value) => {
const item_row = frappe.model.get_doc(cdt, cdn);
if (item_row && item_row[fieldname] != value) {
- const { item_code, batch_no, uom } = this.item_details.current_item;
+ const { item_code, batch_no, uom, rate } = this.item_details.current_item;
const event = {
field: fieldname,
value,
- item: { item_code, batch_no, uom }
+ item: { item_code, batch_no, uom, rate }
}
return this.on_cart_update(event)
}
+
+ return Promise.resolve();
+ },
+
+ highlight_cart_item: (item) => {
+ const cart_item = this.cart.get_cart_item(item);
+ this.cart.toggle_item_highlight(cart_item);
},
item_field_focused: (fieldname) => {
@@ -501,8 +506,8 @@
let item_row = undefined;
try {
let { field, value, item } = args;
- const { item_code, batch_no, serial_no, uom } = item;
- item_row = this.get_item_from_frm(item_code, batch_no, uom);
+ const { item_code, batch_no, serial_no, uom, rate } = item;
+ item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
const item_selected_from_selector = field === 'qty' && value === "+1"
@@ -535,7 +540,7 @@
item_selected_from_selector && (value = flt(value))
- const args = { item_code, batch_no, [field]: value };
+ const args = { item_code, batch_no, rate, [field]: value };
if (serial_no) {
await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
@@ -550,9 +555,11 @@
await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
await this.trigger_new_item_events(item_row);
-
- this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
+
this.update_cart_html(item_row);
+
+ this.item_details.$component.is(':visible') && this.edit_item_details_of(item_row);
+ this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
}
} catch (error) {
@@ -563,12 +570,13 @@
}
}
- get_item_from_frm(item_code, batch_no, uom) {
+ get_item_from_frm(item_code, batch_no, uom, rate) {
const has_batch_no = batch_no;
return this.frm.doc.items.find(
i => i.item_code === item_code
&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
&& (i.uom === uom)
+ && (i.rate == rate)
);
}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 11a63b3..f5019f5 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -184,7 +184,8 @@
const item_code = unescape($cart_item.attr('data-item-code'));
const batch_no = unescape($cart_item.attr('data-batch-no'));
const uom = unescape($cart_item.attr('data-uom'));
- me.events.cart_item_clicked(item_code, batch_no, uom);
+ const rate = unescape($cart_item.attr('data-rate'));
+ me.events.cart_item_clicked(item_code, batch_no, uom, rate);
this.numpad_value = '';
});
@@ -520,28 +521,34 @@
}
}
- get_cart_item({ item_code, batch_no, uom }) {
+ get_cart_item({ item_code, batch_no, uom, rate }) {
const batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
const uom_attr = `[data-uom="${escape(uom)}"]`;
+ const rate_attr = `[data-rate="${escape(rate)}"]`;
const item_selector = batch_no ?
- `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`;
+ `.cart-item-wrapper${batch_attr}${uom_attr}${rate_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}${rate_attr}`;
return this.$cart_items_wrapper.find(item_selector);
}
+ get_item_from_frm(item) {
+ const doc = this.events.get_frm().doc;
+ const { item_code, batch_no, uom, rate } = item;
+ const search_field = batch_no ? 'batch_no' : 'item_code';
+ const search_value = batch_no || item_code;
+
+ return doc.items.find(i => i[search_field] === search_value && i.uom === uom && i.rate === rate);
+ }
+
update_item_html(item, remove_item) {
const $item = this.get_cart_item(item);
if (remove_item) {
$item && $item.next().remove() && $item.remove();
} else {
- const { item_code, batch_no, uom } = item;
- const search_field = batch_no ? 'batch_no' : 'item_code';
- const search_value = batch_no || item_code;
- const item_row = this.events.get_frm().doc.items.find(i => i[search_field] === search_value && i.uom === uom);
-
+ const item_row = this.get_item_from_frm(item);
this.render_cart_item(item_row, $item);
}
@@ -559,7 +566,7 @@
this.$cart_items_wrapper.append(
`<div class="cart-item-wrapper"
data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
- data-batch-no="${escape(item_data.batch_no || '')}">
+ data-batch-no="${escape(item_data.batch_no || '')}" data-rate="${escape(item_data.rate)}">
</div>
<div class="seperator"></div>`
)
@@ -636,13 +643,23 @@
function get_item_image_html() {
const { image, item_name } = item_data;
if (image) {
- return `<div class="item-image"><img src="${image}" alt="${image}""></div>`;
+ return `
+ <div class="item-image">
+ <img
+ onerror="cur_pos.cart.handle_broken_image(this)"
+ src="${image}" alt="${frappe.get_abbr(item_name)}"">
+ </div>`;
} else {
return `<div class="item-image item-abbr">${frappe.get_abbr(item_name)}</div>`;
}
}
}
+ handle_broken_image($img) {
+ const item_abbr = $($img).attr('alt');
+ $($img).parent().replaceWith(`<div class="item-image item-abbr">${item_abbr}</div>`);
+ }
+
scroll_to_item($item) {
if ($item.length === 0) return;
const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index 32a4556..df62696 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -54,13 +54,24 @@
this.$dicount_section = this.$component.find('.discount-section');
}
- toggle_item_details_section(item) {
- const { item_code, batch_no, uom } = this.current_item;
+ has_item_has_changed(item) {
+ const { item_code, batch_no, uom, rate } = this.current_item;
const item_code_is_same = item && item_code === item.item_code;
const batch_is_same = item && batch_no == item.batch_no;
const uom_is_same = item && uom === item.uom;
+ const rate_is_same = item && rate === item.rate;
+
+ if (!item)
+ return false;
- this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true;
+ if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same)
+ return false;
+
+ return true;
+ }
+
+ toggle_item_details_section(item) {
+ this.item_has_changed = this.has_item_has_changed(item);
this.events.toggle_item_selector(this.item_has_changed);
this.toggle_component(this.item_has_changed);
@@ -72,11 +83,12 @@
this.item_row = item;
this.currency = this.events.get_frm().doc.currency;
- this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom };
+ this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom, rate: item.rate };
this.render_dom(item);
this.render_discount_dom(item);
this.render_form(item);
+ this.events.highlight_cart_item(item);
} else {
this.validate_serial_batch_item();
this.current_item = {};
@@ -198,12 +210,14 @@
if (this.allow_rate_change) {
this.rate_control.df.onchange = function() {
if (this.value || flt(this.value) === 0) {
+ me.events.set_value_in_current_cart_item('rate', this.value);
me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
const item_row = frappe.get_doc(me.doctype, me.name);
const doc = me.events.get_frm().doc;
me.$item_price.html(format_currency(item_row.rate, doc.currency));
me.render_discount_dom(item_row);
});
+ me.current_item.rate = this.value;
}
};
} else {
@@ -292,11 +306,7 @@
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
const field_control = this[`${fieldname}_control`];
- const { item_code, batch_no, uom } = this.current_item;
- const item_code_is_same = item_code === item_row.item_code;
- const batch_is_same = batch_no == item_row.batch_no;
- const uom_is_same = uom === item_row.uom;
- const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false;
+ const item_is_same = !this.has_item_has_changed(item_row);
if (item_is_same && field_control && field_control.get_value() !== value) {
field_control.set_value(value);
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 b8a82a9..5b48725 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -78,7 +78,7 @@
get_item_html(item) {
const me = this;
// eslint-disable-next-line no-unused-vars
- const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
+ const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item;
const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
let qty_to_display = actual_qty;
@@ -94,7 +94,11 @@
<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
</div>
<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
- <img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
+ <img
+ onerror="cur_pos.item_selector.handle_broken_image(this)"
+ class="h-full" src="${item_image}"
+ alt="${frappe.get_abbr(item.item_name)}"
+ style="object-fit: cover;">
</div>`;
} else {
return `<div class="item-qty-pill">
@@ -108,6 +112,7 @@
`<div class="item-wrapper"
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
+ data-rate="${escape(price_list_rate)}"
title="${item.item_name}">
${get_item_image_html()}
@@ -116,12 +121,17 @@
<div class="item-name">
${frappe.ellipsis(item.item_name, 18)}
</div>
- <div class="item-rate">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
+ <div class="item-rate">${format_currency(price_list_rate, item.currency, 0) || 0}</div>
</div>
</div>`
);
}
+ handle_broken_image($img) {
+ const item_abbr = $($img).attr('alt');
+ $($img).parent().replaceWith(`<div class="item-display abbr">${item_abbr}</div>`);
+ }
+
make_search_bar() {
const me = this;
const doc = me.events.get_frm().doc;
@@ -213,13 +223,15 @@
let batch_no = unescape($item.attr('data-batch-no'));
let serial_no = unescape($item.attr('data-serial-no'));
let uom = unescape($item.attr('data-uom'));
+ let rate = unescape($item.attr('data-rate'));
// escape(undefined) returns "undefined" then unescape returns "undefined"
batch_no = batch_no === "undefined" ? undefined : batch_no;
serial_no = serial_no === "undefined" ? undefined : serial_no;
uom = uom === "undefined" ? undefined : uom;
+ rate = rate === "undefined" ? undefined : rate;
- me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }});
+ me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom, rate }});
me.set_search_value('');
});
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index acf4eb3..cec831d 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -241,7 +241,7 @@
send_email() {
const frm = this.events.get_frm();
- const recipients = this.email_dialog.get_values().recipients;
+ const recipients = this.email_dialog.get_values().email_id;
const doc = this.doc || frm.doc;
const print_format = frm.pos_print_format;
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 9957aad..b24048d 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -90,12 +90,6 @@
frm.toggle_enable("default_currency", (frm.doc.__onload &&
!frm.doc.__onload.transactions_exist));
- if (frm.has_perm('write')) {
- frm.add_custom_button(__('Create Tax Template'), function() {
- frm.trigger("make_default_tax_template");
- });
- }
-
if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});
@@ -121,17 +115,21 @@
}
if (frm.has_perm('write')) {
- frm.add_custom_button(__('Default Tax Template'), function() {
+ frm.add_custom_button(__('Create Tax Template'), function() {
frm.trigger("make_default_tax_template");
- }, __('Create'));
+ }, __('Manage'));
+ }
+
+ if (frappe.user.has_role('System Manager')) {
+ if (frm.has_perm('write')) {
+ frm.add_custom_button(__('Delete Transactions'), function() {
+ frm.trigger("delete_company_transactions");
+ }, __('Manage'));
+ }
}
}
erpnext.company.set_chart_of_accounts_options(frm.doc);
-
- if (!frappe.user.has_role('System Manager')) {
- frm.get_field("delete_company_transactions").hide();
- }
},
make_default_tax_template: function(frm) {
@@ -145,11 +143,6 @@
})
},
- onload_post_render: function(frm) {
- if(frm.get_field("delete_company_transactions").$input)
- frm.get_field("delete_company_transactions").$input.addClass("btn-danger");
- },
-
country: function(frm) {
erpnext.company.set_chart_of_accounts_options(frm.doc);
},
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 83cbf47..061986d 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -99,7 +99,6 @@
"company_description",
"registration_info",
"registration_details",
- "delete_company_transactions",
"lft",
"rgt",
"old_parent"
@@ -667,11 +666,6 @@
"oldfieldtype": "Code"
},
{
- "fieldname": "delete_company_transactions",
- "fieldtype": "Button",
- "label": "Delete Company Transactions"
- },
- {
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
@@ -747,7 +741,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2021-02-16 15:53:37.167589",
+ "modified": "2021-05-07 03:11:28.189740",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 8c97322..340d89b 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -249,7 +249,7 @@
card = cache.get(cache_key)
if card:
- card = eval(card)
+ card = frappe.safe_eval(card)
else:
card = frappe._dict(getattr(self, "get_" + key)())
@@ -808,7 +808,6 @@
val = balance_on_to_date - balance_before_from_date
else:
last_year_closing_balance = get_balance_on(account, date=fy_start_date - timedelta(days=1))
- print(fy_start_date - timedelta(days=1), last_year_closing_balance)
val = balance_on_to_date + (last_year_closing_balance - balance_before_from_date)
return val
@@ -837,4 +836,4 @@
elif frequency == "Monthly":
to_date = add_to_date(from_date, months=1)
- return from_date, to_date
\ No newline at end of file
+ return from_date, to_date
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py
index e587217..a0ba1ef 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.py
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.py
@@ -59,13 +59,15 @@
# Make property setters to hide rounded total fields
for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
- "Supplier Quotation", "Purchase Order", "Purchase Invoice"):
+ "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"):
make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
+ make_property_setter(doctype, "disable_rounded_total", "default", cint(self.disable_rounded_total), "Text", validate_fields_for_doctype=False)
+
def toggle_in_words(self):
self.disable_in_words = cint(self.disable_in_words)
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index c7220cb..bbee74c 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -39,7 +39,7 @@
if cint(frappe.db.get_single_value('System Settings', 'setup_complete') or 0):
message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed.
You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall"""
- frappe.throw(message)
+ frappe.throw(message) # nosemgrep
def set_single_defaults():
diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py
index 429a558..5019837 100644
--- a/erpnext/setup/setup_wizard/operations/taxes_setup.py
+++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py
@@ -106,6 +106,9 @@
'charge_type': 'On Net Total'
}
+ if doctype == 'Purchase Taxes and Charges Template':
+ tax_row_defaults['add_deduct_tax'] = 'Add'
+
# if account_head is a dict, search or create the account and get it's name
if isinstance(account_data, dict):
tax_row_defaults['description'] = '{0} @ {1}'.format(account_data.get('account_name'), account_data.get('tax_rate'))
diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/shopping_cart/test_shopping_cart.py
index d857bf5..ac61aeb 100644
--- a/erpnext/shopping_cart/test_shopping_cart.py
+++ b/erpnext/shopping_cart/test_shopping_cart.py
@@ -7,7 +7,7 @@
from frappe.utils import nowdate, add_months
from erpnext.shopping_cart.cart import _get_cart_quotation, update_cart, get_party
from erpnext.tests.utils import create_test_contact_and_address
-
+from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
# test_dependencies = ['Payment Terms Template']
@@ -125,7 +125,7 @@
tax_rule = frappe.get_test_records("Tax Rule")[0]
try:
frappe.get_doc(tax_rule).insert()
- except frappe.DuplicateEntryError:
+ except (frappe.DuplicateEntryError, ConflictingTaxRule):
pass
def create_quotation(self):
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index db05739..a657ecf 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -230,7 +230,6 @@
},
],
});
- var submitted = false;
dialog.show();
dialog.get_field('item_code').set_input(item);
@@ -254,7 +253,6 @@
}
dialog.set_primary_action(__('Submit'), function () {
- if(submitted) return;
var values = dialog.get_values();
if (!values) {
return;
@@ -267,11 +265,12 @@
frappe.msgprint(__('Source and target warehouse must be different'));
}
- submitted = true;
frappe.call({
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
args: values,
+ btn: dialog.get_primary_btn(),
freeze: true,
+ freeze_message: __('Creating Stock Entry'),
callback: function (r) {
frappe.show_alert(__('Stock Entry {0} created',
['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>']));
diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json
index f1d7f8c..bb396e8 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.json
+++ b/erpnext/stock/doctype/packed_item/packed_item.json
@@ -13,6 +13,7 @@
"section_break_6",
"warehouse",
"target_warehouse",
+ "conversion_factor",
"column_break_9",
"qty",
"uom",
@@ -209,13 +210,18 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "label": "Conversion Factor"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-09-24 09:25:13.050151",
+ "modified": "2021-05-26 07:08:05.111385",
"modified_by": "Administrator",
"module": "Stock",
"name": "Packed Item",
diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py
index 5341f29..4ab71bd 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.py
+++ b/erpnext/stock/doctype/packed_item/packed_item.py
@@ -53,6 +53,7 @@
pi.parent_detail_docname = main_item_row.name
pi.uom = item.stock_uom
pi.qty = flt(qty)
+ pi.conversion_factor = main_item_row.conversion_factor
if description and not pi.description:
pi.description = description
if not pi.warehouse and not doc.amended_from:
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index e5ef978..5095a80 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -297,6 +297,8 @@
item_code = "Test Extra Item 1", qty=10, basic_rate=100)
se2 = make_stock_entry(target="_Test Warehouse - _TC",
item_code = "_Test FG Item", qty=1, basic_rate=100)
+ se3 = make_stock_entry(target="_Test Warehouse - _TC",
+ item_code = "Test Extra Item 2", qty=1, basic_rate=100)
rm_items = [
{
"item_code": item_code,
@@ -331,6 +333,7 @@
se.cancel()
se1.cancel()
se2.cancel()
+ se3.cancel()
po.reload()
po.cancel()
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index 56b046a..7f3d701 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -1,29 +1,45 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
# See license.txt
-from __future__ import unicode_literals
-import frappe
import unittest
+
+import frappe
from frappe.utils import nowdate
-from erpnext.stock.doctype.item.test_item import create_item
+
+from erpnext.controllers.stock_controller import (
+ QualityInspectionNotSubmittedError,
+ QualityInspectionRejectedError,
+ QualityInspectionRequiredError,
+ make_quality_inspections,
+)
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
-from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError, QualityInspectionNotSubmittedError
# test_records = frappe.get_test_records('Quality Inspection')
+
class TestQualityInspection(unittest.TestCase):
def setUp(self):
create_item("_Test Item with QA")
- frappe.db.set_value("Item", "_Test Item with QA", "inspection_required_before_delivery", 1)
+ frappe.db.set_value(
+ "Item", "_Test Item with QA", "inspection_required_before_delivery", 1
+ )
def test_qa_for_delivery(self):
- make_stock_entry(item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100)
+ make_stock_entry(
+ item_code="_Test Item with QA",
+ target="_Test Warehouse - _TC",
+ qty=1,
+ basic_rate=100
+ )
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
self.assertRaises(QualityInspectionRequiredError, dn.submit)
- qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected")
+ qa = create_quality_inspection(
+ reference_type="Delivery Note", reference_name=dn.name, status="Rejected"
+ )
dn.reload()
self.assertRaises(QualityInspectionRejectedError, dn.submit)
@@ -38,7 +54,9 @@
def test_qa_not_submit(self):
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
- qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True)
+ qa = create_quality_inspection(
+ reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True
+ )
dn.items[0].quality_inspection = qa.name
self.assertRaises(QualityInspectionNotSubmittedError, dn.submit)
@@ -48,21 +66,28 @@
def test_value_based_qi_readings(self):
# Test QI based on acceptance values (Non formula)
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
- readings = [{
- "specification": "Iron Content", # numeric reading
- "min_value": 0.1,
- "max_value": 0.9,
- "reading_1": "0.4"
- },
- {
- "specification": "Particle Inspection Needed", # non-numeric reading
- "numeric": 0,
- "value": "Yes",
- "reading_value": "Yes"
- }]
+ readings = [
+ {
+ "specification": "Iron Content", # numeric reading
+ "min_value": 0.1,
+ "max_value": 0.9,
+ "reading_1": "0.4"
+ },
+ {
+ "specification": "Particle Inspection Needed", # non-numeric reading
+ "numeric": 0,
+ "value": "Yes",
+ "reading_value": "Yes"
+ }
+ ]
- qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
- readings=readings, do_not_save=True)
+ qa = create_quality_inspection(
+ reference_type="Delivery Note",
+ reference_name=dn.name,
+ readings=readings,
+ do_not_save=True
+ )
+
qa.save()
# status must be auto set as per formula
@@ -74,36 +99,43 @@
def test_formula_based_qi_readings(self):
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
- readings = [{
- "specification": "Iron Content", # numeric reading
- "formula_based_criteria": 1,
- "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50",
- "reading_1": "0.4"
- },
- {
- "specification": "Calcium Content", # numeric reading
- "formula_based_criteria": 1,
- "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50",
- "reading_1": "0.7"
- },
- {
- "specification": "Mg Content", # numeric reading
- "formula_based_criteria": 1,
- "acceptance_formula": "mean < 0.9",
- "reading_1": "0.5",
- "reading_2": "0.7",
- "reading_3": "random text" # check if random string input causes issues
- },
- {
- "specification": "Calcium Content", # non-numeric reading
- "formula_based_criteria": 1,
- "numeric": 0,
- "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')",
- "reading_value": "Grade B"
- }]
+ readings = [
+ {
+ "specification": "Iron Content", # numeric reading
+ "formula_based_criteria": 1,
+ "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50",
+ "reading_1": "0.4"
+ },
+ {
+ "specification": "Calcium Content", # numeric reading
+ "formula_based_criteria": 1,
+ "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50",
+ "reading_1": "0.7"
+ },
+ {
+ "specification": "Mg Content", # numeric reading
+ "formula_based_criteria": 1,
+ "acceptance_formula": "mean < 0.9",
+ "reading_1": "0.5",
+ "reading_2": "0.7",
+ "reading_3": "random text" # check if random string input causes issues
+ },
+ {
+ "specification": "Calcium Content", # non-numeric reading
+ "formula_based_criteria": 1,
+ "numeric": 0,
+ "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')",
+ "reading_value": "Grade B"
+ }
+ ]
- qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
- readings=readings, do_not_save=True)
+ qa = create_quality_inspection(
+ reference_type="Delivery Note",
+ reference_name=dn.name,
+ readings=readings,
+ do_not_save=True
+ )
+
qa.save()
# status must be auto set as per formula
@@ -115,6 +147,19 @@
qa.delete()
dn.delete()
+ def test_make_quality_inspections_from_linked_document(self):
+ dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
+ for item in dn.items:
+ item.sample_size = item.qty
+ quality_inspections = make_quality_inspections(dn.doctype, dn.name, dn.items)
+ self.assertEqual(len(dn.items), len(quality_inspections))
+
+ # cleanup
+ for qi in quality_inspections:
+ frappe.delete_doc("Quality Inspection", qi)
+ dn.delete()
+
+
def create_quality_inspection(**args):
args = frappe._dict(args)
qa = frappe.new_doc("Quality Inspection")
@@ -134,7 +179,7 @@
readings = args.readings
if args.status == "Rejected":
- readings["reading_1"] = "12" # status is auto set in child on save
+ readings["reading_1"] = "12" # status is auto set in child on save
if isinstance(readings, list):
for entry in readings:
@@ -150,10 +195,11 @@
return qa
+
def create_quality_inspection_parameter(parameter):
if not frappe.db.exists("Quality Inspection Parameter", parameter):
frappe.get_doc({
"doctype": "Quality Inspection Parameter",
"parameter": parameter,
"description": parameter
- }).insert()
\ No newline at end of file
+ }).insert()
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index daa1e51..93a6fc0 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -115,6 +115,14 @@
return;
}
+ if (!frm.is_new() && frm.doc.docstatus === 0) {
+ frm.add_custom_button(__("Quality Inspection(s)"), () => {
+ let transaction_controller = new erpnext.TransactionController({ frm: frm });
+ transaction_controller.make_quality_inspection();
+ }, __("Create"));
+ frm.page.set_inner_btn_group_as_primary(__('Create'));
+ }
+
let quality_inspection_field = frm.get_docfield("items", "quality_inspection");
quality_inspection_field.get_route_options_for_new_doc = function(row) {
if (frm.is_new()) return;
@@ -155,7 +163,7 @@
refresh: function(frm) {
if(!frm.doc.docstatus) {
frm.trigger('validate_purpose_consumption');
- frm.add_custom_button(__('Create Material Request'), function() {
+ frm.add_custom_button(__('Material Request'), function() {
frappe.model.with_doctype('Material Request', function() {
var mr = frappe.model.get_new_doc('Material Request');
var items = frm.get_field('items').grid.get_selected_children();
@@ -178,7 +186,7 @@
});
frappe.set_route('Form', 'Material Request', mr.name);
});
- });
+ }, __("Create"));
}
if(frm.doc.items) {
@@ -600,7 +608,6 @@
add_to_transit: function(frm) {
if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') {
frm.set_value('to_warehouse', '');
- frm.set_value('stock_entry_type', 'Material Transfer');
frm.fields_dict.to_warehouse.get_query = function() {
return {
filters:{
@@ -610,12 +617,13 @@
}
};
};
- frm.trigger('set_tansit_warehouse');
+ frm.trigger('set_transit_warehouse');
}
},
- set_tansit_warehouse: function(frm) {
- if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse) {
+ set_transit_warehouse: function(frm) {
+ if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse
+ && frm.doc.from_warehouse) {
let dt = frm.doc.from_warehouse ? 'Warehouse' : 'Company';
let dn = frm.doc.from_warehouse ? frm.doc.from_warehouse : frm.doc.company;
frappe.db.get_value(dt, dn, 'default_in_transit_warehouse', (r) => {
@@ -985,7 +993,7 @@
},
from_warehouse: function(doc) {
- this.frm.trigger('set_tansit_warehouse');
+ this.frm.trigger('set_transit_warehouse');
this.set_warehouse_in_children(doc.items, "s_warehouse", doc.from_warehouse);
},
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 98c047a..a0b5457 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -59,10 +59,6 @@
"supplier_name",
"supplier_address",
"address_display",
- "column_break_39",
- "customer",
- "customer_name",
- "customer_address",
"accounting_dimensions_section",
"project",
"dimension_col_break",
@@ -435,13 +431,13 @@
},
{
"collapsible": 1,
- "depends_on": "eval: in_list([\"Sales Return\", \"Purchase Return\", \"Send to Subcontractor\"], doc.purpose)",
+ "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"",
"fieldname": "contact_section",
"fieldtype": "Section Break",
- "label": "Customer or Supplier Details"
+ "label": "Supplier Details"
},
{
- "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"",
+ "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"",
"fieldname": "supplier",
"fieldtype": "Link",
"label": "Supplier",
@@ -453,7 +449,7 @@
},
{
"bold": 1,
- "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"",
+ "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"",
"fieldname": "supplier_name",
"fieldtype": "Data",
"label": "Supplier Name",
@@ -463,7 +459,7 @@
"read_only": 1
},
{
- "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"",
+ "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"",
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Supplier Address",
@@ -478,41 +474,6 @@
"label": "Address"
},
{
- "fieldname": "column_break_39",
- "fieldtype": "Column Break"
- },
- {
- "depends_on": "eval:doc.purpose==\"Sales Return\"",
- "fieldname": "customer",
- "fieldtype": "Link",
- "label": "Customer",
- "no_copy": 1,
- "oldfieldname": "customer",
- "oldfieldtype": "Link",
- "options": "Customer",
- "print_hide": 1
- },
- {
- "bold": 1,
- "depends_on": "eval:doc.purpose==\"Sales Return\"",
- "fieldname": "customer_name",
- "fieldtype": "Data",
- "label": "Customer Name",
- "no_copy": 1,
- "oldfieldname": "customer_name",
- "oldfieldtype": "Data",
- "read_only": 1
- },
- {
- "depends_on": "eval:doc.purpose==\"Sales Return\"",
- "fieldname": "customer_address",
- "fieldtype": "Small Text",
- "label": "Customer Address",
- "no_copy": 1,
- "oldfieldname": "customer_address",
- "oldfieldtype": "Small Text"
- },
- {
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
@@ -637,6 +598,8 @@
{
"default": "0",
"depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry",
+ "fetch_from": "stock_entry_type.add_to_transit",
+ "fetch_if_empty": 1,
"fieldname": "add_to_transit",
"fieldtype": "Check",
"label": "Add to Transit",
@@ -655,7 +618,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-12-09 14:58:13.267321",
+ "modified": "2021-05-24 11:32:23.904307",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json
index 0f2b55e..eee38be 100644
--- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json
+++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json
@@ -6,7 +6,8 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "purpose"
+ "purpose",
+ "add_to_transit"
],
"fields": [
{
@@ -18,10 +19,17 @@
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"reqd": 1,
"set_only_once": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval: doc.purpose == 'Material Transfer'",
+ "fieldname": "add_to_transit",
+ "fieldtype": "Check",
+ "label": "Add to Transit"
}
],
"links": [],
- "modified": "2020-08-10 23:24:37.160817",
+ "modified": "2021-05-21 11:27:01.144110",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Type",
diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
index a4116ab..1069ec8 100644
--- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
+++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
@@ -7,4 +7,6 @@
from frappe.model.document import Document
class StockEntryType(Document):
- pass
+ def validate(self):
+ if self.add_to_transit and self.purpose != 'Material Transfer':
+ self.add_to_transit = 0
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 3296f5b..ba31ad7 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
@@ -15,10 +15,12 @@
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import create_landed_cost_voucher
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import BackDatedStockTransaction
+from frappe.core.page.permission_manager.permission_manager import reset
class TestStockLedgerEntry(unittest.TestCase):
def setUp(self):
items = create_items()
+ reset('Stock Entry')
# delete SLE and BINs for all items
frappe.db.sql("delete from `tabStock Ledger Entry` where item_code in (%s)" % (', '.join(['%s']*len(items))), items)
@@ -314,10 +316,11 @@
# Set User with Stock User role but not Stock Manager
try:
user = frappe.get_doc("User", "test@example.com")
- frappe.set_user(user.name)
user.add_roles("Stock User")
user.remove_roles("Stock Manager")
+ frappe.set_user(user.name)
+
stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
posting_date=add_days(today(), -1), do_not_submit=True)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index b9f9190..306df99 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -477,19 +477,19 @@
def get_items(warehouse, posting_date, posting_time, company):
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
items = frappe.db.sql("""
- select i.name, i.item_name, bin.warehouse
+ select i.name, i.item_name, bin.warehouse, i.has_serial_no
from tabBin bin, tabItem i
where i.name=bin.item_code and i.disabled=0 and i.is_stock_item = 1
- and i.has_variants = 0 and i.has_serial_no = 0 and i.has_batch_no = 0
+ and i.has_variants = 0 and i.has_batch_no = 0
and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse)
""", (lft, rgt))
items += frappe.db.sql("""
- select i.name, i.item_name, id.default_warehouse
+ select i.name, i.item_name, id.default_warehouse, i.has_serial_no
from tabItem i, `tabItem Default` id
where i.name = id.parent
and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse)
- and i.is_stock_item = 1 and i.has_serial_no = 0 and i.has_batch_no = 0
+ and i.is_stock_item = 1 and i.has_batch_no = 0
and i.has_variants = 0 and i.disabled = 0 and id.company=%s
group by i.name
""", (lft, rgt, company))
@@ -497,7 +497,7 @@
res = []
for d in set(items):
stock_bal = get_stock_balance(d[0], d[2], posting_date, posting_time,
- with_valuation_rate=True)
+ with_valuation_rate=True , with_serial_no=cint(d[3]))
if frappe.db.get_value("Item", d[0], "disabled") == 0:
res.append({
@@ -507,7 +507,9 @@
"item_name": d[1],
"valuation_rate": stock_bal[1],
"current_qty": stock_bal[0],
- "current_valuation_rate": stock_bal[1]
+ "current_valuation_rate": stock_bal[1],
+ "current_serial_no": stock_bal[2] if cint(d[3]) else '',
+ "serial_no": stock_bal[2] if cint(d[3]) else ''
})
return res
diff --git a/erpnext/stock/report/item_variant_details/item_variant_details.py b/erpnext/stock/report/item_variant_details/item_variant_details.py
index e8449cc..d8563d7 100644
--- a/erpnext/stock/report/item_variant_details/item_variant_details.py
+++ b/erpnext/stock/report/item_variant_details/item_variant_details.py
@@ -14,47 +14,58 @@
if not item:
return []
item_dicts = []
- variants = None
- variant_results = frappe.db.sql("""select name from `tabItem`
- where variant_of = %s""", item, as_dict=1)
+ variant_results = frappe.db.get_all(
+ "Item",
+ fields=["name"],
+ filters={
+ "variant_of": ["=", item],
+ "disabled": 0
+ }
+ )
+
if not variant_results:
- frappe.msgprint(_("There isn't any item variant for the selected item"))
+ frappe.msgprint(_("There aren't any item variants for the selected item"))
return []
else:
- variants = ", ".join([frappe.db.escape(variant['name']) for variant in variant_results])
+ variant_list = [variant['name'] for variant in variant_results]
- order_count_map = get_open_sales_orders_map(variants)
- stock_details_map = get_stock_details_map(variants)
- buying_price_map = get_buying_price_map(variants)
- selling_price_map = get_selling_price_map(variants)
- attr_val_map = get_attribute_values_map(variants)
+ order_count_map = get_open_sales_orders_count(variant_list)
+ stock_details_map = get_stock_details_map(variant_list)
+ buying_price_map = get_buying_price_map(variant_list)
+ selling_price_map = get_selling_price_map(variant_list)
+ attr_val_map = get_attribute_values_map(variant_list)
- attribute_list = [d[0] for d in frappe.db.sql("""select attribute
- from `tabItem Variant Attribute`
- where parent in ({variants}) group by attribute""".format(variants=variants))]
+ attributes = frappe.db.get_all(
+ "Item Variant Attribute",
+ fields=["attribute"],
+ filters={
+ "parent": ["in", variant_list]
+ },
+ group_by="attribute"
+ )
+ attribute_list = [row.get("attribute") for row in attributes]
# Prepare dicts
variant_dicts = [{"variant_name": d['name']} for d in variant_results]
for item_dict in variant_dicts:
- name = item_dict["variant_name"]
+ name = item_dict.get("variant_name")
- for d in attribute_list:
- attr_dict = attr_val_map[name]
- if attr_dict and attr_dict.get(d):
- item_dict[d] = attr_val_map[name][d]
+ for attribute in attribute_list:
+ attr_dict = attr_val_map.get(name)
+ if attr_dict and attr_dict.get(attribute):
+ item_dict[frappe.scrub(attribute)] = attr_val_map.get(name).get(attribute)
- item_dict["Open Orders"] = order_count_map.get(name) or 0
+ item_dict["open_orders"] = order_count_map.get(name) or 0
if stock_details_map.get(name):
- item_dict["Inventory"] = stock_details_map.get(name)["Inventory"] or 0
- item_dict["In Production"] = stock_details_map.get(name)["In Production"] or 0
- item_dict["Available Selling"] = stock_details_map.get(name)["Available Selling"] or 0
+ item_dict["current_stock"] = stock_details_map.get(name)["Inventory"] or 0
+ item_dict["in_production"] = stock_details_map.get(name)["In Production"] or 0
else:
- item_dict["Inventory"] = item_dict["In Production"] = item_dict["Available Selling"] = 0
+ item_dict["current_stock"] = item_dict["in_production"] = 0
- item_dict["Avg. Buying Price List Rate"] = buying_price_map.get(name) or 0
- item_dict["Avg. Selling Price List Rate"] = selling_price_map.get(name) or 0
+ item_dict["avg_buying_price_list_rate"] = buying_price_map.get(name) or 0
+ item_dict["avg_selling_price_list_rate"] = selling_price_map.get(name) or 0
item_dicts.append(item_dict)
@@ -71,117 +82,158 @@
item_doc = frappe.get_doc("Item", item)
- for d in item_doc.attributes:
- columns.append(d.attribute + ":Data:100")
+ for entry in item_doc.attributes:
+ columns.append({
+ "fieldname": frappe.scrub(entry.attribute),
+ "label": entry.attribute,
+ "fieldtype": "Data",
+ "width": 100
+ })
- columns += [_("Avg. Buying Price List Rate") + ":Currency:110", _("Avg. Selling Price List Rate") + ":Currency:110",
- _("Inventory") + ":Float:100", _("In Production") + ":Float:100",
- _("Open Orders") + ":Float:100", _("Available Selling") + ":Float:100"
+ additional_columns = [
+ {
+ "fieldname": "avg_buying_price_list_rate",
+ "label": _("Avg. Buying Price List Rate"),
+ "fieldtype": "Currency",
+ "width": 150
+ },
+ {
+ "fieldname": "avg_selling_price_list_rate",
+ "label": _("Avg. Selling Price List Rate"),
+ "fieldtype": "Currency",
+ "width": 150
+ },
+ {
+ "fieldname": "current_stock",
+ "label": _("Current Stock"),
+ "fieldtype": "Float",
+ "width": 120
+ },
+ {
+ "fieldname": "in_production",
+ "label": _("In Production"),
+ "fieldtype": "Float",
+ "width": 150
+ },
+ {
+ "fieldname": "open_orders",
+ "label": _("Open Sales Orders"),
+ "fieldtype": "Float",
+ "width": 150
+ }
]
+ columns.extend(additional_columns)
return columns
-def get_open_sales_orders_map(variants):
- open_sales_orders = frappe.db.sql("""
- select
- count(*) as count,
- item_code
- from
- `tabSales Order Item`
- where
- docstatus = 1 and
- qty > ifnull(delivered_qty, 0) and
- item_code in ({variants})
- group by
- item_code
- """.format(variants=variants), as_dict=1)
+def get_open_sales_orders_count(variants_list):
+ open_sales_orders = frappe.db.get_list(
+ "Sales Order",
+ fields=[
+ "name",
+ "`tabSales Order Item`.item_code"
+ ],
+ filters=[
+ ["Sales Order", "docstatus", "=", 1],
+ ["Sales Order Item", "item_code", "in", variants_list]
+ ],
+ distinct=1
+ )
order_count_map = {}
- for d in open_sales_orders:
- order_count_map[d["item_code"]] = d["count"]
+ for row in open_sales_orders:
+ item_code = row.get("item_code")
+ if order_count_map.get(item_code) is None:
+ order_count_map[item_code] = 1
+ else:
+ order_count_map[item_code] += 1
return order_count_map
-def get_stock_details_map(variants):
- stock_details = frappe.db.sql("""
- select
- sum(planned_qty) as planned_qty,
- sum(actual_qty) as actual_qty,
- sum(projected_qty) as projected_qty,
- item_code
- from
- `tabBin`
- where
- item_code in ({variants})
- group by
- item_code
- """.format(variants=variants), as_dict=1)
+def get_stock_details_map(variant_list):
+ stock_details = frappe.db.get_all(
+ "Bin",
+ fields=[
+ "sum(planned_qty) as planned_qty",
+ "sum(actual_qty) as actual_qty",
+ "sum(projected_qty) as projected_qty",
+ "item_code",
+ ],
+ filters={
+ "item_code": ["in", variant_list]
+ },
+ group_by="item_code"
+ )
stock_details_map = {}
- for d in stock_details:
- name = d["item_code"]
+ for row in stock_details:
+ name = row.get("item_code")
stock_details_map[name] = {
- "Inventory" :d["actual_qty"],
- "In Production" :d["planned_qty"],
- "Available Selling" :d["projected_qty"]
+ "Inventory": row.get("actual_qty"),
+ "In Production": row.get("planned_qty")
}
return stock_details_map
-def get_buying_price_map(variants):
- buying = frappe.db.sql("""
- select
- avg(price_list_rate) as avg_rate,
- item_code
- from
- `tabItem Price`
- where
- item_code in ({variants}) and buying=1
- group by
- item_code
- """.format(variants=variants), as_dict=1)
+def get_buying_price_map(variant_list):
+ buying = frappe.db.get_all(
+ "Item Price",
+ fields=[
+ "avg(price_list_rate) as avg_rate",
+ "item_code",
+ ],
+ filters={
+ "item_code": ["in", variant_list],
+ "buying": 1
+ },
+ group_by="item_code"
+ )
buying_price_map = {}
- for d in buying:
- buying_price_map[d["item_code"]] = d["avg_rate"]
+ for row in buying:
+ buying_price_map[row.get("item_code")] = row.get("avg_rate")
return buying_price_map
-def get_selling_price_map(variants):
- selling = frappe.db.sql("""
- select
- avg(price_list_rate) as avg_rate,
- item_code
- from
- `tabItem Price`
- where
- item_code in ({variants}) and selling=1
- group by
- item_code
- """.format(variants=variants), as_dict=1)
+def get_selling_price_map(variant_list):
+ selling = frappe.db.get_all(
+ "Item Price",
+ fields=[
+ "avg(price_list_rate) as avg_rate",
+ "item_code",
+ ],
+ filters={
+ "item_code": ["in", variant_list],
+ "selling": 1
+ },
+ group_by="item_code"
+ )
selling_price_map = {}
- for d in selling:
- selling_price_map[d["item_code"]] = d["avg_rate"]
+ for row in selling:
+ selling_price_map[row.get("item_code")] = row.get("avg_rate")
return selling_price_map
-def get_attribute_values_map(variants):
- list_attr = frappe.db.sql("""
- select
- attribute, attribute_value, parent
- from
- `tabItem Variant Attribute`
- where
- parent in ({variants})
- """.format(variants=variants), as_dict=1)
+def get_attribute_values_map(variant_list):
+ attribute_list = frappe.db.get_all(
+ "Item Variant Attribute",
+ fields=[
+ "attribute",
+ "attribute_value",
+ "parent"
+ ],
+ filters={
+ "parent": ["in", variant_list]
+ }
+ )
attr_val_map = {}
- for d in list_attr:
- name = d["parent"]
+ for row in attribute_list:
+ name = row.get("parent")
if not attr_val_map.get(name):
attr_val_map[name] = {}
- attr_val_map[name][d["attribute"]] = d["attribute_value"]
+ attr_val_map[name][row.get("attribute")] = row.get("attribute_value")
return attr_val_map
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 8ba1f1c..8917bfe 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -194,9 +194,6 @@
serial_nos = frappe.db.sql("""select count(name) from `tabSerial No`
where item_code=%s and warehouse=%s and docstatus < 2""", (d[0], d[1]))
- if serial_nos and flt(serial_nos[0][0]) != flt(d[2]):
- print(d[0], d[1], d[2], serial_nos[0][0])
-
sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry`
where item_code = %s and warehouse = %s and is_cancelled = 0
order by posting_date desc limit 1""", (d[0], d[1]))
@@ -230,7 +227,7 @@
})
update_bin(args)
-
+
create_repost_item_valuation_entry({
"item_code": d[0],
"warehouse": d[1],
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 9729987..b2825fc 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -232,7 +232,8 @@
and is_cancelled = 0
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
order by timestamp(posting_date, posting_time) desc, creation desc
- limit 1""", args, as_dict=1)
+ limit 1
+ for update""", args, as_dict=1)
return sle[0] if sle else frappe._dict()
@@ -623,7 +624,7 @@
break
# If no entry found with outgoing rate, collapse stack
- if index == None:
+ if index is None: # nosemgrep
new_stock_value = sum((d[0]*d[1] for d in self.wh_data.stock_queue)) - qty_to_pop*outgoing_rate
new_stock_qty = sum((d[0] for d in self.wh_data.stock_queue)) - qty_to_pop
self.wh_data.stock_queue = [[new_stock_qty, new_stock_value/new_stock_qty if new_stock_qty > 0 else outgoing_rate]]
diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json
index a43381c..bc29821 100644
--- a/erpnext/support/doctype/issue/issue.json
+++ b/erpnext/support/doctype/issue/issue.json
@@ -119,7 +119,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "Open\nReplied\nHold\nResolved\nClosed",
+ "options": "Open\nReplied\nOn Hold\nResolved\nClosed",
"search_index": 1
},
{
@@ -410,7 +410,7 @@
"icon": "fa fa-ticket",
"idx": 7,
"links": [],
- "modified": "2020-08-11 18:49:07.574769",
+ "modified": "2021-05-26 10:49:07.574769",
"modified_by": "Administrator",
"module": "Support",
"name": "Issue",
diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js
index eb0e06c..a5122d0 100644
--- a/erpnext/support/report/issue_summary/issue_summary.js
+++ b/erpnext/support/report/issue_summary/issue_summary.js
@@ -42,6 +42,7 @@
"",
{label: __('Open'), value: 'Open'},
{label: __('Replied'), value: 'Replied'},
+ {label: __('On Hold'), value: 'On Hold'},
{label: __('Resolved'), value: 'Resolved'},
{label: __('Closed'), value: 'Closed'}
]
diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py
index 7861e30..bba25b8 100644
--- a/erpnext/support/report/issue_summary/issue_summary.py
+++ b/erpnext/support/report/issue_summary/issue_summary.py
@@ -62,7 +62,7 @@
'width': 200
})
- self.statuses = ['Open', 'Replied', 'Resolved', 'Closed']
+ self.statuses = ['Open', 'Replied', 'On Hold', 'Resolved', 'Closed']
for status in self.statuses:
self.columns.append({
'label': _(status),
@@ -265,6 +265,7 @@
labels = []
open_issues = []
replied_issues = []
+ on_hold_issues = []
resolved_issues = []
closed_issues = []
@@ -277,6 +278,7 @@
labels.append(entry.get(entity_field))
open_issues.append(entry.get('open'))
replied_issues.append(entry.get('replied'))
+ on_hold_issues.append(entry.get('on_hold'))
resolved_issues.append(entry.get('resolved'))
closed_issues.append(entry.get('closed'))
@@ -293,6 +295,10 @@
'values': replied_issues[:30]
},
{
+ 'name': 'On Hold',
+ 'values': on_hold_issues[:30]
+ },
+ {
'name': 'Resolved',
'values': resolved_issues[:30]
},
@@ -313,12 +319,14 @@
open_issues = 0
replied = 0
+ on_hold = 0
resolved = 0
closed = 0
for entry in self.data:
open_issues += entry.get('open')
replied += entry.get('replied')
+ on_hold += entry.get('on_hold')
resolved += entry.get('resolved')
closed += entry.get('closed')
@@ -336,6 +344,12 @@
'datatype': 'Int',
},
{
+ 'value': on_hold,
+ 'indicator': 'Grey',
+ 'label': _('On Hold'),
+ 'datatype': 'Int',
+ },
+ {
'value': resolved,
'indicator': 'Green',
'label': _('Resolved'),
diff --git a/erpnext/tests/__init__.py b/erpnext/tests/__init__.py
index e69de29..a504340 100644
--- a/erpnext/tests/__init__.py
+++ b/erpnext/tests/__init__.py
@@ -0,0 +1 @@
+global_test_dependencies = ['User', 'Company', 'Item']
diff --git a/erpnext/utilities/__init__.py b/erpnext/utilities/__init__.py
index 618cc98..0a5aa3c 100644
--- a/erpnext/utilities/__init__.py
+++ b/erpnext/utilities/__init__.py
@@ -12,7 +12,6 @@
for f in dt.fields:
if f.fieldname == d.fieldname and f.fieldtype in ("Text", "Small Text"):
- print(f.parent, f.fieldname)
f.fieldtype = "Text Editor"
dt.save()
break
diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js
index 377a3cc..5562cbd 100644
--- a/erpnext/www/book_appointment/index.js
+++ b/erpnext/www/book_appointment/index.js
@@ -48,7 +48,7 @@
function hide_next_button() {
let next_button = document.getElementById('next-button');
next_button.disabled = true;
- next_button.onclick = () => frappe.msgprint("Please select a date and time");
+ next_button.onclick = () => frappe.msgprint(__("Please select a date and time"));
}
function show_next_button() {
@@ -63,7 +63,7 @@
if (date_picker.value === '') {
clear_time_slots();
hide_next_button();
- frappe.throw('Please select a date');
+ frappe.throw(__('Please select a date'));
}
window.selected_date = date_picker.value;
window.selected_timezone = timezone.value;
diff --git a/requirements.txt b/requirements.txt
index f1ffeb8..32da48e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,4 +10,3 @@
taxjar~=1.9.2
tweepy~=3.10.0
Unidecode~=1.2.0
-WooCommerce~=3.0.0