Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into tds_advance_payment_v13
This commit is contained in:
commit
6f2dacc60c
12
.git-blame-ignore-revs
Normal file
12
.git-blame-ignore-revs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Since version 2.23 (released in August 2019), git-blame has a feature
|
||||||
|
# to ignore or bypass certain commits.
|
||||||
|
#
|
||||||
|
# This file contains a list of commits that are not likely what you
|
||||||
|
# are looking for in a blame, such as mass reformatting or renaming.
|
||||||
|
# You can set this file as a default ignore file for blame by running
|
||||||
|
# the following command.
|
||||||
|
#
|
||||||
|
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||||
|
|
||||||
|
# This commit just changes spaces to tabs for indentation in some files
|
||||||
|
5f473611bd6ed57703716244a054d3fb5ba9cd23
|
8
.github/helper/semgrep_rules/translate.py
vendored
8
.github/helper/semgrep_rules/translate.py
vendored
@ -51,3 +51,11 @@ _(f"what" + f"this is also not cool")
|
|||||||
_("")
|
_("")
|
||||||
# ruleid: frappe-translation-empty-string
|
# ruleid: frappe-translation-empty-string
|
||||||
_('')
|
_('')
|
||||||
|
|
||||||
|
|
||||||
|
class Test:
|
||||||
|
# ok: frappe-translation-python-splitting
|
||||||
|
def __init__(
|
||||||
|
args
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
4
.github/helper/semgrep_rules/translate.yml
vendored
4
.github/helper/semgrep_rules/translate.yml
vendored
@ -44,8 +44,8 @@ rules:
|
|||||||
pattern-either:
|
pattern-either:
|
||||||
- pattern: _(...) + _(...)
|
- pattern: _(...) + _(...)
|
||||||
- pattern: _("..." + "...")
|
- pattern: _("..." + "...")
|
||||||
- pattern-regex: '_\([^\)]*\\\s*' # lines broken by `\`
|
- pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\`
|
||||||
- pattern-regex: '_\(\s*\n' # line breaks allowed by python for using ( )
|
- pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( )
|
||||||
message: |
|
message: |
|
||||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||||
|
9
.github/helper/semgrep_rules/ux.js
vendored
Normal file
9
.github/helper/semgrep_rules/ux.js
vendored
Normal file
@ -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") }}. ');
|
18
.github/helper/semgrep_rules/ux.py
vendored
18
.github/helper/semgrep_rules/ux.py
vendored
@ -2,30 +2,30 @@ import frappe
|
|||||||
from frappe import msgprint, throw, _
|
from frappe import msgprint, throw, _
|
||||||
|
|
||||||
|
|
||||||
# ruleid: frappe-missing-translate-function
|
# ruleid: frappe-missing-translate-function-python
|
||||||
throw("Error Occured")
|
throw("Error Occured")
|
||||||
|
|
||||||
# ruleid: frappe-missing-translate-function
|
# ruleid: frappe-missing-translate-function-python
|
||||||
frappe.throw("Error Occured")
|
frappe.throw("Error Occured")
|
||||||
|
|
||||||
# ruleid: frappe-missing-translate-function
|
# ruleid: frappe-missing-translate-function-python
|
||||||
frappe.msgprint("Useful message")
|
frappe.msgprint("Useful message")
|
||||||
|
|
||||||
# ruleid: frappe-missing-translate-function
|
# ruleid: frappe-missing-translate-function-python
|
||||||
msgprint("Useful message")
|
msgprint("Useful message")
|
||||||
|
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
translatedmessage = _("Hello")
|
translatedmessage = _("Hello")
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
throw(translatedmessage)
|
throw(translatedmessage)
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
msgprint(translatedmessage)
|
msgprint(translatedmessage)
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
msgprint(_("Helpful message"))
|
msgprint(_("Helpful message"))
|
||||||
|
|
||||||
# ok: frappe-missing-translate-function
|
# ok: frappe-missing-translate-function-python
|
||||||
frappe.throw(_("Error occured"))
|
frappe.throw(_("Error occured"))
|
||||||
|
23
.github/helper/semgrep_rules/ux.yml
vendored
23
.github/helper/semgrep_rules/ux.yml
vendored
@ -1,15 +1,30 @@
|
|||||||
rules:
|
rules:
|
||||||
- id: frappe-missing-translate-function
|
- id: frappe-missing-translate-function-python
|
||||||
pattern-either:
|
pattern-either:
|
||||||
- patterns:
|
- patterns:
|
||||||
- pattern: frappe.msgprint("...", ...)
|
- pattern: frappe.msgprint("...", ...)
|
||||||
- pattern-not: frappe.msgprint(_("..."), ...)
|
- pattern-not: frappe.msgprint(_("..."), ...)
|
||||||
- pattern-not: frappe.msgprint(__("..."), ...)
|
|
||||||
- patterns:
|
- patterns:
|
||||||
- pattern: frappe.throw("...", ...)
|
- pattern: frappe.throw("...", ...)
|
||||||
- pattern-not: frappe.throw(_("..."), ...)
|
- pattern-not: frappe.throw(_("..."), ...)
|
||||||
- pattern-not: frappe.throw(__("..."), ...)
|
|
||||||
message: |
|
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
|
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
|
severity: ERROR
|
||||||
|
69
.github/workflows/patch.yml
vendored
Normal file
69
.github/workflows/patch.yml
vendored
Normal file
@ -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
|
@ -1,6 +1,6 @@
|
|||||||
name: CI
|
name: Server
|
||||||
|
|
||||||
on: [pull_request, workflow_dispatch, push]
|
on: [pull_request, workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@ -10,15 +10,9 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
container: [1, 2, 3]
|
||||||
- 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 }}
|
name: Python Unit Tests
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
@ -36,7 +30,7 @@ jobs:
|
|||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.6
|
python-version: 3.7
|
||||||
|
|
||||||
- name: Add to Hosts
|
- name: Add to Hosts
|
||||||
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||||
@ -49,6 +43,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pip-
|
${{ runner.os }}-pip-
|
||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
env:
|
env:
|
||||||
@ -60,6 +55,7 @@ jobs:
|
|||||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
${{ runner.os }}-build-
|
${{ runner.os }}-build-
|
||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
@ -76,33 +72,39 @@ jobs:
|
|||||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: ${{ matrix.RUN_COMMAND }}
|
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
|
||||||
env:
|
env:
|
||||||
TYPE: ${{ matrix.TYPE }}
|
TYPE: server
|
||||||
|
CI_BUILD_ID: ${{ github.run_id }}
|
||||||
|
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
|
||||||
|
|
||||||
- name: Coverage - Pull Request
|
- name: Upload Coverage Data
|
||||||
if: matrix.TYPE == 'server' && github.event_name == 'pull_request'
|
|
||||||
run: |
|
run: |
|
||||||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||||
cd ${GITHUB_WORKSPACE}
|
cd ${GITHUB_WORKSPACE}
|
||||||
pip install coveralls==2.2.0
|
pip3 install coverage==5.5
|
||||||
pip install coverage==4.5.4
|
pip3 install coveralls==3.0.1
|
||||||
coveralls --service=github
|
coveralls
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
COVERALLS_FLAG_NAME: run-${{ matrix.container }}
|
||||||
COVERALLS_SERVICE_NAME: github
|
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
|
||||||
|
COVERALLS_PARALLEL: true
|
||||||
- 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
|
|
||||||
|
|
||||||
|
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 }}
|
@ -27,7 +27,7 @@ class AccountingDimension(Document):
|
|||||||
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
|
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
|
||||||
|
|
||||||
if exists and self.is_new():
|
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():
|
if not self.is_new():
|
||||||
self.validate_document_type_change()
|
self.validate_document_type_change()
|
||||||
|
@ -7,7 +7,8 @@ import frappe
|
|||||||
import unittest
|
import unittest
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
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.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):
|
class TestAccountingDimension(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -9,6 +9,8 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
|
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
|
||||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||||
|
|
||||||
|
test_dependencies = ['Location', 'Cost Center', 'Department']
|
||||||
|
|
||||||
class TestAccountingDimensionFilter(unittest.TestCase):
|
class TestAccountingDimensionFilter(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
create_dimension()
|
create_dimension()
|
||||||
|
@ -10,6 +10,8 @@ from erpnext.accounts.general_ledger import ClosedAccountingPeriod
|
|||||||
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
|
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
|
test_dependencies = ['Item']
|
||||||
|
|
||||||
class TestAccountingPeriod(unittest.TestCase):
|
class TestAccountingPeriod(unittest.TestCase):
|
||||||
def test_overlap(self):
|
def test_overlap(self):
|
||||||
ap1 = create_accounting_period(start_date = "2018-04-01",
|
ap1 = create_accounting_period(start_date = "2018-04-01",
|
||||||
@ -38,7 +40,7 @@ def create_accounting_period(**args):
|
|||||||
accounting_period.start_date = args.start_date or nowdate()
|
accounting_period.start_date = args.start_date or nowdate()
|
||||||
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
||||||
accounting_period.company = args.company or "_Test Company"
|
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", {
|
accounting_period.append("closed_documents", {
|
||||||
"document_type": 'Sales Invoice', "closed": 1
|
"document_type": 'Sales Invoice', "closed": 1
|
||||||
})
|
})
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
@ -24,7 +25,7 @@ class AccountsSettings(Document):
|
|||||||
def validate_stale_days(self):
|
def validate_stale_days(self):
|
||||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||||
frappe.msgprint(
|
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)
|
raise_exception=1)
|
||||||
|
|
||||||
def enable_payment_schedule_in_print(self):
|
def enable_payment_schedule_in_print(self):
|
||||||
|
@ -120,4 +120,4 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
|||||||
plaid_success(token, response) {
|
plaid_success(token, response) {
|
||||||
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
|
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -11,6 +11,8 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_pur
|
|||||||
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
|
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
|
|
||||||
|
test_dependencies = ['Monthly Distribution']
|
||||||
|
|
||||||
class TestBudget(unittest.TestCase):
|
class TestBudget(unittest.TestCase):
|
||||||
def test_monthly_budget_crossed_ignore(self):
|
def test_monthly_budget_crossed_ignore(self):
|
||||||
set_total_expense_zero(nowdate(), "cost_center")
|
set_total_expense_zero(nowdate(), "cost_center")
|
||||||
|
@ -22,7 +22,7 @@ def validate_company(company):
|
|||||||
'allow_account_creation_against_child_company'])
|
'allow_account_creation_against_child_company'])
|
||||||
|
|
||||||
if parent_company and (not 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(
|
msg += _("Please import accounts against parent company or enable {} in company master.").format(
|
||||||
frappe.bold('Allow Account Creation Against Child Company'))
|
frappe.bold('Allow Account Creation Against Child Company'))
|
||||||
frappe.throw(msg, title=_('Wrong Company'))
|
frappe.throw(msg, title=_('Wrong Company'))
|
||||||
@ -56,7 +56,7 @@ def get_file(file_name):
|
|||||||
extension = extension.lstrip(".")
|
extension = extension.lstrip(".")
|
||||||
|
|
||||||
if extension not in ('csv', 'xlsx', 'xls'):
|
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
|
return file_doc, extension
|
||||||
|
|
||||||
@ -293,7 +293,7 @@ def validate_accounts(file_name):
|
|||||||
accounts_dict = {}
|
accounts_dict = {}
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
accounts_dict.setdefault(account["account_name"], account)
|
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 = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _("Alternatively, you can download the template and fill your data in.")
|
msg += _("Alternatively, you can download the template and fill your data in.")
|
||||||
|
@ -29,7 +29,7 @@ class TestDunning(unittest.TestCase):
|
|||||||
self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
|
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('dunning_amount'), 2), 20.44)
|
||||||
self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
|
self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
|
||||||
|
|
||||||
def test_gl_entries(self):
|
def test_gl_entries(self):
|
||||||
dunning = create_dunning()
|
dunning = create_dunning()
|
||||||
dunning.submit()
|
dunning.submit()
|
||||||
|
@ -39,7 +39,11 @@ class JournalEntry(AccountsController):
|
|||||||
self.validate_multi_currency()
|
self.validate_multi_currency()
|
||||||
self.set_amounts_in_company_currency()
|
self.set_amounts_in_company_currency()
|
||||||
self.validate_debit_credit_amount()
|
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_against_jv()
|
||||||
self.validate_reference_doc()
|
self.validate_reference_doc()
|
||||||
self.set_against_account()
|
self.set_against_account()
|
||||||
|
17
erpnext/accounts/doctype/journal_entry/regional/india.js
Normal file
17
erpnext/accounts/doctype/journal_entry/regional/india.js
Normal file
@ -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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -107,7 +107,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
|||||||
frm.set_value("taxes", []);
|
frm.set_value("taxes", []);
|
||||||
|
|
||||||
for (let row of frm.doc.payment_reconciliation) {
|
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) {
|
for (let row of frm.doc.pos_transactions) {
|
||||||
@ -154,6 +154,9 @@ function add_to_pos_transaction(d, frm) {
|
|||||||
function refresh_payments(d, frm) {
|
function refresh_payments(d, frm) {
|
||||||
d.payments.forEach(p => {
|
d.payments.forEach(p => {
|
||||||
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
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) {
|
if (payment) {
|
||||||
payment.expected_amount += flt(p.amount);
|
payment.expected_amount += flt(p.amount);
|
||||||
payment.difference = payment.closing_amount - payment.expected_amount;
|
payment.difference = payment.closing_amount - payment.expected_amount;
|
||||||
|
@ -140,6 +140,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
return
|
return
|
||||||
|
|
||||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
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)
|
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||||
if flt(available_stock) <= 0:
|
if flt(available_stock) <= 0:
|
||||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
|
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
|
||||||
@ -213,8 +214,9 @@ class POSInvoice(SalesInvoice):
|
|||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
||||||
if not 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. ")
|
if not frappe.db.exists('Product Bundle', d.item_code):
|
||||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid 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"))
|
||||||
|
|
||||||
def validate_mode_of_payment(self):
|
def validate_mode_of_payment(self):
|
||||||
if len(self.payments) == 0:
|
if len(self.payments) == 0:
|
||||||
@ -455,15 +457,36 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_stock_availability(item_code, warehouse):
|
def get_stock_availability(item_code, warehouse):
|
||||||
|
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`
|
bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
|
||||||
where item_code = %s and warehouse = %s
|
where item_code = %s and warehouse = %s
|
||||||
limit 1""", (item_code, warehouse), as_dict=1)
|
limit 1""", (item_code, warehouse), as_dict=1)
|
||||||
|
|
||||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
return bin_qty[0].actual_qty or 0 if bin_qty else 0
|
||||||
|
|
||||||
bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0
|
|
||||||
|
|
||||||
return bin_qty - pos_sales_qty
|
|
||||||
|
|
||||||
def get_pos_reserved_qty(item_code, warehouse):
|
def get_pos_reserved_qty(item_code, warehouse):
|
||||||
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||||
@ -522,4 +545,4 @@ def add_return_modes(doc, pos_profile):
|
|||||||
mode_of_payment = pos_payment_method.mode_of_payment
|
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]:
|
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)
|
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
||||||
append_payment(payment_mode[0])
|
append_payment(payment_mode[0])
|
||||||
|
@ -152,7 +152,7 @@ class PricingRule(Document):
|
|||||||
frappe.throw(_("Valid from date must be less than valid upto date"))
|
frappe.throw(_("Valid from date must be less than valid upto date"))
|
||||||
|
|
||||||
def validate_condition(self):
|
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"))
|
frappe.throw(_("Invalid condition expression"))
|
||||||
|
|
||||||
#--------------------------------------------------------------------------------
|
#--------------------------------------------------------------------------------
|
||||||
|
@ -19,7 +19,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
|
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
frappe.msgprint('No Records for these settings.')
|
frappe.msgprint(__('No Records for these settings.'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -33,7 +33,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(result) {
|
success: function(result) {
|
||||||
if(jQuery.isEmptyObject(result)){
|
if(jQuery.isEmptyObject(result)){
|
||||||
frappe.msgprint('No Records for these settings.');
|
frappe.msgprint(__('No Records for these settings.'));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
window.location = url;
|
window.location = url;
|
||||||
@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
frm.refresh_field('customers');
|
frm.refresh_field('customers');
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
frappe.throw('No Customers found with selected options.');
|
frappe.throw(__('No Customers found with selected options.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,4 +129,4 @@ frappe.ui.form.on('Process Statement Of Accounts Customer', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -94,7 +94,7 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
html = frappe.render_template(template_path, \
|
html = frappe.render_template(template_path, \
|
||||||
{"filters": filters, "data": res, "ageing": ageing[0] if doc.include_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,
|
"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')
|
"terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms')
|
||||||
if doc.terms_and_conditions else None})
|
if doc.terms_and_conditions else None})
|
||||||
|
@ -637,8 +637,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
def test_rejected_serial_no(self):
|
def test_rejected_serial_no(self):
|
||||||
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
|
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_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC",
|
||||||
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"),
|
self.assertEqual(frappe.db.get_value("Serial No", pi.get("items")[0].serial_no, "warehouse"),
|
||||||
pi.get("items")[0].warehouse)
|
pi.get("items")[0].warehouse)
|
||||||
@ -1090,7 +1090,8 @@ def make_purchase_invoice(**args):
|
|||||||
"project": args.project,
|
"project": args.project,
|
||||||
"rejected_warehouse": args.rejected_warehouse or "",
|
"rejected_warehouse": args.rejected_warehouse or "",
|
||||||
"rejected_serial_no": args.rejected_serial_no 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:
|
if args.get_taxes_and_charges:
|
||||||
|
@ -17,7 +17,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
var me = this;
|
var me = this;
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet'];
|
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry'];
|
||||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||||
// show debit_to in print format
|
// show debit_to in print format
|
||||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||||
|
@ -1139,7 +1139,6 @@ class SalesInvoice(SellingController):
|
|||||||
"""
|
"""
|
||||||
self.set_serial_no_against_delivery_note()
|
self.set_serial_no_against_delivery_note()
|
||||||
self.validate_serial_against_delivery_note()
|
self.validate_serial_against_delivery_note()
|
||||||
self.validate_serial_against_sales_invoice()
|
|
||||||
|
|
||||||
def set_serial_no_against_delivery_note(self):
|
def set_serial_no_against_delivery_note(self):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
@ -1170,26 +1169,6 @@ class SalesInvoice(SellingController):
|
|||||||
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
|
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)))
|
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):
|
def update_project(self):
|
||||||
if self.project:
|
if self.project:
|
||||||
project = frappe.get_doc("Project", self.project)
|
project = frappe.get_doc("Project", self.project)
|
||||||
|
@ -933,12 +933,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
|
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
|
||||||
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0],
|
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0],
|
||||||
"delivery_document_no"), si.name)
|
"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
|
return si
|
||||||
|
|
||||||
|
@ -185,10 +185,10 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
|||||||
for d in gl_map:
|
for d in gl_map:
|
||||||
if d.account == round_off_account:
|
if d.account == round_off_account:
|
||||||
round_off_gle = d
|
round_off_gle = d
|
||||||
if d.debit_in_account_currency:
|
if d.debit:
|
||||||
debit_credit_diff -= flt(d.debit_in_account_currency)
|
debit_credit_diff -= flt(d.debit)
|
||||||
else:
|
else:
|
||||||
debit_credit_diff += flt(d.credit_in_account_currency)
|
debit_credit_diff += flt(d.credit)
|
||||||
round_off_account_exists = True
|
round_off_account_exists = True
|
||||||
|
|
||||||
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
|
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
|
||||||
|
@ -166,6 +166,11 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
"fieldname": "show_cancelled_entries",
|
"fieldname": "show_cancelled_entries",
|
||||||
"label": __("Show Cancelled Entries"),
|
"label": __("Show Cancelled Entries"),
|
||||||
"fieldtype": "Check"
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "show_net_values_in_party_account",
|
||||||
|
"label": __("Show Net Values in Party Account"),
|
||||||
|
"fieldtype": "Check"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -344,6 +344,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
|||||||
consolidated_gle = OrderedDict()
|
consolidated_gle = OrderedDict()
|
||||||
group_by = group_by_field(filters.get('group_by'))
|
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):
|
def update_value_in_dict(data, key, gle):
|
||||||
data[key].debit += flt(gle.debit)
|
data[key].debit += flt(gle.debit)
|
||||||
data[key].credit += flt(gle.credit)
|
data[key].credit += flt(gle.credit)
|
||||||
@ -351,6 +354,24 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
|||||||
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
|
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
|
||||||
data[key].credit_in_account_currency += flt(gle.credit_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:
|
if data[key].against_voucher and gle.against_voucher:
|
||||||
data[key].against_voucher += ', ' + gle.against_voucher
|
data[key].against_voucher += ', ' + gle.against_voucher
|
||||||
|
|
||||||
@ -388,6 +409,12 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
|||||||
|
|
||||||
return totals, entries
|
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):
|
def get_result_as_list(data, filters):
|
||||||
balance, balance_in_account_currency = 0, 0
|
balance, balance_in_account_currency = 0, 0
|
||||||
inv_details = get_supplier_invoice_details()
|
inv_details = get_supplier_invoice_details()
|
||||||
|
@ -81,8 +81,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
|
|||||||
presentation_currency = currency_info['presentation_currency']
|
presentation_currency = currency_info['presentation_currency']
|
||||||
company_currency = currency_info['company_currency']
|
company_currency = currency_info['company_currency']
|
||||||
|
|
||||||
pl_accounts = [d.name for d in frappe.get_list('Account',
|
account_currencies = list(set(entry['account_currency'] for entry in gl_entries))
|
||||||
filters={'report_type': 'Profit and Loss', 'company': company})]
|
|
||||||
|
|
||||||
for entry in gl_entries:
|
for entry in gl_entries:
|
||||||
account = entry['account']
|
account = entry['account']
|
||||||
@ -92,10 +91,15 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
|
|||||||
credit_in_account_currency = flt(entry['credit_in_account_currency'])
|
credit_in_account_currency = flt(entry['credit_in_account_currency'])
|
||||||
account_currency = entry['account_currency']
|
account_currency = entry['account_currency']
|
||||||
|
|
||||||
if account_currency != presentation_currency:
|
if len(account_currencies) == 1 and account_currency == presentation_currency:
|
||||||
value = debit or credit
|
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)
|
converted_value = convert(value, presentation_currency, company_currency, date)
|
||||||
|
|
||||||
if entry.get('debit'):
|
if entry.get('debit'):
|
||||||
@ -104,13 +108,6 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
|
|||||||
if entry.get('credit'):
|
if entry.get('credit'):
|
||||||
entry['credit'] = converted_value
|
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)
|
converted_gl_list.append(entry)
|
||||||
|
|
||||||
return converted_gl_list
|
return converted_gl_list
|
||||||
|
@ -566,7 +566,7 @@ class TestAsset(unittest.TestCase):
|
|||||||
doc = make_invoice(pr.name)
|
doc = make_invoice(pr.name)
|
||||||
|
|
||||||
self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
||||||
|
|
||||||
def test_asset_cwip_toggling_cases(self):
|
def test_asset_cwip_toggling_cases(self):
|
||||||
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
|
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
|
||||||
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
|
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
|
||||||
|
@ -9,12 +9,12 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
|||||||
from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import execute
|
from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import execute
|
||||||
import json, frappe, unittest
|
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')
|
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', target='_Test Warehouse - _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 Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
|
||||||
transfer_subcontracted_raw_materials(po.name)
|
transfer_subcontracted_raw_materials(po.name)
|
||||||
col, data = execute(filters=frappe._dict({'supplier': po.supplier,
|
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)),
|
'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
|
||||||
@ -38,7 +38,8 @@ def transfer_subcontracted_raw_materials(po):
|
|||||||
'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}]
|
'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}]
|
||||||
rm_item_string = json.dumps(rm_item)
|
rm_item_string = json.dumps(rm_item)
|
||||||
se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string))
|
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.to_warehouse = '_Test Warehouse 1 - _TC'
|
||||||
se.stock_entry_type = 'Send to Subcontractor'
|
se.stock_entry_type = 'Send to Subcontractor'
|
||||||
se.save()
|
se.save()
|
||||||
se.submit()
|
se.submit()
|
||||||
|
@ -225,7 +225,7 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
def validate_date_with_fiscal_year(self):
|
def validate_date_with_fiscal_year(self):
|
||||||
if self.meta.get_field("fiscal_year"):
|
if self.meta.get_field("fiscal_year"):
|
||||||
date_field = ""
|
date_field = None
|
||||||
if self.meta.get_field("posting_date"):
|
if self.meta.get_field("posting_date"):
|
||||||
date_field = "posting_date"
|
date_field = "posting_date"
|
||||||
elif self.meta.get_field("transaction_date"):
|
elif self.meta.get_field("transaction_date"):
|
||||||
@ -1092,7 +1092,6 @@ class AccountsController(TransactionBase):
|
|||||||
else:
|
else:
|
||||||
grand_total -= self.get("total_advance")
|
grand_total -= self.get("total_advance")
|
||||||
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
||||||
|
|
||||||
if total != flt(grand_total, self.precision("grand_total")) or \
|
if total != flt(grand_total, self.precision("grand_total")) or \
|
||||||
base_total != flt(base_grand_total, self.precision("base_grand_total")):
|
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"))
|
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
|
||||||
@ -1530,6 +1529,7 @@ def validate_and_delete_children(parent, data):
|
|||||||
for d in deleted_children:
|
for d in deleted_children:
|
||||||
update_bin_on_delete(d, parent.doctype)
|
update_bin_on_delete(d, parent.doctype)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
||||||
def check_doc_permissions(doc, perm_type='create'):
|
def check_doc_permissions(doc, perm_type='create'):
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
import erpnext
|
import erpnext
|
||||||
|
import json
|
||||||
from frappe.desk.reportview import get_match_cond, get_filters_cond
|
from frappe.desk.reportview import get_match_cond, get_filters_cond
|
||||||
from frappe.utils import nowdate, getdate
|
from frappe.utils import nowdate, getdate
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@ -198,13 +199,16 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
|
if isinstance(filters, str):
|
||||||
|
filters = json.loads(filters)
|
||||||
|
|
||||||
#Get searchfields from meta and use in Item Link field query
|
#Get searchfields from meta and use in Item Link field query
|
||||||
meta = frappe.get_meta("Item", cached=True)
|
meta = frappe.get_meta("Item", cached=True)
|
||||||
searchfields = meta.get_search_fields()
|
searchfields = meta.get_search_fields()
|
||||||
|
|
||||||
if "description" in searchfields:
|
if "description" in searchfields:
|
||||||
searchfields.remove("description")
|
searchfields.remove("description")
|
||||||
|
|
||||||
columns = ''
|
columns = ''
|
||||||
extra_searchfields = [field for field in searchfields
|
extra_searchfields = [field for field in searchfields
|
||||||
if not field in ["name", "item_group", "description"]]
|
if not field in ["name", "item_group", "description"]]
|
||||||
@ -216,9 +220,10 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
if not field in searchfields]
|
if not field in searchfields]
|
||||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||||
|
|
||||||
if filters.get('supplier'):
|
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_group_list = frappe.get_all('Supplier Item Group',
|
||||||
|
filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
|
||||||
|
|
||||||
item_groups = []
|
item_groups = []
|
||||||
for i in item_group_list:
|
for i in item_group_list:
|
||||||
item_groups.append(i.item_group)
|
item_groups.append(i.item_group)
|
||||||
@ -227,7 +232,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
|
|
||||||
if item_groups:
|
if item_groups:
|
||||||
filters['item_group'] = ['in', item_groups]
|
filters['item_group'] = ['in', item_groups]
|
||||||
|
|
||||||
description_cond = ''
|
description_cond = ''
|
||||||
if frappe.db.count('Item', cache=True) < 50000:
|
if frappe.db.count('Item', cache=True) < 50000:
|
||||||
# scan description only if items are less than 50000
|
# scan description only if items are less than 50000
|
||||||
|
@ -76,12 +76,12 @@ status_map = {
|
|||||||
["Stopped", "eval:self.status == 'Stopped'"],
|
["Stopped", "eval:self.status == 'Stopped'"],
|
||||||
["Cancelled", "eval:self.docstatus == 2"],
|
["Cancelled", "eval:self.docstatus == 2"],
|
||||||
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
|
["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'"],
|
["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'"],
|
["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'"],
|
["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'"],
|
["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 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'"]
|
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
|
||||||
],
|
],
|
||||||
"Bank Transaction": [
|
"Bank Transaction": [
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
import json
|
||||||
import frappe, erpnext
|
|
||||||
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
|
|
||||||
from frappe import _
|
|
||||||
import frappe.defaults
|
|
||||||
from collections import defaultdict
|
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.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.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 import get_warehouse_account_map
|
||||||
|
from erpnext.stock.stock_ledger import get_valuation_rate
|
||||||
|
|
||||||
|
|
||||||
class QualityInspectionRequiredError(frappe.ValidationError): pass
|
class QualityInspectionRequiredError(frappe.ValidationError): pass
|
||||||
class QualityInspectionRejectedError(frappe.ValidationError): pass
|
class QualityInspectionRejectedError(frappe.ValidationError): pass
|
||||||
@ -189,7 +193,6 @@ class StockController(AccountsController):
|
|||||||
if hasattr(self, "items"):
|
if hasattr(self, "items"):
|
||||||
item_doclist = self.get("items")
|
item_doclist = self.get("items")
|
||||||
elif self.doctype == "Stock Reconciliation":
|
elif self.doctype == "Stock Reconciliation":
|
||||||
import json
|
|
||||||
item_doclist = []
|
item_doclist = []
|
||||||
data = json.loads(self.reconciliation_json)
|
data = json.loads(self.reconciliation_json)
|
||||||
for row in data[data.index(self.head_row)+1:]:
|
for row in data[data.index(self.head_row)+1:]:
|
||||||
@ -319,7 +322,7 @@ class StockController(AccountsController):
|
|||||||
return serialized_items
|
return serialized_items
|
||||||
|
|
||||||
def validate_warehouse(self):
|
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
|
warehouses = list(set([d.warehouse for d in
|
||||||
self.get("items") if getattr(d, "warehouse", None)]))
|
self.get("items") if getattr(d, "warehouse", None)]))
|
||||||
@ -498,6 +501,39 @@ class StockController(AccountsController):
|
|||||||
check_if_stock_and_account_balance_synced(self.posting_date,
|
check_if_stock_and_account_balance_synced(self.posting_date,
|
||||||
self.company, self.doctype, self.name)
|
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():
|
def is_reposting_pending():
|
||||||
return frappe.db.exists("Repost Item Valuation",
|
return frappe.db.exists("Repost Item Valuation",
|
||||||
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
|
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
|
||||||
|
@ -38,7 +38,7 @@ class Appointment(Document):
|
|||||||
number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents')
|
number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents')
|
||||||
if not number_of_agents == 0:
|
if not number_of_agents == 0:
|
||||||
if (number_of_appointments_in_same_slot >= number_of_agents):
|
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
|
# Link lead
|
||||||
if not self.party:
|
if not self.party:
|
||||||
lead = self.find_lead_by_email()
|
lead = self.find_lead_by_email()
|
||||||
@ -75,10 +75,10 @@ class Appointment(Document):
|
|||||||
subject=_('Appointment Confirmation'))
|
subject=_('Appointment Confirmation'))
|
||||||
if frappe.session.user == "Guest":
|
if frappe.session.user == "Guest":
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
'Please check your email to confirm the appointment')
|
_('Please check your email to confirm the appointment'))
|
||||||
else :
|
else :
|
||||||
frappe.msgprint(
|
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):
|
def on_change(self):
|
||||||
# Sync Calendar
|
# Sync Calendar
|
||||||
@ -91,7 +91,7 @@ class Appointment(Document):
|
|||||||
|
|
||||||
def set_verified(self, email):
|
def set_verified(self, email):
|
||||||
if not email == self.customer_email:
|
if not email == self.customer_email:
|
||||||
frappe.throw('Email verification failed.')
|
frappe.throw(_('Email verification failed.'))
|
||||||
# Create new lead
|
# Create new lead
|
||||||
self.create_lead_and_link()
|
self.create_lead_and_link()
|
||||||
# Remove unverified status
|
# Remove unverified status
|
||||||
@ -184,7 +184,7 @@ class Appointment(Document):
|
|||||||
appointment_event.insert(ignore_permissions=True)
|
appointment_event.insert(ignore_permissions=True)
|
||||||
self.calendar_event = appointment_event.name
|
self.calendar_event = appointment_event.name
|
||||||
self.save(ignore_permissions=True)
|
self.save(ignore_permissions=True)
|
||||||
|
|
||||||
def _get_verify_url(self):
|
def _get_verify_url(self):
|
||||||
verify_route = '/book_appointment/verify'
|
verify_route = '/book_appointment/verify'
|
||||||
params = {
|
params = {
|
||||||
|
@ -280,7 +280,6 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:",
|
|
||||||
"fieldname": "territory",
|
"fieldname": "territory",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Territory",
|
"label": "Territory",
|
||||||
@ -431,7 +430,7 @@
|
|||||||
"icon": "fa fa-info-sign",
|
"icon": "fa fa-info-sign",
|
||||||
"idx": 195,
|
"idx": 195,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-01-06 19:42:46.190051",
|
"modified": "2021-06-04 10:11:22.831139",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Opportunity",
|
"name": "Opportunity",
|
||||||
|
@ -75,7 +75,7 @@ class CourseSchedulingTool(Document):
|
|||||||
"""Validates if Course Start Date is greater than Course End Date"""
|
"""Validates if Course Start Date is greater than Course End Date"""
|
||||||
if self.course_start_date > self.course_end_date:
|
if self.course_start_date > self.course_end_date:
|
||||||
frappe.throw(
|
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):
|
def delete_course_schedule(self, rescheduled, reschedule_errors):
|
||||||
"""Delete all course schedule within the Date range and specified filters"""
|
"""Delete all course schedule within the Date range and specified filters"""
|
||||||
|
@ -9,8 +9,7 @@ from frappe.utils import nowdate
|
|||||||
from frappe.utils.make_random import get_random
|
from frappe.utils.make_random import get_random
|
||||||
from erpnext.education.doctype.program.test_program import make_program_and_linked_courses
|
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):
|
class TestFees(unittest.TestCase):
|
||||||
|
|
||||||
def test_fees(self):
|
def test_fees(self):
|
||||||
|
@ -74,7 +74,6 @@ class Student(Document):
|
|||||||
student_user.flags.ignore_permissions = True
|
student_user.flags.ignore_permissions = True
|
||||||
student_user.add_roles("Student")
|
student_user.add_roles("Student")
|
||||||
student_user.save()
|
student_user.save()
|
||||||
update_password_link = student_user.reset_password()
|
|
||||||
|
|
||||||
def update_applicant_status(self):
|
def update_applicant_status(self):
|
||||||
"""Updates Student Applicant status to Admitted"""
|
"""Updates Student Applicant status to Admitted"""
|
||||||
|
@ -81,7 +81,7 @@ class TestMpesaSettings(unittest.TestCase):
|
|||||||
integration_request.reload()
|
integration_request.reload()
|
||||||
self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
|
self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
|
||||||
self.assertEqual(integration_request.status, "Completed")
|
self.assertEqual(integration_request.status, "Completed")
|
||||||
|
|
||||||
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
|
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
|
||||||
integration_request.delete()
|
integration_request.delete()
|
||||||
pr.reload()
|
pr.reload()
|
||||||
@ -139,7 +139,7 @@ class TestMpesaSettings(unittest.TestCase):
|
|||||||
pr.cancel()
|
pr.cancel()
|
||||||
pr.delete()
|
pr.delete()
|
||||||
pos_invoice.delete()
|
pos_invoice.delete()
|
||||||
|
|
||||||
def test_processing_of_only_one_succes_callback_payload(self):
|
def test_processing_of_only_one_succes_callback_payload(self):
|
||||||
create_mpesa_settings(payment_gateway_name="Payment")
|
create_mpesa_settings(payment_gateway_name="Payment")
|
||||||
mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
|
mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
|
||||||
|
@ -99,5 +99,7 @@ class PlaidConnector():
|
|||||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
|
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
|
||||||
transactions.extend(response["transactions"])
|
transactions.extend(response["transactions"])
|
||||||
return transactions
|
return transactions
|
||||||
|
except ItemError as e:
|
||||||
|
raise e
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
||||||
|
@ -16,6 +16,10 @@ frappe.ui.form.on('Plaid Settings', {
|
|||||||
new erpnext.integrations.plaidLink(frm);
|
new erpnext.integrations.plaidLink(frm);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Reset Plaid Link'), () => {
|
||||||
|
new erpnext.integrations.plaidLink(frm);
|
||||||
|
});
|
||||||
|
|
||||||
frm.add_custom_button(__("Sync Now"), () => {
|
frm.add_custom_button(__("Sync Now"), () => {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
|
method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
|
||||||
|
@ -12,6 +12,7 @@ from frappe.desk.doctype.tag.tag import add_tag
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import add_months, formatdate, getdate, today
|
from frappe.utils import add_months, formatdate, getdate, today
|
||||||
|
|
||||||
|
from plaid.errors import ItemError
|
||||||
|
|
||||||
class PlaidSettings(Document):
|
class PlaidSettings(Document):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -51,7 +52,7 @@ def add_institution(token, response):
|
|||||||
})
|
})
|
||||||
bank.insert()
|
bank.insert()
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.throw(frappe.get_traceback())
|
frappe.log_error(frappe.get_traceback(), title=_('Plaid Link Error'))
|
||||||
else:
|
else:
|
||||||
bank = frappe.get_doc("Bank", response["institution"]["name"])
|
bank = frappe.get_doc("Bank", response["institution"]["name"])
|
||||||
bank.plaid_access_token = access_token
|
bank.plaid_access_token = access_token
|
||||||
@ -83,7 +84,12 @@ def add_bank_accounts(response, bank, company):
|
|||||||
if not acc_subtype:
|
if not acc_subtype:
|
||||||
add_account_subtype(account["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:
|
try:
|
||||||
new_account = frappe.get_doc({
|
new_account = frappe.get_doc({
|
||||||
"doctype": "Bank Account",
|
"doctype": "Bank Account",
|
||||||
@ -103,10 +109,27 @@ def add_bank_accounts(response, bank, company):
|
|||||||
except frappe.UniqueValidationError:
|
except frappe.UniqueValidationError:
|
||||||
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
|
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
|
||||||
except Exception:
|
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:
|
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
|
return result
|
||||||
|
|
||||||
@ -172,9 +195,16 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
|||||||
account_id = None
|
account_id = None
|
||||||
|
|
||||||
plaid = PlaidConnector(access_token)
|
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):
|
def new_bank_transaction(transaction):
|
||||||
@ -183,11 +213,11 @@ def new_bank_transaction(transaction):
|
|||||||
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
|
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
|
||||||
|
|
||||||
if float(transaction["amount"]) >= 0:
|
if float(transaction["amount"]) >= 0:
|
||||||
debit = float(transaction["amount"])
|
|
||||||
credit = 0
|
|
||||||
else:
|
|
||||||
debit = 0
|
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"
|
status = "Pending" if transaction["pending"] == "True" else "Settled"
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class TestTherapyPlan(unittest.TestCase):
|
|||||||
self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
|
self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
|
||||||
|
|
||||||
patient, medical_department, practitioner = create_healthcare_docs()
|
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 = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
|
||||||
session = frappe.get_doc(session)
|
session = frappe.get_doc(session)
|
||||||
session.submit()
|
session.submit()
|
||||||
|
@ -332,7 +332,9 @@ scheduler_events = {
|
|||||||
"erpnext.projects.doctype.project.project.collect_project_status",
|
"erpnext.projects.doctype.project.project.collect_project_status",
|
||||||
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
|
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
|
||||||
"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
|
"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
|
||||||
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
|
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders"
|
||||||
|
],
|
||||||
|
"hourly_long": [
|
||||||
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
|
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
|
||||||
],
|
],
|
||||||
"daily": [
|
"daily": [
|
||||||
|
@ -4,8 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
|
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate
|
||||||
comma_or, get_fullname, add_days, nowdate, get_datetime_str
|
|
||||||
from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
|
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.leave_block_list.leave_block_list import get_applicable_block_dates
|
||||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||||
@ -85,7 +84,7 @@ class LeaveApplication(Document):
|
|||||||
|
|
||||||
def validate_dates(self):
|
def validate_dates(self):
|
||||||
if frappe.db.get_single_value("HR Settings", "restrict_backdated_leave_application"):
|
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")
|
allowed_role = frappe.db.get_single_value("HR Settings", "role_allowed_to_create_backdated_leave_application")
|
||||||
if allowed_role not in frappe.get_roles():
|
if allowed_role not in frappe.get_roles():
|
||||||
frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role))
|
frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role))
|
||||||
@ -248,9 +247,9 @@ class LeaveApplication(Document):
|
|||||||
self.throw_overlap_error(d)
|
self.throw_overlap_error(d)
|
||||||
|
|
||||||
def throw_overlap_error(self, d):
|
def throw_overlap_error(self, d):
|
||||||
msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
|
form_link = get_link_to_form("Leave Application", d.name)
|
||||||
d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
|
msg = _("Employee {0} has already applied for {1} between {2} and {3} : {4}").format(self.employee,
|
||||||
+ """ <b><a href="/app/Form/Leave Application/{0}">{0}</a></b>""".format(d["name"])
|
d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date']), form_link)
|
||||||
frappe.throw(msg, OverlapError)
|
frappe.throw(msg, OverlapError)
|
||||||
|
|
||||||
def get_total_leaves_on_half_day(self):
|
def get_total_leaves_on_half_day(self):
|
||||||
@ -356,7 +355,7 @@ class LeaveApplication(Document):
|
|||||||
|
|
||||||
sender = dict()
|
sender = dict()
|
||||||
sender['email'] = frappe.get_doc('User', frappe.session.user).email
|
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:
|
try:
|
||||||
frappe.sendmail(
|
frappe.sendmail(
|
||||||
@ -823,4 +822,4 @@ def get_leave_approver(employee):
|
|||||||
leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
|
leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
|
||||||
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
|
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
|
||||||
|
|
||||||
return leave_approver
|
return leave_approver
|
||||||
|
@ -5,11 +5,18 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
import erpnext
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data
|
from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data
|
||||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
|
||||||
|
test_dependencies = ['Holiday List']
|
||||||
|
|
||||||
class TestUploadAttendance(unittest.TestCase):
|
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):
|
def test_date_range(self):
|
||||||
employee = make_employee("test_employee@company.com")
|
employee = make_employee("test_employee@company.com")
|
||||||
employee_doc = frappe.get_doc("Employee", employee)
|
employee_doc = frappe.get_doc("Employee", employee)
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
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.employee.test_employee import make_employee
|
||||||
from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
|
from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
|
||||||
|
|
||||||
@ -18,23 +18,13 @@ class TestVehicleLog(unittest.TestCase):
|
|||||||
self.employee_id = make_employee("testdriver@example.com", company="_Test Company")
|
self.employee_id = make_employee("testdriver@example.com", company="_Test Company")
|
||||||
|
|
||||||
self.license_plate = get_vehicle(self.employee_id)
|
self.license_plate = get_vehicle(self.employee_id)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.delete_doc("Vehicle", self.license_plate, force=1)
|
frappe.delete_doc("Vehicle", self.license_plate, force=1)
|
||||||
frappe.delete_doc("Employee", self.employee_id, force=1)
|
frappe.delete_doc("Employee", self.employee_id, force=1)
|
||||||
|
|
||||||
def test_make_vehicle_log_and_syncing_of_odometer_value(self):
|
def test_make_vehicle_log_and_syncing_of_odometer_value(self):
|
||||||
vehicle_log = frappe.get_doc({
|
vehicle_log = make_vehicle_log(self.license_plate, self.employee_id)
|
||||||
"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()
|
|
||||||
|
|
||||||
#checking value of vehicle odometer value on submit.
|
#checking value of vehicle odometer value on submit.
|
||||||
vehicle = frappe.get_doc("Vehicle", self.license_plate)
|
vehicle = frappe.get_doc("Vehicle", self.license_plate)
|
||||||
@ -51,19 +41,9 @@ class TestVehicleLog(unittest.TestCase):
|
|||||||
self.assertEqual(vehicle.last_odometer, current_odometer - distance_travelled)
|
self.assertEqual(vehicle.last_odometer, current_odometer - distance_travelled)
|
||||||
|
|
||||||
vehicle_log.delete()
|
vehicle_log.delete()
|
||||||
|
|
||||||
def test_vehicle_log_fuel_expense(self):
|
def test_vehicle_log_fuel_expense(self):
|
||||||
vehicle_log = frappe.get_doc({
|
vehicle_log = make_vehicle_log(self.license_plate, self.employee_id)
|
||||||
"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()
|
|
||||||
|
|
||||||
expense_claim = make_expense_claim(vehicle_log.name)
|
expense_claim = make_expense_claim(vehicle_log.name)
|
||||||
fuel_expense = expense_claim.expenses[0].amount
|
fuel_expense = expense_claim.expenses[0].amount
|
||||||
@ -73,6 +53,18 @@ class TestVehicleLog(unittest.TestCase):
|
|||||||
frappe.delete_doc("Expense Claim", expense_claim.name)
|
frappe.delete_doc("Expense Claim", expense_claim.name)
|
||||||
frappe.delete_doc("Vehicle Log", vehicle_log.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):
|
def get_vehicle(employee_id):
|
||||||
license_plate=random_string(10).upper()
|
license_plate=random_string(10).upper()
|
||||||
vehicle = frappe.get_doc({
|
vehicle = frappe.get_doc({
|
||||||
@ -81,15 +73,46 @@ def get_vehicle(employee_id):
|
|||||||
"make": "Maruti",
|
"make": "Maruti",
|
||||||
"model": "PCM",
|
"model": "PCM",
|
||||||
"employee": employee_id,
|
"employee": employee_id,
|
||||||
"last_odometer":5000,
|
"last_odometer": 5000,
|
||||||
"acquisition_date":frappe.utils.nowdate(),
|
"acquisition_date": nowdate(),
|
||||||
"location": "Mumbai",
|
"location": "Mumbai",
|
||||||
"chassis_no": "1234ABCD",
|
"chassis_no": "1234ABCD",
|
||||||
"uom": "Litre",
|
"uom": "Litre",
|
||||||
"vehicle_value":frappe.utils.flt(500000)
|
"vehicle_value": flt(500000)
|
||||||
})
|
})
|
||||||
try:
|
try:
|
||||||
vehicle.insert()
|
vehicle.insert()
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
pass
|
pass
|
||||||
return license_plate
|
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
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2016-09-03 14:14:51.788550",
|
"creation": "2016-09-03 14:14:51.788550",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -10,7 +11,6 @@
|
|||||||
"naming_series",
|
"naming_series",
|
||||||
"license_plate",
|
"license_plate",
|
||||||
"employee",
|
"employee",
|
||||||
"column_break_4",
|
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
"model",
|
"model",
|
||||||
"make",
|
"make",
|
||||||
@ -65,10 +65,6 @@
|
|||||||
"options": "Employee",
|
"options": "Employee",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_4",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_7",
|
"fieldname": "column_break_7",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
@ -142,7 +138,6 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "service_detail",
|
"fieldname": "service_detail",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Service Detail",
|
|
||||||
"options": "Vehicle Service"
|
"options": "Vehicle Service"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -158,7 +153,7 @@
|
|||||||
"fetch_from": "license_plate.last_odometer",
|
"fetch_from": "license_plate.last_odometer",
|
||||||
"fieldname": "last_odometer",
|
"fieldname": "last_odometer",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "last Odometer Value ",
|
"label": "Last Odometer Value ",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@ -168,7 +163,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2020-03-18 16:45:45.060761",
|
"links": [],
|
||||||
|
"modified": "2021-05-17 00:10:21.188352",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Vehicle Log",
|
"name": "Vehicle Log",
|
||||||
|
@ -37,5 +37,22 @@ frappe.query_reports["Employee Leave Balance"] = {
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Employee",
|
"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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,16 @@ import frappe
|
|||||||
from frappe.utils import flt, add_days
|
from frappe.utils import flt, add_days
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on
|
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):
|
def execute(filters=None):
|
||||||
if filters.to_date <= filters.from_date:
|
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()
|
columns = get_columns()
|
||||||
data = get_data(filters)
|
data = get_data(filters)
|
||||||
|
charts = get_chart_data(data)
|
||||||
return columns, data
|
return columns, data, None, charts
|
||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
columns = [{
|
columns = [{
|
||||||
@ -31,9 +32,10 @@ def get_columns():
|
|||||||
'options': 'Employee'
|
'options': 'Employee'
|
||||||
}, {
|
}, {
|
||||||
'label': _('Employee Name'),
|
'label': _('Employee Name'),
|
||||||
'fieldtype': 'Data',
|
'fieldtype': 'Dynamic Link',
|
||||||
'fieldname': 'employee_name',
|
'fieldname': 'employee_name',
|
||||||
'width': 100,
|
'width': 100,
|
||||||
|
'options': 'employee'
|
||||||
}, {
|
}, {
|
||||||
'label': _('Opening Balance'),
|
'label': _('Opening Balance'),
|
||||||
'fieldtype': 'float',
|
'fieldtype': 'float',
|
||||||
@ -64,8 +66,7 @@ def get_columns():
|
|||||||
return columns
|
return columns
|
||||||
|
|
||||||
def get_data(filters):
|
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)
|
conditions = get_conditions(filters)
|
||||||
|
|
||||||
user = frappe.session.user
|
user = frappe.session.user
|
||||||
@ -113,12 +114,8 @@ def get_data(filters):
|
|||||||
|
|
||||||
# not be shown on the basis of days left it create in user mind for carry_forward leave
|
# 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.closing_balance = (new_allocation + opening - (row.leaves_expired + leaves_taken))
|
||||||
|
|
||||||
|
|
||||||
row.indent = 1
|
row.indent = 1
|
||||||
data.append(row)
|
data.append(row)
|
||||||
new_leaves_allocated = 0
|
|
||||||
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -129,27 +126,37 @@ def get_conditions(filters):
|
|||||||
if filters.get('employee'):
|
if filters.get('employee'):
|
||||||
conditions['name'] = filters.get('employee')
|
conditions['name'] = filters.get('employee')
|
||||||
|
|
||||||
if filters.get('employee'):
|
|
||||||
conditions['name'] = filters.get('employee')
|
|
||||||
|
|
||||||
if filters.get('company'):
|
if filters.get('company'):
|
||||||
conditions['company'] = filters.get('company')
|
conditions['company'] = filters.get('company')
|
||||||
|
|
||||||
|
if filters.get('department'):
|
||||||
|
conditions['department'] = filters.get('department')
|
||||||
|
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
def get_department_leave_approver_map(department=None):
|
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
|
# 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
|
# retrieve approvers list from current department and from its subsequent child departments
|
||||||
approver_list = frappe.get_all('Department Approver', filters={
|
approver_list = frappe.get_all('Department Approver',
|
||||||
'parentfield': 'leave_approvers',
|
filters={
|
||||||
'parent': ('in', department_list)
|
'parentfield': 'leave_approvers',
|
||||||
}, fields=['parent', 'approver'], as_list=1)
|
'parent': ('in', department_list)
|
||||||
|
},
|
||||||
|
fields=['parent', 'approver'],
|
||||||
|
as_list=1
|
||||||
|
)
|
||||||
|
|
||||||
approvers = {}
|
approvers = {}
|
||||||
|
|
||||||
@ -190,3 +197,40 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type):
|
|||||||
new_allocation += record.leaves
|
new_allocation += record.leaves
|
||||||
|
|
||||||
return new_allocation, expired_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]})
|
||||||
|
73
erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
Normal file
73
erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
Normal file
@ -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)
|
@ -1,31 +1,52 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
frappe.query_reports["Vehicle Expenses"] = {
|
||||||
frappe.query_reports["Vehicle Expenses"] = {
|
"filters": [
|
||||||
"filters": [
|
{
|
||||||
{
|
"fieldname": "filter_based_on",
|
||||||
"fieldname": "fiscal_year",
|
"label": __("Filter Based On"),
|
||||||
"label": __("Fiscal Year"),
|
"fieldtype": "Select",
|
||||||
"fieldtype": "Link",
|
"options": ["Fiscal Year", "Date Range"],
|
||||||
"options": "Fiscal Year",
|
"default": ["Fiscal Year"],
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"reqd": 1
|
||||||
"reqd": 1,
|
},
|
||||||
"on_change": function(query_report) {
|
{
|
||||||
var fiscal_year = query_report.get_values().fiscal_year;
|
"fieldname": "fiscal_year",
|
||||||
if (!fiscal_year) {
|
"label": __("Fiscal Year"),
|
||||||
return;
|
"fieldtype": "Link",
|
||||||
}
|
"options": "Fiscal Year",
|
||||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
|
||||||
|
"reqd": 1
|
||||||
frappe.query_report.set_filter({
|
},
|
||||||
from_date: fy.year_start_date,
|
{
|
||||||
to_date: fy.year_end_date
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 1,
|
||||||
"apply_user_permissions": 1,
|
"columns": [],
|
||||||
"creation": "2016-09-09 03:33:40.605734",
|
"creation": "2016-09-09 03:33:40.605734",
|
||||||
"disabled": 0,
|
"disable_prepared_report": 0,
|
||||||
"docstatus": 0,
|
"disabled": 0,
|
||||||
"doctype": "Report",
|
"docstatus": 0,
|
||||||
"idx": 2,
|
"doctype": "Report",
|
||||||
"is_standard": "Yes",
|
"filters": [],
|
||||||
"modified": "2017-02-24 19:59:18.641284",
|
"idx": 2,
|
||||||
"modified_by": "Administrator",
|
"is_standard": "Yes",
|
||||||
"module": "HR",
|
"modified": "2021-05-16 22:48:22.767535",
|
||||||
"name": "Vehicle Expenses",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator",
|
"module": "HR",
|
||||||
"ref_doctype": "Vehicle",
|
"name": "Vehicle Expenses",
|
||||||
"report_name": "Vehicle Expenses",
|
"owner": "Administrator",
|
||||||
"report_type": "Script Report",
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Vehicle",
|
||||||
|
"report_name": "Vehicle Expenses",
|
||||||
|
"report_type": "Script Report",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"role": "Fleet Manager"
|
"role": "Fleet Manager"
|
||||||
|
@ -5,86 +5,209 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import erpnext
|
import erpnext
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt,cstr
|
from frappe.utils import flt
|
||||||
from erpnext.accounts.report.financial_statements import get_period_list
|
from erpnext.accounts.report.financial_statements import get_period_list
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
columns, data, chart = [], [], []
|
filters = frappe._dict(filters or {})
|
||||||
if filters.get('fiscal_year'):
|
|
||||||
company = erpnext.get_default_company()
|
columns = get_columns()
|
||||||
period_list = get_period_list(filters.get('fiscal_year'), filters.get('fiscal_year'),
|
data = get_vehicle_log_data(filters)
|
||||||
'', '', 'Fiscal Year', 'Monthly', company=company)
|
chart = get_chart_data(data, filters)
|
||||||
columns=get_columns()
|
|
||||||
data=get_log_data(filters)
|
|
||||||
chart=get_chart_data(data,period_list)
|
|
||||||
return columns, data, None, chart
|
return columns, data, None, chart
|
||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
columns = [_("License") + ":Link/Vehicle:100", _('Create') + ":data:50",
|
return [
|
||||||
_("Model") + ":data:50", _("Location") + ":data:100",
|
{
|
||||||
_("Log") + ":Link/Vehicle Log:100", _("Odometer") + ":Int:80",
|
'fieldname': 'vehicle',
|
||||||
_("Date") + ":Date:100", _("Fuel Qty") + ":Float:80",
|
'fieldtype': 'Link',
|
||||||
_("Fuel Price") + ":Float:100",_("Fuel Expense") + ":Float:100",
|
'label': _('Vehicle'),
|
||||||
_("Service Expense") + ":Float:100"
|
'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
|
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)
|
def get_vehicle_log_data(filters):
|
||||||
data = frappe.db.sql("""select
|
start_date, end_date = get_period_dates(filters)
|
||||||
vhcl.license_plate as "License", vhcl.make as "Make", vhcl.model as "Model",
|
conditions, values = get_conditions(filters)
|
||||||
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",
|
data = frappe.db.sql("""
|
||||||
log.fuel_qty * log.price as "Fuel Expense"
|
SELECT
|
||||||
from
|
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
|
`tabVehicle` vhcl,`tabVehicle Log` log
|
||||||
where
|
WHERE
|
||||||
vhcl.license_plate = log.license_plate and log.docstatus = 1 and date between %s and %s
|
vhcl.license_plate = log.license_plate
|
||||||
order by date""" ,(fy.year_start_date, fy.year_end_date), as_dict=1)
|
and log.docstatus = 1
|
||||||
dl=list(data)
|
and date between %(start_date)s and %(end_date)s
|
||||||
for row in dl:
|
{0}
|
||||||
row["Service Expense"]= get_service_expense(row["Log"])
|
ORDER BY date""".format(conditions), values, as_dict=1)
|
||||||
return dl
|
|
||||||
|
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):
|
def get_service_expense(logname):
|
||||||
expense_amount = frappe.db.sql("""select sum(expense_amount)
|
expense_amount = frappe.db.sql("""
|
||||||
from `tabVehicle Log` log,`tabVehicle Service` ser
|
SELECT sum(expense_amount)
|
||||||
where ser.parent=log.name and log.name=%s""",logname)
|
FROM
|
||||||
return flt(expense_amount[0][0]) if expense_amount else 0
|
`tabVehicle Log` log, `tabVehicle Service` service
|
||||||
|
WHERE
|
||||||
|
service.parent=log.name and log.name=%s
|
||||||
|
""", logname)
|
||||||
|
|
||||||
|
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 = [], []
|
||||||
|
|
||||||
def get_chart_data(data,period_list):
|
|
||||||
fuel_exp_data,service_exp_data,fueldata,servicedata = [],[],[],[]
|
|
||||||
service_exp_data = []
|
|
||||||
fueldata = []
|
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
total_fuel_exp=0
|
total_fuel_exp = 0
|
||||||
total_ser_exp=0
|
total_service_exp = 0
|
||||||
for row in data:
|
|
||||||
if row["Date"] <= period.to_date and row["Date"] >= period.from_date:
|
for row in data:
|
||||||
total_fuel_exp+=flt(row["Fuel Expense"])
|
if row.date <= period.to_date and row.date >= period.from_date:
|
||||||
total_ser_exp+=flt(row["Service Expense"])
|
total_fuel_exp += flt(row.fuel_expense)
|
||||||
fueldata.append([period.key,total_fuel_exp])
|
total_service_exp += flt(row.service_expense)
|
||||||
servicedata.append([period.key,total_ser_exp])
|
|
||||||
|
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]
|
||||||
|
|
||||||
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]
|
|
||||||
datasets = []
|
datasets = []
|
||||||
if fuel_exp_data:
|
if fuel_exp_data:
|
||||||
datasets.append({
|
datasets.append({
|
||||||
'name': 'Fuel Expenses',
|
'name': _('Fuel Expenses'),
|
||||||
'values': fuel_exp_data
|
'values': fuel_exp_data
|
||||||
})
|
})
|
||||||
|
|
||||||
if service_exp_data:
|
if service_exp_data:
|
||||||
datasets.append({
|
datasets.append({
|
||||||
'name': 'Service Expenses',
|
'name': _('Service Expenses'),
|
||||||
'values': service_exp_data
|
'values': service_exp_data
|
||||||
})
|
})
|
||||||
|
|
||||||
chart = {
|
chart = {
|
||||||
"data": {
|
'data': {
|
||||||
'labels': labels,
|
'labels': labels,
|
||||||
'datasets': datasets
|
'datasets': datasets
|
||||||
}
|
},
|
||||||
|
'type': 'line',
|
||||||
|
'fieldtype': 'Currency'
|
||||||
}
|
}
|
||||||
chart["type"] = "line"
|
|
||||||
return chart
|
return chart
|
||||||
|
@ -269,6 +269,7 @@ def get_total_exemption_amount(declarations):
|
|||||||
total_exemption_amount = sum([flt(d.total_exemption_amount) for d in exemptions.values()])
|
total_exemption_amount = sum([flt(d.total_exemption_amount) for d in exemptions.values()])
|
||||||
return total_exemption_amount
|
return total_exemption_amount
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_leave_period(from_date, to_date, company):
|
def get_leave_period(from_date, to_date, company):
|
||||||
leave_period = frappe.db.sql("""
|
leave_period = frappe.db.sql("""
|
||||||
select name, from_date, to_date
|
select name, from_date, to_date
|
||||||
|
@ -264,7 +264,7 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict
|
|||||||
pending_amount = amounts['pending_principal_amount']
|
pending_amount = amounts['pending_principal_amount']
|
||||||
|
|
||||||
if amount and (amount > pending_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:
|
if not amount:
|
||||||
amount = pending_amount
|
amount = pending_amount
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_default": 0,
|
"is_default": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Loan Management",
|
"label": "Loans",
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -220,10 +220,10 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-02-18 17:31:53.586508",
|
"modified": "2021-05-25 17:31:53.586508",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Management",
|
"name": "Loans",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"pin_to_bottom": 0,
|
"pin_to_bottom": 0,
|
||||||
"pin_to_top": 0,
|
"pin_to_top": 0,
|
||||||
|
@ -2,40 +2,36 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext.maintenance");
|
frappe.provide("erpnext.maintenance");
|
||||||
|
|
||||||
frappe.ui.form.on('Maintenance Schedule', {
|
frappe.ui.form.on('Maintenance Schedule', {
|
||||||
setup: function(frm) {
|
setup: function (frm) {
|
||||||
frm.set_query('contact_person', erpnext.queries.contact_query);
|
frm.set_query('contact_person', erpnext.queries.contact_query);
|
||||||
frm.set_query('customer_address', erpnext.queries.address_query);
|
frm.set_query('customer_address', erpnext.queries.address_query);
|
||||||
frm.set_query('customer', erpnext.queries.customer);
|
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) {
|
if (!frm.doc.status) {
|
||||||
frm.set_value({status:'Draft'});
|
frm.set_value({ status: 'Draft' });
|
||||||
}
|
}
|
||||||
if (frm.doc.__islocal) {
|
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(() => {
|
setTimeout(() => {
|
||||||
frm.toggle_display('generate_schedule', !(frm.is_new()));
|
frm.toggle_display('generate_schedule', !(frm.is_new()));
|
||||||
frm.toggle_display('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)
|
erpnext.utils.get_party_details(frm)
|
||||||
},
|
},
|
||||||
customer_address: function(frm) {
|
customer_address: function (frm) {
|
||||||
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display');
|
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display');
|
||||||
},
|
},
|
||||||
contact_person: function(frm) {
|
contact_person: function (frm) {
|
||||||
erpnext.utils.get_contact_details(frm);
|
erpnext.utils.get_contact_details(frm);
|
||||||
},
|
},
|
||||||
generate_schedule: function(frm) {
|
generate_schedule: function (frm) {
|
||||||
if (frm.is_new()) {
|
if (frm.is_new()) {
|
||||||
frappe.msgprint(__('Please save first'));
|
frappe.msgprint(__('Please save first'));
|
||||||
} else {
|
} else {
|
||||||
@ -46,14 +42,14 @@ frappe.ui.form.on('Maintenance Schedule', {
|
|||||||
|
|
||||||
// TODO commonify this code
|
// TODO commonify this code
|
||||||
erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({
|
erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({
|
||||||
refresh: function() {
|
refresh: function () {
|
||||||
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
|
frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' };
|
||||||
|
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
if (this.frm.doc.docstatus === 0) {
|
if (this.frm.doc.docstatus === 0) {
|
||||||
this.frm.add_custom_button(__('Sales Order'),
|
this.frm.add_custom_button(__('Sales Order'),
|
||||||
function() {
|
function () {
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_schedule",
|
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_schedule",
|
||||||
source_doctype: "Sales Order",
|
source_doctype: "Sales Order",
|
||||||
@ -68,52 +64,107 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({
|
|||||||
});
|
});
|
||||||
}, __("Get Items From"));
|
}, __("Get Items From"));
|
||||||
} else if (this.frm.doc.docstatus === 1) {
|
} else if (this.frm.doc.docstatus === 1) {
|
||||||
this.frm.add_custom_button(__('Create Maintenance Visit'), function() {
|
let schedules = me.frm.doc.schedules;
|
||||||
frappe.model.open_mapped_doc({
|
let flag = schedules.some(schedule => schedule.completion_status === "Pending");
|
||||||
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
|
if (flag) {
|
||||||
source_name: me.frm.doc.name,
|
this.frm.add_custom_button(__('Maintenance Visit'), function () {
|
||||||
frm: me.frm
|
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);
|
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);
|
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);
|
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);
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
|
let me = this;
|
||||||
if (item.start_date && item.end_date && item.periodicity) {
|
if (item.start_date && item.periodicity) {
|
||||||
if(item.start_date > item.end_date) {
|
me.frm.call('validate_end_date_visits');
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({frm: cur_frm}));
|
$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({ frm: cur_frm }));
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -4,12 +4,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
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 frappe import throw, _
|
||||||
from erpnext.utilities.transaction_base import TransactionBase, delete_events
|
from erpnext.utilities.transaction_base import TransactionBase, delete_events
|
||||||
from erpnext.stock.utils import get_valid_serial_nos
|
from erpnext.stock.utils import get_valid_serial_nos
|
||||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
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):
|
class MaintenanceSchedule(TransactionBase):
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@ -32,8 +33,40 @@ class MaintenanceSchedule(TransactionBase):
|
|||||||
child.idx = count
|
child.idx = count
|
||||||
count = count + 1
|
count = count + 1
|
||||||
child.sales_person = d.sales_person
|
child.sales_person = d.sales_person
|
||||||
|
child.completion_status = "Pending"
|
||||||
|
child.item_reference = d.name
|
||||||
|
|
||||||
|
@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])
|
||||||
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if not self.get('schedules'):
|
if not self.get('schedules'):
|
||||||
@ -58,9 +91,10 @@ class MaintenanceSchedule(TransactionBase):
|
|||||||
|
|
||||||
if no_email_sp:
|
if no_email_sp:
|
||||||
frappe.msgprint(
|
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)
|
self.owner, "<br>" + "<br>".join(no_email_sp)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
scheduled_date = frappe.db.sql("""select scheduled_date from
|
scheduled_date = frappe.db.sql("""select scheduled_date from
|
||||||
`tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and
|
`tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and
|
||||||
@ -106,7 +140,7 @@ class MaintenanceSchedule(TransactionBase):
|
|||||||
if employee:
|
if employee:
|
||||||
holiday_list = get_holiday_list_for_employee(employee)
|
holiday_list = get_holiday_list_for_employee(employee)
|
||||||
else:
|
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)
|
holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%s''', holiday_list)
|
||||||
|
|
||||||
@ -135,8 +169,7 @@ class MaintenanceSchedule(TransactionBase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if date_diff < days_in_period[d.periodicity]:
|
if date_diff < days_in_period[d.periodicity]:
|
||||||
throw(_("Row {0}: To set {1} periodicity, difference between from and to date \
|
throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}")
|
||||||
must be greater than or equal to {2}")
|
|
||||||
.format(d.idx, d.periodicity, days_in_period[d.periodicity]))
|
.format(d.idx, d.periodicity, days_in_period[d.periodicity]))
|
||||||
|
|
||||||
def validate_maintenance_detail(self):
|
def validate_maintenance_detail(self):
|
||||||
@ -166,13 +199,15 @@ class MaintenanceSchedule(TransactionBase):
|
|||||||
throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order))
|
throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order))
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_end_date_visits()
|
||||||
self.validate_maintenance_detail()
|
self.validate_maintenance_detail()
|
||||||
self.validate_dates_with_periodicity()
|
self.validate_dates_with_periodicity()
|
||||||
self.validate_sales_order()
|
self.validate_sales_order()
|
||||||
|
self.generate_schedule()
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
frappe.db.set(self, 'status', 'Draft')
|
frappe.db.set(self, 'status', 'Draft')
|
||||||
|
|
||||||
def update_amc_date(self, serial_nos, amc_expiry_date=None):
|
def update_amc_date(self, serial_nos, amc_expiry_date=None):
|
||||||
for serial_no in serial_nos:
|
for serial_no in serial_nos:
|
||||||
serial_no_doc = frappe.get_doc("Serial No", serial_no)
|
serial_no_doc = frappe.get_doc("Serial No", serial_no)
|
||||||
@ -202,8 +237,8 @@ class MaintenanceSchedule(TransactionBase):
|
|||||||
|
|
||||||
if not sr_details.warehouse and sr_details.delivery_date and \
|
if not sr_details.warehouse and sr_details.delivery_date and \
|
||||||
getdate(sr_details.delivery_date) >= getdate(amc_start_date):
|
getdate(sr_details.delivery_date) >= getdate(amc_start_date):
|
||||||
throw(_("Maintenance start date can not be before delivery date for Serial No {0}")
|
throw(_("Maintenance start date can not be before delivery date for Serial No {0}")
|
||||||
.format(serial_no))
|
.format(serial_no))
|
||||||
|
|
||||||
def validate_schedule(self):
|
def validate_schedule(self):
|
||||||
item_lst1 =[]
|
item_lst1 =[]
|
||||||
@ -245,13 +280,50 @@ class MaintenanceSchedule(TransactionBase):
|
|||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
delete_events(self.doctype, self.name)
|
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()
|
@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
|
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_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, {
|
doclist = get_mapped_doc("Maintenance Schedule", source_name, {
|
||||||
"Maintenance Schedule": {
|
"Maintenance Schedule": {
|
||||||
"doctype": "Maintenance Visit",
|
"doctype": "Maintenance Visit",
|
||||||
@ -261,15 +333,12 @@ def make_maintenance_visit(source_name, target_doc=None):
|
|||||||
"validation": {
|
"validation": {
|
||||||
"docstatus": ["=", 1]
|
"docstatus": ["=", 1]
|
||||||
},
|
},
|
||||||
"postprocess": update_status
|
"postprocess": update_status_and_detail
|
||||||
},
|
},
|
||||||
"Maintenance Schedule Item": {
|
"Maintenance Schedule Item": {
|
||||||
"doctype": "Maintenance Visit Purpose",
|
"doctype": "Maintenance Visit Purpose",
|
||||||
"field_map": {
|
"condition": lambda doc: doc.item_name == item_name,
|
||||||
"parent": "prevdoc_docname",
|
"postprocess": update_sales
|
||||||
"parenttype": "prevdoc_doctype",
|
|
||||||
"sales_person": "service_person"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, target_doc)
|
}, target_doc)
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
from __future__ import unicode_literals
|
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 frappe
|
||||||
import unittest
|
import unittest
|
||||||
@ -21,7 +22,57 @@ class TestMaintenanceSchedule(unittest.TestCase):
|
|||||||
ms.cancel()
|
ms.cancel()
|
||||||
events_after_cancel = get_events(ms)
|
events_after_cancel = get_events(ms)
|
||||||
self.assertTrue(len(events_after_cancel) == 0)
|
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):
|
def get_events(ms):
|
||||||
return frappe.get_all("Event Participants", filters={
|
return frappe.get_all("Event Participants", filters={
|
||||||
"reference_doctype": ms.doctype,
|
"reference_doctype": ms.doctype,
|
||||||
@ -33,12 +84,11 @@ def make_maintenance_schedule():
|
|||||||
ms = frappe.new_doc("Maintenance Schedule")
|
ms = frappe.new_doc("Maintenance Schedule")
|
||||||
ms.company = "_Test Company"
|
ms.company = "_Test Company"
|
||||||
ms.customer = "_Test Customer"
|
ms.customer = "_Test Customer"
|
||||||
ms.transaction_date = get_datetime()
|
ms.transaction_date = today()
|
||||||
|
|
||||||
ms.append("items", {
|
ms.append("items", {
|
||||||
"item_code": "_Test Item",
|
"item_code": "_Test Item",
|
||||||
"start_date": get_datetime(),
|
"start_date": today(),
|
||||||
"end_date": add_days(get_datetime(), 32),
|
|
||||||
"periodicity": "Weekly",
|
"periodicity": "Weekly",
|
||||||
"no_of_visits": 4,
|
"no_of_visits": 4,
|
||||||
"sales_person": "Sales Team",
|
"sales_person": "Sales Team",
|
||||||
|
@ -1,222 +1,137 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
"autoname": "hash",
|
||||||
"allow_rename": 0,
|
"creation": "2013-02-22 01:28:05",
|
||||||
"autoname": "hash",
|
"doctype": "DocType",
|
||||||
"beta": 0,
|
"document_type": "Document",
|
||||||
"creation": "2013-02-22 01:28:05",
|
"editable_grid": 1,
|
||||||
"custom": 0,
|
"engine": "InnoDB",
|
||||||
"docstatus": 0,
|
"field_order": [
|
||||||
"doctype": "DocType",
|
"item_code",
|
||||||
"document_type": "Document",
|
"item_name",
|
||||||
"editable_grid": 1,
|
"column_break_3",
|
||||||
"engine": "InnoDB",
|
"scheduled_date",
|
||||||
|
"actual_date",
|
||||||
|
"section_break_6",
|
||||||
|
"sales_person",
|
||||||
|
"column_break_8",
|
||||||
|
"completion_status",
|
||||||
|
"section_break_10",
|
||||||
|
"serial_no",
|
||||||
|
"item_reference"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"columns": 2,
|
||||||
"bold": 0,
|
"fieldname": "item_code",
|
||||||
"collapsible": 0,
|
"fieldtype": "Link",
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"fieldname": "item_code",
|
"label": "Item Code",
|
||||||
"fieldtype": "Link",
|
"oldfieldname": "item_code",
|
||||||
"hidden": 0,
|
"oldfieldtype": "Link",
|
||||||
"ignore_user_permissions": 0,
|
"options": "Item",
|
||||||
"ignore_xss_filter": 0,
|
"read_only": 1,
|
||||||
"in_filter": 0,
|
"search_index": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "item_name",
|
||||||
"bold": 0,
|
"fieldtype": "Data",
|
||||||
"collapsible": 0,
|
"in_global_search": 1,
|
||||||
"columns": 0,
|
"label": "Item Name",
|
||||||
"fieldname": "item_name",
|
"oldfieldname": "item_name",
|
||||||
"fieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"hidden": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"columns": 2,
|
||||||
"bold": 0,
|
"fieldname": "scheduled_date",
|
||||||
"collapsible": 0,
|
"fieldtype": "Date",
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"fieldname": "scheduled_date",
|
"label": "Scheduled Date",
|
||||||
"fieldtype": "Date",
|
"oldfieldname": "scheduled_date",
|
||||||
"hidden": 0,
|
"oldfieldtype": "Date",
|
||||||
"ignore_user_permissions": 0,
|
"reqd": 1,
|
||||||
"ignore_xss_filter": 0,
|
"search_index": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "actual_date",
|
||||||
"bold": 0,
|
"fieldtype": "Date",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Actual Date",
|
||||||
"fieldname": "actual_date",
|
"no_copy": 1,
|
||||||
"fieldtype": "Date",
|
"oldfieldname": "actual_date",
|
||||||
"hidden": 1,
|
"oldfieldtype": "Date",
|
||||||
"ignore_user_permissions": 0,
|
"print_hide": 1,
|
||||||
"ignore_xss_filter": 0,
|
"read_only": 1,
|
||||||
"in_filter": 0,
|
"report_hide": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 1,
|
||||||
"bold": 0,
|
"columns": 2,
|
||||||
"collapsible": 0,
|
"fieldname": "sales_person",
|
||||||
"columns": 0,
|
"fieldtype": "Link",
|
||||||
"fieldname": "sales_person",
|
"in_list_view": 1,
|
||||||
"fieldtype": "Link",
|
"label": "Sales Person",
|
||||||
"hidden": 0,
|
"oldfieldname": "incharge_name",
|
||||||
"ignore_user_permissions": 0,
|
"oldfieldtype": "Link",
|
||||||
"ignore_xss_filter": 0,
|
"options": "Sales Person",
|
||||||
"in_filter": 0,
|
"read_only_depends_on": "eval:doc.completion_status != \"Pending\""
|
||||||
"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": 0,
|
"fieldname": "serial_no",
|
||||||
"bold": 0,
|
"fieldtype": "Small Text",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Serial No",
|
||||||
"fieldname": "serial_no",
|
"oldfieldname": "serial_no",
|
||||||
"fieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"hidden": 0,
|
"print_width": "160px",
|
||||||
"ignore_user_permissions": 0,
|
"read_only": 1,
|
||||||
"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,
|
|
||||||
"width": "160px"
|
"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,
|
"idx": 1,
|
||||||
"hide_toolbar": 0,
|
"istable": 1,
|
||||||
"idx": 1,
|
"links": [],
|
||||||
"image_view": 0,
|
"modified": "2021-05-27 16:07:25.905015",
|
||||||
"in_create": 0,
|
"modified_by": "Administrator",
|
||||||
|
"module": "Maintenance",
|
||||||
"is_submittable": 0,
|
"name": "Maintenance Schedule Detail",
|
||||||
"issingle": 0,
|
"owner": "Administrator",
|
||||||
"istable": 1,
|
"permissions": [],
|
||||||
"max_attachments": 0,
|
"sort_field": "modified",
|
||||||
"modified": "2017-02-17 17:05:44.644663",
|
"sort_order": "DESC",
|
||||||
"modified_by": "Administrator",
|
"track_changes": 1
|
||||||
"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
|
|
||||||
}
|
}
|
@ -1,431 +1,160 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"autoname": "hash",
|
||||||
"allow_import": 0,
|
"creation": "2013-02-22 01:28:05",
|
||||||
"allow_rename": 0,
|
"doctype": "DocType",
|
||||||
"autoname": "hash",
|
"document_type": "Document",
|
||||||
"beta": 0,
|
"editable_grid": 1,
|
||||||
"creation": "2013-02-22 01:28:05",
|
"engine": "InnoDB",
|
||||||
"custom": 0,
|
"field_order": [
|
||||||
"docstatus": 0,
|
"item_code",
|
||||||
"doctype": "DocType",
|
"item_name",
|
||||||
"document_type": "Document",
|
"description",
|
||||||
"editable_grid": 1,
|
"column_break_4",
|
||||||
"engine": "InnoDB",
|
"start_date",
|
||||||
|
"end_date",
|
||||||
|
"periodicity",
|
||||||
|
"schedule_details",
|
||||||
|
"no_of_visits",
|
||||||
|
"column_break_10",
|
||||||
|
"sales_person",
|
||||||
|
"reference",
|
||||||
|
"serial_no",
|
||||||
|
"sales_order"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "item_code",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Item Code",
|
||||||
"fieldname": "item_code",
|
"oldfieldname": "item_code",
|
||||||
"fieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"hidden": 0,
|
"options": "Item",
|
||||||
"ignore_user_permissions": 0,
|
"reqd": 1,
|
||||||
"ignore_xss_filter": 0,
|
"search_index": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 1,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_from": "item_code.item_name",
|
"fetch_from": "item_code.item_name",
|
||||||
"fieldname": "item_name",
|
"fieldname": "item_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"in_global_search": 1,
|
||||||
"ignore_user_permissions": 0,
|
"in_list_view": 1,
|
||||||
"ignore_xss_filter": 0,
|
"label": "Item Name",
|
||||||
"in_filter": 0,
|
"oldfieldname": "item_name",
|
||||||
"in_global_search": 1,
|
"oldfieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"read_only": 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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_from": "item_code.description",
|
"fetch_from": "item_code.description",
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"hidden": 0,
|
"label": "Description",
|
||||||
"ignore_user_permissions": 0,
|
"oldfieldname": "description",
|
||||||
"ignore_xss_filter": 0,
|
"oldfieldtype": "Data",
|
||||||
"in_filter": 0,
|
"print_width": "300px",
|
||||||
"in_global_search": 0,
|
"read_only": 1,
|
||||||
"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,
|
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "schedule_details",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Section Break"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "start_date",
|
||||||
"bold": 0,
|
"fieldtype": "Date",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Start Date",
|
||||||
"fieldname": "start_date",
|
"oldfieldname": "start_date",
|
||||||
"fieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"hidden": 0,
|
"reqd": 1,
|
||||||
"ignore_user_permissions": 0,
|
"search_index": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "end_date",
|
||||||
"bold": 0,
|
"fieldtype": "Date",
|
||||||
"collapsible": 0,
|
"label": "End Date",
|
||||||
"columns": 0,
|
"oldfieldname": "end_date",
|
||||||
"fieldname": "end_date",
|
"oldfieldtype": "Date",
|
||||||
"fieldtype": "Date",
|
"reqd": 1,
|
||||||
"hidden": 0,
|
"search_index": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 1,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "periodicity",
|
||||||
"bold": 0,
|
"fieldtype": "Select",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Periodicity",
|
||||||
"fieldname": "periodicity",
|
"oldfieldname": "periodicity",
|
||||||
"fieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"hidden": 0,
|
"options": "\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly\nRandom"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 1,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "no_of_visits",
|
||||||
"bold": 0,
|
"fieldtype": "Int",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "No of Visits",
|
||||||
"fieldname": "no_of_visits",
|
"oldfieldname": "no_of_visits",
|
||||||
"fieldtype": "Int",
|
"oldfieldtype": "Int",
|
||||||
"hidden": 0,
|
"reqd": 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": "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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "sales_person",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"label": "Sales Person",
|
||||||
"collapsible": 0,
|
"oldfieldname": "incharge_name",
|
||||||
"columns": 0,
|
"oldfieldtype": "Link",
|
||||||
"fieldname": "sales_person",
|
"options": "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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "reference",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Section Break",
|
||||||
"bold": 0,
|
"label": "Reference"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "serial_no",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Small Text",
|
||||||
"bold": 0,
|
"label": "Serial No",
|
||||||
"collapsible": 0,
|
"oldfieldname": "serial_no",
|
||||||
"columns": 0,
|
"oldfieldtype": "Small Text"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "sales_order",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"label": "Sales Order",
|
||||||
"collapsible": 0,
|
"no_copy": 1,
|
||||||
"columns": 0,
|
"oldfieldname": "prevdoc_docname",
|
||||||
"fieldname": "sales_order",
|
"oldfieldtype": "Data",
|
||||||
"fieldtype": "Link",
|
"options": "Sales Order",
|
||||||
"hidden": 0,
|
"print_hide": 1,
|
||||||
"ignore_user_permissions": 0,
|
"print_width": "150px",
|
||||||
"ignore_xss_filter": 0,
|
"read_only": 1,
|
||||||
"in_filter": 0,
|
"search_index": 1,
|
||||||
"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,
|
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_10",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"idx": 1,
|
||||||
"hide_heading": 0,
|
"istable": 1,
|
||||||
"hide_toolbar": 0,
|
"links": [],
|
||||||
"idx": 1,
|
"modified": "2021-04-15 16:09:47.311994",
|
||||||
"image_view": 0,
|
"modified_by": "Administrator",
|
||||||
"in_create": 0,
|
"module": "Maintenance",
|
||||||
"is_submittable": 0,
|
"name": "Maintenance Schedule Item",
|
||||||
"issingle": 0,
|
"owner": "Administrator",
|
||||||
"istable": 1,
|
"permissions": [],
|
||||||
"max_attachments": 0,
|
"sort_field": "modified",
|
||||||
"modified": "2018-05-16 22:43:14.260729",
|
"sort_order": "DESC"
|
||||||
"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
|
|
||||||
}
|
}
|
@ -2,39 +2,62 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext.maintenance");
|
frappe.provide("erpnext.maintenance");
|
||||||
|
var serial_nos = [];
|
||||||
frappe.ui.form.on('Maintenance Visit', {
|
frappe.ui.form.on('Maintenance Visit', {
|
||||||
refresh: function(frm) {
|
refresh: function (frm) {
|
||||||
//filters for serial_no based on item_code
|
//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];
|
let item = locals[cdt][cdn];
|
||||||
return {
|
if (serial_nos) {
|
||||||
filters: {
|
return {
|
||||||
'item_code': item.item_code
|
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('contact_person', erpnext.queries.contact_query);
|
||||||
frm.set_query('customer_address', erpnext.queries.address_query);
|
frm.set_query('customer_address', erpnext.queries.address_query);
|
||||||
frm.set_query('customer', erpnext.queries.customer);
|
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) {
|
if (!frm.doc.status) {
|
||||||
frm.set_value({status:'Draft'});
|
frm.set_value({ status: 'Draft' });
|
||||||
}
|
}
|
||||||
if (frm.doc.__islocal) {
|
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);
|
erpnext.utils.get_party_details(frm);
|
||||||
},
|
},
|
||||||
customer_address: function(frm) {
|
customer_address: function (frm) {
|
||||||
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display');
|
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display');
|
||||||
},
|
},
|
||||||
contact_person: function(frm) {
|
contact_person: function (frm) {
|
||||||
erpnext.utils.get_contact_details(frm);
|
erpnext.utils.get_contact_details(frm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,14 +65,14 @@ frappe.ui.form.on('Maintenance Visit', {
|
|||||||
|
|
||||||
// TODO commonify this code
|
// TODO commonify this code
|
||||||
erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({
|
erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({
|
||||||
refresh: function() {
|
refresh: function () {
|
||||||
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
|
frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' };
|
||||||
|
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
if (this.frm.doc.docstatus===0) {
|
if (this.frm.doc.docstatus === 0) {
|
||||||
this.frm.add_custom_button(__('Maintenance Schedule'),
|
this.frm.add_custom_button(__('Maintenance Schedule'),
|
||||||
function() {
|
function () {
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
|
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
|
||||||
source_doctype: "Maintenance Schedule",
|
source_doctype: "Maintenance Schedule",
|
||||||
@ -64,7 +87,7 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({
|
|||||||
})
|
})
|
||||||
}, __("Get Items From"));
|
}, __("Get Items From"));
|
||||||
this.frm.add_custom_button(__('Warranty Claim'),
|
this.frm.add_custom_button(__('Warranty Claim'),
|
||||||
function() {
|
function () {
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
|
method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
|
||||||
source_doctype: "Warranty Claim",
|
source_doctype: "Warranty Claim",
|
||||||
@ -80,7 +103,7 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({
|
|||||||
})
|
})
|
||||||
}, __("Get Items From"));
|
}, __("Get Items From"));
|
||||||
this.frm.add_custom_button(__('Sales Order'),
|
this.frm.add_custom_button(__('Sales Order'),
|
||||||
function() {
|
function () {
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",
|
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",
|
||||||
source_doctype: "Sales Order",
|
source_doctype: "Sales Order",
|
||||||
@ -99,4 +122,4 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({frm: cur_frm}));
|
$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({ frm: cur_frm }));
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.utils import get_datetime
|
||||||
|
|
||||||
from erpnext.utilities.transaction_base import TransactionBase
|
from erpnext.utilities.transaction_base import TransactionBase
|
||||||
|
|
||||||
@ -16,44 +17,62 @@ class MaintenanceVisit(TransactionBase):
|
|||||||
if d.serial_no and not frappe.db.exists("Serial No", d.serial_no):
|
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))
|
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):
|
def validate(self):
|
||||||
self.validate_serial_no()
|
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):
|
def update_customer_issue(self, flag):
|
||||||
for d in self.get('purposes'):
|
if not self.maintenance_schedule:
|
||||||
if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' :
|
for d in self.get('purposes'):
|
||||||
if flag==1:
|
if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' :
|
||||||
mntc_date = self.mntc_date
|
if flag==1:
|
||||||
service_person = d.service_person
|
mntc_date = self.mntc_date
|
||||||
work_done = d.work_done
|
service_person = d.service_person
|
||||||
status = "Open"
|
work_done = d.work_done
|
||||||
if self.completion_status == 'Fully Completed':
|
status = "Open"
|
||||||
status = 'Closed'
|
if self.completion_status == 'Fully Completed':
|
||||||
elif self.completion_status == 'Partially Completed':
|
status = 'Closed'
|
||||||
status = 'Work In Progress'
|
elif self.completion_status == 'Partially Completed':
|
||||||
else:
|
status = 'Work In Progress'
|
||||||
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 ''
|
|
||||||
else:
|
else:
|
||||||
status = 'Open'
|
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))
|
||||||
mntc_date = None
|
|
||||||
service_person = None
|
|
||||||
work_done = None
|
|
||||||
|
|
||||||
wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname)
|
if nm:
|
||||||
wc_doc.update({
|
status = 'Work In Progress'
|
||||||
'resolution_date': mntc_date,
|
mntc_date = nm and nm[0][1] or ''
|
||||||
'resolved_by': service_person,
|
service_person = nm and nm[0][2] or ''
|
||||||
'resolution_details': work_done,
|
work_done = nm and nm[0][3] or ''
|
||||||
'status': status
|
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):
|
def check_if_last_visit(self):
|
||||||
"""check if last maintenance visit against same sales order/ Warranty Claim"""
|
"""check if last maintenance visit against same sales order/ Warranty Claim"""
|
||||||
@ -77,6 +96,8 @@ class MaintenanceVisit(TransactionBase):
|
|||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_customer_issue(1)
|
self.update_customer_issue(1)
|
||||||
frappe.db.set(self, 'status', 'Submitted')
|
frappe.db.set(self, 'status', 'Submitted')
|
||||||
|
self.update_completion_status()
|
||||||
|
self.update_actual_date()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.check_if_last_visit()
|
self.check_if_last_visit()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"creation": "2013-02-22 01:28:06",
|
"creation": "2013-02-22 01:28:06",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -8,14 +9,15 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"item_code",
|
"item_code",
|
||||||
"item_name",
|
"item_name",
|
||||||
|
"column_break_3",
|
||||||
|
"service_person",
|
||||||
"serial_no",
|
"serial_no",
|
||||||
|
"section_break_6",
|
||||||
"description",
|
"description",
|
||||||
"work_details",
|
"work_details",
|
||||||
"service_person",
|
|
||||||
"work_done",
|
"work_done",
|
||||||
"prevdoc_doctype",
|
"prevdoc_doctype",
|
||||||
"prevdoc_docname",
|
"prevdoc_docname"
|
||||||
"prevdoc_detail_docname"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -62,6 +64,8 @@
|
|||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "prevdoc_detail_docname.sales_person",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "service_person",
|
"fieldname": "service_person",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -83,49 +87,30 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "prevdoc_doctype",
|
"fieldname": "prevdoc_doctype",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"hidden": 1,
|
||||||
"label": "Document Type",
|
"label": "Document Type",
|
||||||
"no_copy": 1,
|
"options": "DocType"
|
||||||
"oldfieldname": "prevdoc_doctype",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"options": "DocType",
|
|
||||||
"print_hide": 1,
|
|
||||||
"print_width": "150px",
|
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 1,
|
|
||||||
"width": "150px"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "prevdoc_docname",
|
"fieldname": "prevdoc_docname",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
|
"hidden": 1,
|
||||||
"label": "Against Document No",
|
"label": "Against Document No",
|
||||||
"no_copy": 1,
|
"options": "prevdoc_doctype"
|
||||||
"oldfieldname": "prevdoc_docname",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"options": "prevdoc_doctype",
|
|
||||||
"print_hide": 1,
|
|
||||||
"print_width": "160px",
|
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 1,
|
|
||||||
"width": "160px"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "prevdoc_detail_docname",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 1,
|
},
|
||||||
"label": "Against Document Detail No",
|
{
|
||||||
"no_copy": 1,
|
"fieldname": "section_break_6",
|
||||||
"oldfieldname": "prevdoc_detail_docname",
|
"fieldtype": "Section Break"
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"print_hide": 1,
|
|
||||||
"print_width": "160px",
|
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 1,
|
|
||||||
"width": "160px"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
"links": [],
|
||||||
|
"modified": "2021-05-27 17:47:21.474282",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Maintenance",
|
"module": "Maintenance",
|
||||||
"name": "Maintenance Visit Purpose",
|
"name": "Maintenance Visit Purpose",
|
||||||
|
@ -211,16 +211,27 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
get_items: function(frm) {
|
get_items: function (frm) {
|
||||||
|
frm.clear_table('prod_plan_references');
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "get_items",
|
method: "get_items",
|
||||||
freeze: true,
|
freeze: true,
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
callback: function() {
|
callback: function () {
|
||||||
refresh_field('po_items');
|
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) {
|
get_items_for_mr: function(frm) {
|
||||||
if (!frm.doc.for_warehouse) {
|
if (!frm.doc.for_warehouse) {
|
||||||
|
@ -28,7 +28,10 @@
|
|||||||
"material_requests",
|
"material_requests",
|
||||||
"select_items_to_manufacture_section",
|
"select_items_to_manufacture_section",
|
||||||
"get_items",
|
"get_items",
|
||||||
|
"combine_items",
|
||||||
"po_items",
|
"po_items",
|
||||||
|
"section_break_25",
|
||||||
|
"prod_plan_references",
|
||||||
"material_request_planning",
|
"material_request_planning",
|
||||||
"include_non_stock_items",
|
"include_non_stock_items",
|
||||||
"include_subcontracted_items",
|
"include_subcontracted_items",
|
||||||
@ -316,13 +319,31 @@
|
|||||||
"fieldname": "include_safety_stock",
|
"fieldname": "include_safety_stock",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Include Safety Stock in Required Qty Calculation"
|
"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",
|
"icon": "fa fa-calendar",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-08 11:17:25.470147",
|
"modified": "2021-05-24 16:59:03.643211",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
|
@ -96,8 +96,10 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_items(self):
|
def get_items(self):
|
||||||
|
self.set('po_items', [])
|
||||||
if self.get_items_from == "Sales Order":
|
if self.get_items_from == "Sales Order":
|
||||||
self.get_so_items()
|
self.get_so_items()
|
||||||
|
|
||||||
elif self.get_items_from == "Material Request":
|
elif self.get_items_from == "Material Request":
|
||||||
self.get_mr_items()
|
self.get_mr_items()
|
||||||
|
|
||||||
@ -165,9 +167,31 @@ class ProductionPlan(Document):
|
|||||||
self.calculate_total_planned_qty()
|
self.calculate_total_planned_qty()
|
||||||
|
|
||||||
def add_items(self, items):
|
def add_items(self, items):
|
||||||
self.set('po_items', [])
|
refs = {}
|
||||||
for data in items:
|
for data in items:
|
||||||
item_details = get_item_details(data.item_code)
|
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', {
|
pi = self.append('po_items', {
|
||||||
'include_exploded_items': 1,
|
'include_exploded_items': 1,
|
||||||
'warehouse': data.warehouse,
|
'warehouse': data.warehouse,
|
||||||
@ -185,11 +209,28 @@ class ProductionPlan(Document):
|
|||||||
pi.sales_order = data.parent
|
pi.sales_order = data.parent
|
||||||
pi.sales_order_item = data.name
|
pi.sales_order_item = data.name
|
||||||
pi.description = data.description
|
pi.description = data.description
|
||||||
|
|
||||||
elif self.get_items_from == "Material Request":
|
elif self.get_items_from == "Material Request":
|
||||||
pi.material_request = data.parent
|
pi.material_request = data.parent
|
||||||
pi.material_request_item = data.name
|
pi.material_request_item = data.name
|
||||||
pi.description = data.description
|
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):
|
def calculate_total_planned_qty(self):
|
||||||
self.total_planned_qty = 0
|
self.total_planned_qty = 0
|
||||||
|
@ -100,7 +100,7 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
|
|
||||||
def test_production_plan_sales_orders(self):
|
def test_production_plan_sales_orders(self):
|
||||||
item = 'Test Production Item 1'
|
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 = so.name
|
||||||
sales_order_item = so.items[0].name
|
sales_order_item = so.items[0].name
|
||||||
|
|
||||||
@ -124,8 +124,8 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
|
|
||||||
wo_doc = frappe.get_doc('Work Order', work_order)
|
wo_doc = frappe.get_doc('Work Order', work_order)
|
||||||
wo_doc.update({
|
wo_doc.update({
|
||||||
'wip_warehouse': '_Test Warehouse 1 - _TC',
|
'wip_warehouse': 'Work In Progress - _TC',
|
||||||
'fg_warehouse': '_Test Warehouse - _TC'
|
'fg_warehouse': 'Finished Goods - _TC'
|
||||||
})
|
})
|
||||||
wo_doc.submit()
|
wo_doc.submit()
|
||||||
|
|
||||||
@ -145,6 +145,58 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(sales_orders, [])
|
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):
|
def test_pp_to_mr_customer_provided(self):
|
||||||
#Material Request from Production Plan for Customer Provided
|
#Material Request from Production Plan for Customer Provided
|
||||||
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
||||||
|
@ -1,792 +1,229 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
"autoname": "hash",
|
||||||
"allow_guest_to_view": 0,
|
"creation": "2013-02-22 01:27:49",
|
||||||
"allow_import": 0,
|
"doctype": "DocType",
|
||||||
"allow_rename": 0,
|
"editable_grid": 1,
|
||||||
"autoname": "hash",
|
"engine": "InnoDB",
|
||||||
"beta": 0,
|
"field_order": [
|
||||||
"creation": "2013-02-22 01:27:49",
|
"include_exploded_items",
|
||||||
"custom": 0,
|
"item_code",
|
||||||
"docstatus": 0,
|
"bom_no",
|
||||||
"doctype": "DocType",
|
"planned_qty",
|
||||||
"editable_grid": 1,
|
"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": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_in_quick_entry": 0,
|
"default": "0",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "include_exploded_items",
|
||||||
"bold": 0,
|
"fieldtype": "Check",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 2,
|
"label": "Include Exploded Items"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "item_code",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Item Code",
|
||||||
"columns": 2,
|
"oldfieldname": "item_code",
|
||||||
"fetch_if_empty": 0,
|
"oldfieldtype": "Link",
|
||||||
"fieldname": "item_code",
|
"options": "Item",
|
||||||
"fieldtype": "Link",
|
"print_width": "150px",
|
||||||
"hidden": 0,
|
"reqd": 1,
|
||||||
"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,
|
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 2,
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "bom_no",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "BOM No",
|
||||||
"columns": 2,
|
"oldfieldname": "bom_no",
|
||||||
"fetch_if_empty": 0,
|
"oldfieldtype": "Link",
|
||||||
"fieldname": "bom_no",
|
"options": "BOM",
|
||||||
"fieldtype": "Link",
|
"print_width": "100px",
|
||||||
"hidden": 0,
|
"reqd": 1,
|
||||||
"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,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "planned_qty",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Planned Qty",
|
||||||
"collapsible": 0,
|
"oldfieldname": "planned_qty",
|
||||||
"columns": 0,
|
"oldfieldtype": "Currency",
|
||||||
"fetch_if_empty": 0,
|
"print_width": "100px",
|
||||||
"fieldname": "planned_qty",
|
"reqd": 1,
|
||||||
"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,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_6",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"description": "If enabled, system will create the work order for the exploded items against which BOM is available.",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "make_work_order_for_sub_assembly_items",
|
||||||
"bold": 0,
|
"fieldtype": "Check",
|
||||||
"collapsible": 0,
|
"label": "Make Work Order for Sub Assembly Items"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "warehouse",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "For Warehouse",
|
||||||
"collapsible": 0,
|
"options": "Warehouse"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "Today",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "planned_start_date",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Datetime",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Planned Start Date",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "section_break_9",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Section Break",
|
||||||
"allow_on_submit": 0,
|
"label": "Quantity and Description"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "pending_qty",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Float",
|
||||||
"bold": 0,
|
"label": "Pending Qty",
|
||||||
"collapsible": 0,
|
"oldfieldname": "prevdoc_reqd_qty",
|
||||||
"columns": 0,
|
"oldfieldtype": "Currency",
|
||||||
"default": "0",
|
"print_width": "100px",
|
||||||
"fetch_if_empty": 0,
|
"read_only": 1,
|
||||||
"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,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "ordered_qty",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Float",
|
||||||
"bold": 0,
|
"label": "Ordered Qty",
|
||||||
"collapsible": 0,
|
"print_hide": 1,
|
||||||
"columns": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "produced_qty",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Float",
|
||||||
"bold": 0,
|
"label": "Produced Qty",
|
||||||
"collapsible": 0,
|
"no_copy": 1,
|
||||||
"columns": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_17",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "description",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Text Editor",
|
||||||
"allow_on_submit": 0,
|
"label": "Description",
|
||||||
"bold": 0,
|
"oldfieldname": "description",
|
||||||
"collapsible": 0,
|
"oldfieldtype": "Text",
|
||||||
"columns": 0,
|
"print_width": "200px",
|
||||||
"fetch_if_empty": 0,
|
"read_only": 1,
|
||||||
"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,
|
|
||||||
"width": "200px"
|
"width": "200px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "stock_uom",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "UOM",
|
||||||
"bold": 0,
|
"oldfieldname": "stock_uom",
|
||||||
"collapsible": 0,
|
"oldfieldtype": "Data",
|
||||||
"columns": 0,
|
"options": "UOM",
|
||||||
"fetch_if_empty": 0,
|
"print_width": "80px",
|
||||||
"fieldname": "stock_uom",
|
"read_only": 1,
|
||||||
"fieldtype": "Link",
|
"reqd": 1,
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "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,
|
|
||||||
"width": "80px"
|
"width": "80px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "reference_section",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Section Break",
|
||||||
"allow_on_submit": 0,
|
"label": "Reference"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "sales_order",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Sales Order",
|
||||||
"bold": 0,
|
"oldfieldname": "source_docname",
|
||||||
"collapsible": 0,
|
"oldfieldtype": "Data",
|
||||||
"columns": 0,
|
"options": "Sales Order",
|
||||||
"fetch_if_empty": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "sales_order_item",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Data",
|
||||||
"allow_on_submit": 0,
|
"hidden": 1,
|
||||||
"bold": 0,
|
"label": "Sales Order Item",
|
||||||
"collapsible": 0,
|
"no_copy": 1,
|
||||||
"columns": 0,
|
"print_hide": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_19",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "material_request",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Material Request",
|
||||||
"bold": 0,
|
"options": "Material Request",
|
||||||
"collapsible": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "material_request_item",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Data",
|
||||||
"allow_on_submit": 0,
|
"hidden": 1,
|
||||||
"bold": 0,
|
"label": "material_request_item"
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "product_bundle_item",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Product Bundle Item",
|
||||||
"bold": 0,
|
"no_copy": 1,
|
||||||
"collapsible": 0,
|
"options": "Item",
|
||||||
"columns": 0,
|
"print_hide": 1,
|
||||||
"fetch_if_empty": 0,
|
"read_only": 1
|
||||||
"fieldname": "product_bundle_item",
|
},
|
||||||
"fieldtype": "Link",
|
{
|
||||||
"hidden": 0,
|
"fieldname": "item_reference",
|
||||||
"ignore_user_permissions": 0,
|
"fieldtype": "Data",
|
||||||
"ignore_xss_filter": 0,
|
"hidden": 1,
|
||||||
"in_filter": 0,
|
"label": "Item Reference"
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"idx": 1,
|
||||||
"hide_toolbar": 0,
|
"istable": 1,
|
||||||
"idx": 1,
|
"links": [],
|
||||||
"in_create": 0,
|
"modified": "2021-04-28 19:14:57.772123",
|
||||||
"is_submittable": 0,
|
"modified_by": "Administrator",
|
||||||
"issingle": 0,
|
"module": "Manufacturing",
|
||||||
"istable": 1,
|
"name": "Production Plan Item",
|
||||||
"max_attachments": 0,
|
"owner": "Administrator",
|
||||||
"modified": "2019-04-08 23:09:57.199423",
|
"permissions": [],
|
||||||
"modified_by": "Administrator",
|
"sort_field": "modified",
|
||||||
"module": "Manufacturing",
|
"sort_order": "ASC"
|
||||||
"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
|
|
||||||
}
|
}
|
@ -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
|
||||||
|
}
|
@ -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
|
@ -76,9 +76,9 @@ frappe.ui.form.on("Work Order", {
|
|||||||
frm.set_query("production_item", function() {
|
frm.set_query("production_item", function() {
|
||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters:[
|
filters: {
|
||||||
['is_stock_item', '=',1]
|
"is_stock_item": 1,
|
||||||
]
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -240,8 +240,12 @@ class WorkOrder(Document):
|
|||||||
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
|
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
|
||||||
if not self.fg_warehouse:
|
if not self.fg_warehouse:
|
||||||
frappe.throw(_("For Warehouse is required before Submit"))
|
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_reserved_qty_for_production()
|
||||||
self.update_completed_qty_in_material_request()
|
self.update_completed_qty_in_material_request()
|
||||||
self.update_planned_qty()
|
self.update_planned_qty()
|
||||||
@ -250,9 +254,13 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.validate_cancel()
|
self.validate_cancel()
|
||||||
|
|
||||||
frappe.db.set(self,'status', 'Cancelled')
|
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.delete_job_card()
|
||||||
self.update_completed_qty_in_material_request()
|
self.update_completed_qty_in_material_request()
|
||||||
self.update_planned_qty()
|
self.update_planned_qty()
|
||||||
@ -357,7 +365,28 @@ class WorkOrder(Document):
|
|||||||
work_order_qty = qty[0][0] if qty and qty[0][0] else 0
|
work_order_qty = qty[0][0] if qty and qty[0][0] else 0
|
||||||
frappe.db.set_value('Sales Order Item',
|
frappe.db.set_value('Sales Order Item',
|
||||||
self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2))
|
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):
|
def update_completed_qty_in_material_request(self):
|
||||||
if self.material_request:
|
if self.material_request:
|
||||||
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
|
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
|
||||||
|
@ -28,7 +28,7 @@ class Member(Document):
|
|||||||
def setup_subscription(self):
|
def setup_subscription(self):
|
||||||
non_profit_settings = frappe.get_doc('Non Profit Settings')
|
non_profit_settings = frappe.get_doc('Non Profit Settings')
|
||||||
if not non_profit_settings.enable_razorpay_for_memberships:
|
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'))
|
get_link_to_form('Non Profit Settings', 'Non Profit Settings'))
|
||||||
|
|
||||||
controller = get_payment_gateway_controller("Razorpay")
|
controller = get_payment_gateway_controller("Razorpay")
|
||||||
|
@ -779,5 +779,7 @@ erpnext.patches.v12_0.add_ewaybill_validity_field
|
|||||||
erpnext.patches.v13_0.germany_make_custom_fields
|
erpnext.patches.v13_0.germany_make_custom_fields
|
||||||
erpnext.patches.v13_0.germany_fill_debtor_creditor_number
|
erpnext.patches.v13_0.germany_fill_debtor_creditor_number
|
||||||
erpnext.patches.v13_0.set_pos_closing_as_failed
|
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.update_timesheet_changes
|
||||||
erpnext.patches.v13_0.set_training_event_attendance
|
erpnext.patches.v13_0.set_training_event_attendance
|
||||||
|
erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
|
||||||
|
@ -19,6 +19,9 @@ def execute():
|
|||||||
logger.info("purchase_receipt_status: begin patch, PR count: {}"
|
logger.info("purchase_receipt_status: begin patch, PR count: {}"
|
||||||
.format(len(affected_purchase_receipts)))
|
.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:
|
for pr in affected_purchase_receipts:
|
||||||
pr_name = pr[0]
|
pr_name = pr[0]
|
||||||
|
20
erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
Normal file
20
erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
Normal file
@ -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'
|
||||||
|
""")
|
@ -7,25 +7,30 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"employee_details_section",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"employee",
|
"employee",
|
||||||
"employee_name",
|
"employee_name",
|
||||||
"salary_component",
|
|
||||||
"type",
|
|
||||||
"amount",
|
|
||||||
"ref_doctype",
|
|
||||||
"ref_docname",
|
|
||||||
"amended_from",
|
|
||||||
"column_break_5",
|
"column_break_5",
|
||||||
"company",
|
"company",
|
||||||
"department",
|
"department",
|
||||||
|
"salary_details_section",
|
||||||
|
"salary_component",
|
||||||
|
"type",
|
||||||
"currency",
|
"currency",
|
||||||
|
"amount",
|
||||||
|
"column_break_13",
|
||||||
|
"is_recurring",
|
||||||
|
"payroll_date",
|
||||||
"from_date",
|
"from_date",
|
||||||
"to_date",
|
"to_date",
|
||||||
"payroll_date",
|
"properties_and_references_section",
|
||||||
"is_recurring",
|
"deduct_full_tax_on_selected_payroll_date",
|
||||||
|
"ref_doctype",
|
||||||
|
"ref_docname",
|
||||||
|
"column_break_22",
|
||||||
"overwrite_salary_structure_amount",
|
"overwrite_salary_structure_amount",
|
||||||
"deduct_full_tax_on_selected_payroll_date"
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -81,7 +86,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:(doc.is_recurring==0)",
|
"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",
|
"fieldname": "payroll_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -159,6 +164,7 @@
|
|||||||
"fieldname": "ref_docname",
|
"fieldname": "ref_docname",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"label": "Reference Document",
|
"label": "Reference Document",
|
||||||
|
"no_copy": 1,
|
||||||
"options": "ref_doctype",
|
"options": "ref_doctype",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -171,11 +177,34 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 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,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-31 22:33:59.098532",
|
"modified": "2021-05-26 11:10:00.812698",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Payroll",
|
"module": "Payroll",
|
||||||
"name": "Additional Salary",
|
"name": "Additional Salary",
|
||||||
|
@ -15,7 +15,13 @@ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_
|
|||||||
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.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
|
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):
|
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):
|
def setUp(self):
|
||||||
for dt in ["Salary Slip", "Salary Component", "Salary Component Account",
|
for dt in ["Salary Slip", "Salary Component", "Salary Component Account",
|
||||||
"Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]:
|
"Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]:
|
||||||
|
@ -261,11 +261,19 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
|
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
|
||||||
return;
|
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";
|
? "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) {
|
quality_inspection_field.get_route_options_for_new_doc = function(row) {
|
||||||
if(me.frm.is_new()) return;
|
if(me.frm.is_new()) return;
|
||||||
return {
|
return {
|
||||||
@ -280,7 +288,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) {
|
this.frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) {
|
||||||
var d = locals[cdt][cdn];
|
let d = locals[cdt][cdn];
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
@ -953,15 +961,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
(this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length)) {
|
(this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length)) {
|
||||||
var message1 = "";
|
var message1 = "";
|
||||||
var message2 = "";
|
var message2 = "";
|
||||||
var final_message = "Please clear the ";
|
var final_message = __("Please clear the") + " ";
|
||||||
|
|
||||||
if (this.frm.doc.payment_terms_template) {
|
if (this.frm.doc.payment_terms_template) {
|
||||||
message1 = "selected Payment Terms Template";
|
message1 = __("selected Payment Terms Template");
|
||||||
final_message = final_message + message1;
|
final_message = final_message + message1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((this.frm.doc.payment_schedule || []).length) {
|
if ((this.frm.doc.payment_schedule || []).length) {
|
||||||
message2 = "Payment Schedule Table";
|
message2 = __("Payment Schedule Table");
|
||||||
if (message1.length !== 0) message2 = " and " + message2;
|
if (message1.length !== 0) message2 = " and " + message2;
|
||||||
final_message = final_message + message2;
|
final_message = final_message + message2;
|
||||||
}
|
}
|
||||||
@ -1949,6 +1957,130 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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(){
|
get_method_for_payment: function(){
|
||||||
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
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){
|
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
|
||||||
|
@ -644,14 +644,14 @@ frappe.help.help_links["List/Payment Request"] = [
|
|||||||
frappe.help.help_links["List/Asset"] = [
|
frappe.help.help_links["List/Asset"] = [
|
||||||
{
|
{
|
||||||
label: "Managing Fixed Assets",
|
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"] = [
|
frappe.help.help_links["List/Asset Category"] = [
|
||||||
{
|
{
|
||||||
label: "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 @@ frappe.help.help_links["List/Item"] = [
|
|||||||
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
|
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
|
||||||
{
|
{
|
||||||
label: "Item Price",
|
label: "Item Price",
|
||||||
url: docsUrl + "user/manual/en/stock/item/item-price",
|
url: docsUrl + "user/manual/en/stock/item-price",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Barcode",
|
label: "Barcode",
|
||||||
@ -672,25 +672,25 @@ frappe.help.help_links["List/Item"] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Item Wise Taxation",
|
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",
|
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",
|
label: "Item Codification",
|
||||||
url: docsUrl + "user/manual/en/stock/item/item-codification",
|
url: docsUrl + "user/manual/en/stock/articles/item-codification",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Item Variants",
|
label: "Item Variants",
|
||||||
url: docsUrl + "user/manual/en/stock/item/item-variants",
|
url: docsUrl + "user/manual/en/stock/item-variants",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Item Valuation",
|
label: "Item Valuation",
|
||||||
url:
|
url:
|
||||||
docsUrl +
|
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 @@ frappe.help.help_links["Form/Item"] = [
|
|||||||
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
|
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
|
||||||
{
|
{
|
||||||
label: "Item Price",
|
label: "Item Price",
|
||||||
url: docsUrl + "user/manual/en/stock/item/item-price",
|
url: docsUrl + "user/manual/en/stock/item-price",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Barcode",
|
label: "Barcode",
|
||||||
@ -707,19 +707,19 @@ frappe.help.help_links["Form/Item"] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Item Wise Taxation",
|
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",
|
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",
|
label: "Item Codification",
|
||||||
url: docsUrl + "user/manual/en/stock/item/item-codification",
|
url: docsUrl + "user/manual/en/stock/articles/item-codification",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Item Variants",
|
label: "Item Variants",
|
||||||
url: docsUrl + "user/manual/en/stock/item/item-variants",
|
url: docsUrl + "user/manual/en/stock/item-variants",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Item Valuation",
|
label: "Item Valuation",
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
@import "frappe/public/scss/desk/variables";
|
|
||||||
@import "frappe/public/scss/common/mixins";
|
@import "frappe/public/scss/common/mixins";
|
||||||
|
|
||||||
body.product-page {
|
body.product-page {
|
||||||
@ -74,15 +73,6 @@ body.product-page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// .card-body {
|
|
||||||
// text-align: center;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// .featured-item {
|
|
||||||
// .card-body {
|
|
||||||
// text-align: left;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
.card-img-container {
|
.card-img-container {
|
||||||
height: 210px;
|
height: 210px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -217,12 +207,12 @@ body.product-page {
|
|||||||
border-color: var(--table-border-color) !important;
|
border-color: var(--table-border-color) !important;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
||||||
@include media-breakpoint-between(xs, md) {
|
@media (max-width: var(--md-width)) {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
@media (min-width: var(--lg-width)) {
|
||||||
height: 350px;
|
height: 350px;
|
||||||
width: 350px;
|
width: 350px;
|
||||||
}
|
}
|
||||||
@ -233,11 +223,12 @@ body.product-page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item-slideshow {
|
.item-slideshow {
|
||||||
@include media-breakpoint-between(xs, md) {
|
|
||||||
|
@media (max-width: var(--md-width)) {
|
||||||
max-height: 320px;
|
max-height: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
@media (min-width: var(--lg-width)) {
|
||||||
max-height: 430px;
|
max-height: 430px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +245,7 @@ body.product-page {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover, &.active {
|
&:hover, &.active {
|
||||||
border-color: $primary;
|
border-color: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,12 +307,9 @@ body.product-page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item-group-slideshow {
|
.item-group-slideshow {
|
||||||
.item-group-description {
|
|
||||||
// max-width: 900px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-inner.rounded-carousel {
|
.carousel-inner.rounded-carousel {
|
||||||
border-radius: $card-border-radius;
|
border-radius: var(--card-border-radius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
@import "frappe/public/scss/website/variables";
|
|
||||||
|
|
||||||
.filter-options {
|
.filter-options {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
@ -14,7 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-color: $primary;
|
border-color: var(--primary);
|
||||||
|
|
||||||
.check {
|
.check {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -25,7 +24,7 @@
|
|||||||
.check {
|
.check {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
background: $primary;
|
background: var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -38,12 +37,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
border-bottom: 1px solid $border-color;
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transaction-list-item {
|
.transaction-list-item {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
border-top: 1px solid $border-color;
|
border-top: 1px solid var(--border-color);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
a.transaction-item-link {
|
a.transaction-item-link {
|
||||||
|
@ -310,7 +310,7 @@ class GSTR3BReport(Document):
|
|||||||
self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
|
self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
|
||||||
|
|
||||||
gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category')
|
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 \
|
if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \
|
||||||
self.gst_details.get("gst_state") != place_of_supply.split("-")[1]:
|
self.gst_details.get("gst_state") != place_of_supply.split("-")[1]:
|
||||||
|
@ -500,7 +500,7 @@ def download_ewb_json():
|
|||||||
|
|
||||||
if not isinstance(docname, list):
|
if not isinstance(docname, list):
|
||||||
# removes characters not allowed in a filename (https://stackoverflow.com/a/38766141/4767738)
|
# 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))
|
frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(filename_prefix, frappe.utils.random_string(5))
|
||||||
|
|
||||||
|
@ -574,7 +574,7 @@ class Gstr1Report(object):
|
|||||||
def get_json(filters, report_name, data):
|
def get_json(filters, report_name, data):
|
||||||
filters = json.loads(filters)
|
filters = json.loads(filters)
|
||||||
report_data = json.loads(data)
|
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)
|
fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
|
||||||
|
|
||||||
@ -810,23 +810,29 @@ def get_rate_and_tax_details(row, gstin):
|
|||||||
|
|
||||||
return {"num": int(num), "itm_det": itm_det}
|
return {"num": int(num), "itm_det": itm_det}
|
||||||
|
|
||||||
def get_company_gstin_number(company):
|
def get_company_gstin_number(company, address=None):
|
||||||
filters = [
|
if address:
|
||||||
["is_your_company_address", "=", 1],
|
gstin = frappe.db.get_value("Address", address, "gstin")
|
||||||
["Dynamic Link", "link_doctype", "=", "Company"],
|
|
||||||
["Dynamic Link", "link_name", "=", company],
|
|
||||||
["Dynamic Link", "parenttype", "=", "Address"],
|
|
||||||
]
|
|
||||||
|
|
||||||
gstin = frappe.get_all("Address", filters=filters, fields=["gstin"])
|
if not gstin:
|
||||||
|
filters = [
|
||||||
if gstin:
|
["is_your_company_address", "=", 1],
|
||||||
return gstin[0]["gstin"]
|
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||||
else:
|
["Dynamic Link", "link_name", "=", company],
|
||||||
frappe.throw(_("Please set valid GSTIN No. in Company Address for company {0}").format(
|
["Dynamic Link", "parenttype", "=", "Address"],
|
||||||
frappe.bold(company)
|
]
|
||||||
|
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()
|
@frappe.whitelist()
|
||||||
def download_json_file():
|
def download_json_file():
|
||||||
''' download json content in a file '''
|
''' download json content in a file '''
|
||||||
|
@ -8,39 +8,52 @@ from frappe.utils import cint
|
|||||||
from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups
|
from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups
|
||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability
|
||||||
|
|
||||||
from six import string_types
|
def search_by_term(search_term, warehouse, price_list):
|
||||||
|
result = search_for_serial_or_batch_or_barcode_number(search_term)
|
||||||
|
|
||||||
|
item_code = result.get("item_code") or search_term
|
||||||
|
serial_no = result.get("serial_no") or ""
|
||||||
|
batch_no = result.get("batch_no") or ""
|
||||||
|
barcode = result.get("barcode") or ""
|
||||||
|
|
||||||
|
if result:
|
||||||
|
item_info = frappe.db.get_value("Item", item_code,
|
||||||
|
["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"],
|
||||||
|
as_dict=1)
|
||||||
|
|
||||||
|
item_stock_qty = get_stock_availability(item_code, warehouse)
|
||||||
|
price_list_rate, currency = frappe.db.get_value('Item Price', {
|
||||||
|
'price_list': price_list,
|
||||||
|
'item_code': item_code
|
||||||
|
}, ["price_list_rate", "currency"])
|
||||||
|
|
||||||
|
item_info.update({
|
||||||
|
'serial_no': serial_no,
|
||||||
|
'batch_no': batch_no,
|
||||||
|
'barcode': barcode,
|
||||||
|
'price_list_rate': price_list_rate,
|
||||||
|
'currency': currency,
|
||||||
|
'actual_qty': item_stock_qty
|
||||||
|
})
|
||||||
|
|
||||||
|
return {'items': [item_info]}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_items(start, page_length, price_list, item_group, pos_profile, search_value=""):
|
def get_items(start, page_length, price_list, item_group, pos_profile, search_term=""):
|
||||||
data = dict()
|
warehouse, hide_unavailable_items = frappe.db.get_value(
|
||||||
|
'POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items'])
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
|
if search_term:
|
||||||
warehouse, hide_unavailable_items = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items'])
|
result = search_by_term(search_term, warehouse, price_list)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
if not frappe.db.exists('Item Group', item_group):
|
if not frappe.db.exists('Item Group', item_group):
|
||||||
item_group = get_root_of('Item Group')
|
item_group = get_root_of('Item Group')
|
||||||
|
|
||||||
if search_value:
|
condition = get_conditions(search_term)
|
||||||
data = search_serial_or_batch_or_barcode_number(search_value)
|
|
||||||
|
|
||||||
item_code = data.get("item_code") if data.get("item_code") else search_value
|
|
||||||
serial_no = data.get("serial_no") if data.get("serial_no") else ""
|
|
||||||
batch_no = data.get("batch_no") if data.get("batch_no") else ""
|
|
||||||
barcode = data.get("barcode") if data.get("barcode") else ""
|
|
||||||
|
|
||||||
if data:
|
|
||||||
item_info = frappe.db.get_value(
|
|
||||||
"Item", data.get("item_code"),
|
|
||||||
["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"]
|
|
||||||
, as_dict=1)
|
|
||||||
item_info.setdefault('serial_no', serial_no)
|
|
||||||
item_info.setdefault('batch_no', batch_no)
|
|
||||||
item_info.setdefault('barcode', barcode)
|
|
||||||
|
|
||||||
return { 'items': [item_info] }
|
|
||||||
|
|
||||||
condition = get_conditions(item_code, serial_no, batch_no, barcode)
|
|
||||||
condition += get_item_group_condition(pos_profile)
|
condition += get_item_group_condition(pos_profile)
|
||||||
|
|
||||||
lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt'])
|
lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt'])
|
||||||
@ -62,7 +75,6 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va
|
|||||||
`tabItem` item {bin_join_selection}
|
`tabItem` item {bin_join_selection}
|
||||||
WHERE
|
WHERE
|
||||||
item.disabled = 0
|
item.disabled = 0
|
||||||
AND item.is_stock_item = 1
|
|
||||||
AND item.has_variants = 0
|
AND item.has_variants = 0
|
||||||
AND item.is_sales_item = 1
|
AND item.is_sales_item = 1
|
||||||
AND item.is_fixed_asset = 0
|
AND item.is_fixed_asset = 0
|
||||||
@ -84,6 +96,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va
|
|||||||
), {'warehouse': warehouse}, as_dict=1)
|
), {'warehouse': warehouse}, as_dict=1)
|
||||||
|
|
||||||
if items_data:
|
if items_data:
|
||||||
|
items_data = filter_service_items(items_data)
|
||||||
items = [d.item_code for d in items_data]
|
items = [d.item_code for d in items_data]
|
||||||
item_prices_data = frappe.get_all("Item Price",
|
item_prices_data = frappe.get_all("Item Price",
|
||||||
fields = ["item_code", "price_list_rate", "currency"],
|
fields = ["item_code", "price_list_rate", "currency"],
|
||||||
@ -96,10 +109,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va
|
|||||||
for item in items_data:
|
for item in items_data:
|
||||||
item_code = item.item_code
|
item_code = item.item_code
|
||||||
item_price = item_prices.get(item_code) or {}
|
item_price = item_prices.get(item_code) or {}
|
||||||
if allow_negative_stock:
|
item_stock_qty = get_stock_availability(item_code, warehouse)
|
||||||
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)
|
|
||||||
|
|
||||||
row = {}
|
row = {}
|
||||||
row.update(item)
|
row.update(item)
|
||||||
@ -110,14 +120,10 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va
|
|||||||
})
|
})
|
||||||
result.append(row)
|
result.append(row)
|
||||||
|
|
||||||
res = {
|
return {'items': result}
|
||||||
'items': result
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def search_serial_or_batch_or_barcode_number(search_value):
|
def search_for_serial_or_batch_or_barcode_number(search_value):
|
||||||
# search barcode no
|
# search barcode no
|
||||||
barcode_data = frappe.db.get_value('Item Barcode', {'barcode': search_value}, ['barcode', 'parent as item_code'], as_dict=True)
|
barcode_data = frappe.db.get_value('Item Barcode', {'barcode': search_value}, ['barcode', 'parent as item_code'], as_dict=True)
|
||||||
if barcode_data:
|
if barcode_data:
|
||||||
@ -135,27 +141,29 @@ def search_serial_or_batch_or_barcode_number(search_value):
|
|||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_conditions(item_code, serial_no, batch_no, barcode):
|
def filter_service_items(items):
|
||||||
if serial_no or batch_no or barcode:
|
for item in items:
|
||||||
return "item.name = {0}".format(frappe.db.escape(item_code))
|
if not item['is_stock_item']:
|
||||||
|
if not frappe.db.exists('Product Bundle', item['item_code']):
|
||||||
|
items.remove(item)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
return make_condition(item_code)
|
def get_conditions(search_term):
|
||||||
|
|
||||||
def make_condition(item_code):
|
|
||||||
condition = "("
|
condition = "("
|
||||||
condition += """item.name like {item_code}
|
condition += """item.name like {search_term}
|
||||||
or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%'))
|
or item.item_name like {search_term}""".format(search_term=frappe.db.escape('%' + search_term + '%'))
|
||||||
condition += add_search_fields_condition(item_code)
|
condition += add_search_fields_condition(search_term)
|
||||||
condition += ")"
|
condition += ")"
|
||||||
|
|
||||||
return condition
|
return condition
|
||||||
|
|
||||||
def add_search_fields_condition(item_code):
|
def add_search_fields_condition(search_term):
|
||||||
condition = ''
|
condition = ''
|
||||||
search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname'])
|
search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname'])
|
||||||
if search_fields:
|
if search_fields:
|
||||||
for field in search_fields:
|
for field in search_fields:
|
||||||
condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%'))
|
condition += " or item.`{0}` like {1}".format(field['fieldname'], frappe.db.escape('%' + search_term + '%'))
|
||||||
return condition
|
return condition
|
||||||
|
|
||||||
def get_item_group_condition(pos_profile):
|
def get_item_group_condition(pos_profile):
|
||||||
|
@ -241,10 +241,8 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
events: {
|
events: {
|
||||||
get_frm: () => this.frm,
|
get_frm: () => this.frm,
|
||||||
|
|
||||||
cart_item_clicked: (item_code, batch_no, uom) => {
|
cart_item_clicked: (item_code, batch_no, uom, rate) => {
|
||||||
const search_field = batch_no ? 'batch_no' : 'item_code';
|
const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
|
||||||
const search_value = batch_no || item_code;
|
|
||||||
const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom);
|
|
||||||
this.item_details.toggle_item_details_section(item_row);
|
this.item_details.toggle_item_details_section(item_row);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -275,18 +273,25 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
this.cart.toggle_numpad(minimize);
|
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);
|
const item_row = frappe.model.get_doc(cdt, cdn);
|
||||||
if (item_row && item_row[fieldname] != value) {
|
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 = {
|
const event = {
|
||||||
field: fieldname,
|
field: fieldname,
|
||||||
value,
|
value,
|
||||||
item: { item_code, batch_no, uom }
|
item: { item_code, batch_no, uom, rate }
|
||||||
}
|
}
|
||||||
return this.on_cart_update(event)
|
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) => {
|
item_field_focused: (fieldname) => {
|
||||||
@ -501,8 +506,8 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
let item_row = undefined;
|
let item_row = undefined;
|
||||||
try {
|
try {
|
||||||
let { field, value, item } = args;
|
let { field, value, item } = args;
|
||||||
const { item_code, batch_no, serial_no, uom } = item;
|
const { item_code, batch_no, serial_no, uom, rate } = item;
|
||||||
item_row = this.get_item_from_frm(item_code, batch_no, uom);
|
item_row = this.get_item_from_frm(item_code, batch_no, uom, rate);
|
||||||
|
|
||||||
const item_selected_from_selector = field === 'qty' && value === "+1"
|
const item_selected_from_selector = field === 'qty' && value === "+1"
|
||||||
|
|
||||||
@ -535,7 +540,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
|
|
||||||
item_selected_from_selector && (value = flt(value))
|
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) {
|
if (serial_no) {
|
||||||
await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
|
await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
|
||||||
@ -550,9 +555,11 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
|
await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
|
||||||
|
|
||||||
await this.trigger_new_item_events(item_row);
|
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.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) {
|
} catch (error) {
|
||||||
@ -563,12 +570,13 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
const has_batch_no = batch_no;
|
||||||
return this.frm.doc.items.find(
|
return this.frm.doc.items.find(
|
||||||
i => i.item_code === item_code
|
i => i.item_code === item_code
|
||||||
&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
|
&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
|
||||||
&& (i.uom === uom)
|
&& (i.uom === uom)
|
||||||
|
&& (i.rate == rate)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +184,8 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
const item_code = unescape($cart_item.attr('data-item-code'));
|
const item_code = unescape($cart_item.attr('data-item-code'));
|
||||||
const batch_no = unescape($cart_item.attr('data-batch-no'));
|
const batch_no = unescape($cart_item.attr('data-batch-no'));
|
||||||
const uom = unescape($cart_item.attr('data-uom'));
|
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 = '';
|
this.numpad_value = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -520,28 +521,34 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
|
||||||
const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
|
const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
|
||||||
const uom_attr = `[data-uom="${escape(uom)}"]`;
|
const uom_attr = `[data-uom="${escape(uom)}"]`;
|
||||||
|
const rate_attr = `[data-rate="${escape(rate)}"]`;
|
||||||
|
|
||||||
const item_selector = batch_no ?
|
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);
|
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) {
|
update_item_html(item, remove_item) {
|
||||||
const $item = this.get_cart_item(item);
|
const $item = this.get_cart_item(item);
|
||||||
|
|
||||||
if (remove_item) {
|
if (remove_item) {
|
||||||
$item && $item.next().remove() && $item.remove();
|
$item && $item.next().remove() && $item.remove();
|
||||||
} else {
|
} else {
|
||||||
const { item_code, batch_no, uom } = item;
|
const item_row = this.get_item_from_frm(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);
|
|
||||||
|
|
||||||
this.render_cart_item(item_row, $item);
|
this.render_cart_item(item_row, $item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,7 +566,7 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
this.$cart_items_wrapper.append(
|
this.$cart_items_wrapper.append(
|
||||||
`<div class="cart-item-wrapper"
|
`<div class="cart-item-wrapper"
|
||||||
data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
|
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>
|
||||||
<div class="seperator"></div>`
|
<div class="seperator"></div>`
|
||||||
)
|
)
|
||||||
@ -636,13 +643,23 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
function get_item_image_html() {
|
function get_item_image_html() {
|
||||||
const { image, item_name } = item_data;
|
const { image, item_name } = item_data;
|
||||||
if (image) {
|
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 {
|
} else {
|
||||||
return `<div class="item-image item-abbr">${frappe.get_abbr(item_name)}</div>`;
|
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) {
|
scroll_to_item($item) {
|
||||||
if ($item.length === 0) return;
|
if ($item.length === 0) return;
|
||||||
const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
|
const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
|
||||||
|
@ -54,13 +54,24 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
this.$dicount_section = this.$component.find('.discount-section');
|
this.$dicount_section = this.$component.find('.discount-section');
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_item_details_section(item) {
|
has_item_has_changed(item) {
|
||||||
const { item_code, batch_no, uom } = this.current_item;
|
const { item_code, batch_no, uom, rate } = this.current_item;
|
||||||
const item_code_is_same = item && item_code === item.item_code;
|
const item_code_is_same = item && item_code === item.item_code;
|
||||||
const batch_is_same = item && batch_no == item.batch_no;
|
const batch_is_same = item && batch_no == item.batch_no;
|
||||||
const uom_is_same = item && uom === item.uom;
|
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.events.toggle_item_selector(this.item_has_changed);
|
||||||
this.toggle_component(this.item_has_changed);
|
this.toggle_component(this.item_has_changed);
|
||||||
@ -72,11 +83,12 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
this.item_row = item;
|
this.item_row = item;
|
||||||
this.currency = this.events.get_frm().doc.currency;
|
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_dom(item);
|
||||||
this.render_discount_dom(item);
|
this.render_discount_dom(item);
|
||||||
this.render_form(item);
|
this.render_form(item);
|
||||||
|
this.events.highlight_cart_item(item);
|
||||||
} else {
|
} else {
|
||||||
this.validate_serial_batch_item();
|
this.validate_serial_batch_item();
|
||||||
this.current_item = {};
|
this.current_item = {};
|
||||||
@ -121,13 +133,24 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
this.$item_description.html(get_description_html());
|
this.$item_description.html(get_description_html());
|
||||||
this.$item_price.html(format_currency(price_list_rate, this.currency));
|
this.$item_price.html(format_currency(price_list_rate, this.currency));
|
||||||
if (image) {
|
if (image) {
|
||||||
this.$item_image.html(`<img src="${image}" alt="${image}">`);
|
this.$item_image.html(
|
||||||
|
`<img
|
||||||
|
onerror="cur_pos.item_details.handle_broken_image(this)"
|
||||||
|
class="h-full" src="${image}"
|
||||||
|
alt="${frappe.get_abbr(item_name)}"
|
||||||
|
style="object-fit: cover;">`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.$item_image.html(`<div class="item-abbr">${frappe.get_abbr(item_name)}</div>`);
|
this.$item_image.html(`<div class="item-abbr">${frappe.get_abbr(item_name)}</div>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle_broken_image($img) {
|
||||||
|
const item_abbr = $($img).attr('alt');
|
||||||
|
$($img).replaceWith(`<div class="item-abbr">${item_abbr}</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
render_discount_dom(item) {
|
render_discount_dom(item) {
|
||||||
if (item.discount_percentage) {
|
if (item.discount_percentage) {
|
||||||
this.$dicount_section.html(
|
this.$dicount_section.html(
|
||||||
@ -198,12 +221,14 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
if (this.allow_rate_change) {
|
if (this.allow_rate_change) {
|
||||||
this.rate_control.df.onchange = function() {
|
this.rate_control.df.onchange = function() {
|
||||||
if (this.value || flt(this.value) === 0) {
|
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(() => {
|
me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
|
||||||
const item_row = frappe.get_doc(me.doctype, me.name);
|
const item_row = frappe.get_doc(me.doctype, me.name);
|
||||||
const doc = me.events.get_frm().doc;
|
const doc = me.events.get_frm().doc;
|
||||||
me.$item_price.html(format_currency(item_row.rate, doc.currency));
|
me.$item_price.html(format_currency(item_row.rate, doc.currency));
|
||||||
me.render_discount_dom(item_row);
|
me.render_discount_dom(item_row);
|
||||||
});
|
});
|
||||||
|
me.current_item.rate = this.value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -292,11 +317,7 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
|
|
||||||
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
|
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
|
||||||
const field_control = this[`${fieldname}_control`];
|
const field_control = this[`${fieldname}_control`];
|
||||||
const { item_code, batch_no, uom } = this.current_item;
|
const item_is_same = !this.has_item_has_changed(item_row);
|
||||||
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;
|
|
||||||
|
|
||||||
if (item_is_same && field_control && field_control.get_value() !== value) {
|
if (item_is_same && field_control && field_control.get_value() !== value) {
|
||||||
field_control.set_value(value);
|
field_control.set_value(value);
|
||||||
|
@ -51,7 +51,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get_items({start = 0, page_length = 40, search_value=''}) {
|
get_items({start = 0, page_length = 40, search_term=''}) {
|
||||||
const doc = this.events.get_frm().doc;
|
const doc = this.events.get_frm().doc;
|
||||||
const price_list = (doc && doc.selling_price_list) || this.price_list;
|
const price_list = (doc && doc.selling_price_list) || this.price_list;
|
||||||
let { item_group, pos_profile } = this;
|
let { item_group, pos_profile } = this;
|
||||||
@ -61,7 +61,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items",
|
method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items",
|
||||||
freeze: true,
|
freeze: true,
|
||||||
args: { start, page_length, price_list, item_group, search_value, pos_profile },
|
args: { start, page_length, price_list, item_group, search_term, pos_profile },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,8 +78,9 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
get_item_html(item) {
|
get_item_html(item) {
|
||||||
const me = this;
|
const me = this;
|
||||||
// eslint-disable-next-line no-unused-vars
|
// 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";
|
const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
|
||||||
|
const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0;
|
||||||
|
|
||||||
let qty_to_display = actual_qty;
|
let qty_to_display = actual_qty;
|
||||||
|
|
||||||
@ -94,7 +95,11 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
|
<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
|
<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>`;
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
return `<div class="item-qty-pill">
|
return `<div class="item-qty-pill">
|
||||||
@ -108,6 +113,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
`<div class="item-wrapper"
|
`<div class="item-wrapper"
|
||||||
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
|
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-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
|
||||||
|
data-rate="${escape(price_list_rate)}"
|
||||||
title="${item.item_name}">
|
title="${item.item_name}">
|
||||||
|
|
||||||
${get_item_image_html()}
|
${get_item_image_html()}
|
||||||
@ -116,12 +122,17 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
<div class="item-name">
|
<div class="item-name">
|
||||||
${frappe.ellipsis(item.item_name, 18)}
|
${frappe.ellipsis(item.item_name, 18)}
|
||||||
</div>
|
</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, precision) || 0}</div>
|
||||||
</div>
|
</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() {
|
make_search_bar() {
|
||||||
const me = this;
|
const me = this;
|
||||||
const doc = me.events.get_frm().doc;
|
const doc = me.events.get_frm().doc;
|
||||||
@ -213,13 +224,15 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
let batch_no = unescape($item.attr('data-batch-no'));
|
let batch_no = unescape($item.attr('data-batch-no'));
|
||||||
let serial_no = unescape($item.attr('data-serial-no'));
|
let serial_no = unescape($item.attr('data-serial-no'));
|
||||||
let uom = unescape($item.attr('data-uom'));
|
let uom = unescape($item.attr('data-uom'));
|
||||||
|
let rate = unescape($item.attr('data-rate'));
|
||||||
|
|
||||||
// escape(undefined) returns "undefined" then unescape returns "undefined"
|
// escape(undefined) returns "undefined" then unescape returns "undefined"
|
||||||
batch_no = batch_no === "undefined" ? undefined : batch_no;
|
batch_no = batch_no === "undefined" ? undefined : batch_no;
|
||||||
serial_no = serial_no === "undefined" ? undefined : serial_no;
|
serial_no = serial_no === "undefined" ? undefined : serial_no;
|
||||||
uom = uom === "undefined" ? undefined : uom;
|
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('');
|
me.set_search_value('');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -290,7 +303,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.get_items({ search_value: search_term })
|
this.get_items({ search_term })
|
||||||
.then(({ message }) => {
|
.then(({ message }) => {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { items, serial_no, batch_no, barcode } = message;
|
const { items, serial_no, batch_no, barcode } = message;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user