Merge branch 'develop' into datev_fixes
This commit is contained in:
commit
b2be91e731
38
.github/helper/semgrep_rules/README.md
vendored
Normal file
38
.github/helper/semgrep_rules/README.md
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# Semgrep linting
|
||||
|
||||
## What is semgrep?
|
||||
Semgrep or "semantic grep" is language agnostic static analysis tool. In simple terms semgrep is syntax-aware `grep`, so unlike regex it doesn't get confused by different ways of writing same thing or whitespaces or code split in multiple lines etc.
|
||||
|
||||
Example:
|
||||
|
||||
To check if a translate function is using f-string or not the regex would be `r"_\(\s*f[\"']"` while equivalent rule in semgrep would be `_(f"...")`. As semgrep knows grammer of language it takes care of unnecessary whitespace, type of quotation marks etc.
|
||||
|
||||
You can read more such examples in `.github/helper/semgrep_rules` directory.
|
||||
|
||||
# Why/when to use this?
|
||||
We want to maintain quality of contributions, at the same time remembering all the good practices can be pain to deal with while evaluating contributions. Using semgrep if you can translate "best practice" into a rule then it can automate the task for us.
|
||||
|
||||
## Running locally
|
||||
|
||||
Install semgrep using homebrew `brew install semgrep` or pip `pip install semgrep`.
|
||||
|
||||
To run locally use following command:
|
||||
|
||||
`semgrep --config=.github/helper/semgrep_rules [file/folder names]`
|
||||
|
||||
## Testing
|
||||
semgrep allows testing the tests. Refer to this page: https://semgrep.dev/docs/writing-rules/testing-rules/
|
||||
|
||||
When writing new rules you should write few positive and few negative cases as shown in the guide and current tests.
|
||||
|
||||
To run current tests: `semgrep --test --test-ignore-todo .github/helper/semgrep_rules`
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
If you are new to Semgrep read following pages to get started on writing/modifying rules:
|
||||
|
||||
- https://semgrep.dev/docs/getting-started/
|
||||
- https://semgrep.dev/docs/writing-rules/rule-syntax
|
||||
- https://semgrep.dev/docs/writing-rules/pattern-examples/
|
||||
- https://semgrep.dev/docs/writing-rules/rule-ideas/#common-use-cases
|
28
.github/helper/semgrep_rules/frappe_correctness.py
vendored
Normal file
28
.github/helper/semgrep_rules/frappe_correctness.py
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
import frappe
|
||||
from frappe import _, flt
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
def on_submit(self):
|
||||
if self.value_of_goods == 0:
|
||||
frappe.throw(_('Value of goods cannot be 0'))
|
||||
# ruleid: frappe-modifying-after-submit
|
||||
self.status = 'Submitted'
|
||||
|
||||
def on_submit(self):
|
||||
if flt(self.per_billed) < 100:
|
||||
self.update_billing_status()
|
||||
else:
|
||||
# todook: frappe-modifying-after-submit
|
||||
self.status = "Completed"
|
||||
self.db_set("status", "Completed")
|
||||
|
||||
class TestDoc(Document):
|
||||
pass
|
||||
|
||||
def validate(self):
|
||||
#ruleid: frappe-modifying-child-tables-while-iterating
|
||||
for item in self.child_table:
|
||||
if item.value < 0:
|
||||
self.remove(item)
|
74
.github/helper/semgrep_rules/frappe_correctness.yml
vendored
Normal file
74
.github/helper/semgrep_rules/frappe_correctness.yml
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
# This file specifies rules for correctness according to how frappe doctype data model works.
|
||||
|
||||
rules:
|
||||
- id: frappe-modifying-after-submit
|
||||
patterns:
|
||||
- pattern: self.$ATTR = ...
|
||||
- pattern-inside: |
|
||||
def on_submit(self, ...):
|
||||
...
|
||||
- metavariable-regex:
|
||||
metavariable: '$ATTR'
|
||||
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
|
||||
regex: '^(?!status_updater)(.*)$'
|
||||
message: |
|
||||
Doctype modified after submission. Please check if modification of self.$ATTR is commited to database.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-modifying-after-cancel
|
||||
patterns:
|
||||
- pattern: self.$ATTR = ...
|
||||
- pattern-inside: |
|
||||
def on_cancel(self, ...):
|
||||
...
|
||||
- metavariable-regex:
|
||||
metavariable: '$ATTR'
|
||||
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
||||
message: |
|
||||
Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-print-function-in-doctypes
|
||||
pattern: print(...)
|
||||
message: |
|
||||
Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement.
|
||||
languages: [python]
|
||||
severity: WARNING
|
||||
paths:
|
||||
exclude:
|
||||
- test_*.py
|
||||
include:
|
||||
- "*/**/doctype/*"
|
||||
|
||||
- id: frappe-modifying-child-tables-while-iterating
|
||||
pattern-either:
|
||||
- pattern: |
|
||||
for $ROW in self.$TABLE:
|
||||
...
|
||||
self.remove(...)
|
||||
- pattern: |
|
||||
for $ROW in self.$TABLE:
|
||||
...
|
||||
self.append(...)
|
||||
message: |
|
||||
Child table being modified while iterating on it.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
paths:
|
||||
include:
|
||||
- "*/**/doctype/*"
|
||||
|
||||
- id: frappe-same-key-assigned-twice
|
||||
pattern-either:
|
||||
- pattern: |
|
||||
{..., $X: $A, ..., $X: $B, ...}
|
||||
- pattern: |
|
||||
dict(..., ($X, $A), ..., ($X, $B), ...)
|
||||
- pattern: |
|
||||
_dict(..., ($X, $A), ..., ($X, $B), ...)
|
||||
message: |
|
||||
key `$X` is uselessly assigned twice. This could be a potential bug.
|
||||
languages: [python]
|
||||
severity: ERROR
|
6
.github/helper/semgrep_rules/security.py
vendored
Normal file
6
.github/helper/semgrep_rules/security.py
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
def function_name(input):
|
||||
# ruleid: frappe-codeinjection-eval
|
||||
eval(input)
|
||||
|
||||
# ok: frappe-codeinjection-eval
|
||||
eval("1 + 1")
|
25
.github/helper/semgrep_rules/security.yml
vendored
Normal file
25
.github/helper/semgrep_rules/security.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
rules:
|
||||
- id: frappe-codeinjection-eval
|
||||
patterns:
|
||||
- pattern-not: eval("...")
|
||||
- pattern: eval(...)
|
||||
message: |
|
||||
Detected the use of eval(). eval() can be dangerous if used to evaluate
|
||||
dynamic content. Avoid it or use safe_eval().
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-sqli-format-strings
|
||||
patterns:
|
||||
- pattern-inside: |
|
||||
@frappe.whitelist()
|
||||
def $FUNC(...):
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: frappe.db.sql("..." % ...)
|
||||
- pattern: frappe.db.sql(f"...", ...)
|
||||
- pattern: frappe.db.sql("...".format(...), ...)
|
||||
message: |
|
||||
Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines
|
||||
languages: [python]
|
||||
severity: WARNING
|
37
.github/helper/semgrep_rules/translate.js
vendored
Normal file
37
.github/helper/semgrep_rules/translate.js
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// ruleid: frappe-translation-empty-string
|
||||
__("")
|
||||
// ruleid: frappe-translation-empty-string
|
||||
__('')
|
||||
|
||||
// ok: frappe-translation-js-formatting
|
||||
__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name]);
|
||||
|
||||
// ruleid: frappe-translation-js-formatting
|
||||
__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`);
|
||||
|
||||
// ok: frappe-translation-js-formatting
|
||||
__('This is fine');
|
||||
|
||||
|
||||
// ok: frappe-translation-trailing-spaces
|
||||
__('This is fine');
|
||||
|
||||
// ruleid: frappe-translation-trailing-spaces
|
||||
__(' this is not ok ');
|
||||
// ruleid: frappe-translation-trailing-spaces
|
||||
__('this is not ok ');
|
||||
// ruleid: frappe-translation-trailing-spaces
|
||||
__(' this is not ok');
|
||||
|
||||
// ok: frappe-translation-js-splitting
|
||||
__('You have {0} subscribers in your mailing list.', [subscribers.length])
|
||||
|
||||
// todoruleid: frappe-translation-js-splitting
|
||||
__('You have') + subscribers.length + __('subscribers in your mailing list.')
|
||||
|
||||
// ruleid: frappe-translation-js-splitting
|
||||
__('You have' + 'subscribers in your mailing list.')
|
||||
|
||||
// ruleid: frappe-translation-js-splitting
|
||||
__('You have {0} subscribers' +
|
||||
'in your mailing list', [subscribers.length])
|
53
.github/helper/semgrep_rules/translate.py
vendored
Normal file
53
.github/helper/semgrep_rules/translate.py
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
# Examples taken from https://frappeframework.com/docs/user/en/translations
|
||||
# This file is used for testing the tests.
|
||||
|
||||
from frappe import _
|
||||
|
||||
full_name = "Jon Doe"
|
||||
# ok: frappe-translation-python-formatting
|
||||
_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name)
|
||||
|
||||
# ruleid: frappe-translation-python-formatting
|
||||
_('Welcome %s, get started with ERPNext in just a few clicks.' % full_name)
|
||||
# ruleid: frappe-translation-python-formatting
|
||||
_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name})
|
||||
|
||||
# ruleid: frappe-translation-python-formatting
|
||||
_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name))
|
||||
|
||||
|
||||
subscribers = ["Jon", "Doe"]
|
||||
# ok: frappe-translation-python-formatting
|
||||
_('You have {0} subscribers in your mailing list.').format(len(subscribers))
|
||||
|
||||
# ruleid: frappe-translation-python-splitting
|
||||
_('You have') + len(subscribers) + _('subscribers in your mailing list.')
|
||||
|
||||
# ruleid: frappe-translation-python-splitting
|
||||
_('You have {0} subscribers \
|
||||
in your mailing list').format(len(subscribers))
|
||||
|
||||
# ok: frappe-translation-python-splitting
|
||||
_('You have {0} subscribers') \
|
||||
+ 'in your mailing list'
|
||||
|
||||
# ruleid: frappe-translation-trailing-spaces
|
||||
msg = _(" You have {0} pending invoice ")
|
||||
# ruleid: frappe-translation-trailing-spaces
|
||||
msg = _("You have {0} pending invoice ")
|
||||
# ruleid: frappe-translation-trailing-spaces
|
||||
msg = _(" You have {0} pending invoice")
|
||||
|
||||
# ok: frappe-translation-trailing-spaces
|
||||
msg = ' ' + _("You have {0} pending invoices") + ' '
|
||||
|
||||
# ruleid: frappe-translation-python-formatting
|
||||
_(f"can not format like this - {subscribers}")
|
||||
# ruleid: frappe-translation-python-splitting
|
||||
_(f"what" + f"this is also not cool")
|
||||
|
||||
|
||||
# ruleid: frappe-translation-empty-string
|
||||
_("")
|
||||
# ruleid: frappe-translation-empty-string
|
||||
_('')
|
63
.github/helper/semgrep_rules/translate.yml
vendored
Normal file
63
.github/helper/semgrep_rules/translate.yml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
rules:
|
||||
- id: frappe-translation-empty-string
|
||||
pattern-either:
|
||||
- pattern: _("")
|
||||
- pattern: __("")
|
||||
message: |
|
||||
Empty string is useless for translation.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [python, javascript, json]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-trailing-spaces
|
||||
pattern-either:
|
||||
- pattern: _("=~/(^[ \t]+|[ \t]+$)/")
|
||||
- pattern: __("=~/(^[ \t]+|[ \t]+$)/")
|
||||
message: |
|
||||
Trailing or leading whitespace not allowed in translate strings.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [python, javascript, json]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-python-formatting
|
||||
pattern-either:
|
||||
- pattern: _("..." % ...)
|
||||
- pattern: _("...".format(...))
|
||||
- pattern: _(f"...")
|
||||
message: |
|
||||
Only positional formatters are allowed and formatting should not be done before translating.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-js-formatting
|
||||
patterns:
|
||||
- pattern: __(`...`)
|
||||
- pattern-not: __("...")
|
||||
message: |
|
||||
Template strings are not allowed for text formatting.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [javascript, json]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-python-splitting
|
||||
pattern-either:
|
||||
- pattern: _(...) + ... + _(...)
|
||||
- pattern: _("..." + "...")
|
||||
- pattern-regex: '_\([^\)]*\\\s*'
|
||||
message: |
|
||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-js-splitting
|
||||
pattern-either:
|
||||
- pattern-regex: '__\([^\)]*[\+\\]\s*'
|
||||
- pattern: __('...' + '...')
|
||||
- pattern: __('...') + __('...')
|
||||
message: |
|
||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [javascript, json]
|
||||
severity: ERROR
|
31
.github/helper/semgrep_rules/ux.py
vendored
Normal file
31
.github/helper/semgrep_rules/ux.py
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import frappe
|
||||
from frappe import msgprint, throw, _
|
||||
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
throw("Error Occured")
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
frappe.throw("Error Occured")
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
frappe.msgprint("Useful message")
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
msgprint("Useful message")
|
||||
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
translatedmessage = _("Hello")
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
throw(translatedmessage)
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
msgprint(translatedmessage)
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
msgprint(_("Helpful message"))
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
frappe.throw(_("Error occured"))
|
15
.github/helper/semgrep_rules/ux.yml
vendored
Normal file
15
.github/helper/semgrep_rules/ux.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
rules:
|
||||
- id: frappe-missing-translate-function
|
||||
pattern-either:
|
||||
- patterns:
|
||||
- pattern: frappe.msgprint("...", ...)
|
||||
- pattern-not: frappe.msgprint(_("..."), ...)
|
||||
- pattern-not: frappe.msgprint(__("..."), ...)
|
||||
- patterns:
|
||||
- pattern: frappe.throw("...", ...)
|
||||
- pattern-not: frappe.throw(_("..."), ...)
|
||||
- pattern-not: frappe.throw(__("..."), ...)
|
||||
message: |
|
||||
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
|
||||
languages: [python, javascript, json]
|
||||
severity: ERROR
|
7
.github/workflows/ci-tests.yml
vendored
7
.github/workflows/ci-tests.yml
vendored
@ -85,10 +85,9 @@ jobs:
|
||||
run: |
|
||||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||
cd ${GITHUB_WORKSPACE}
|
||||
pip install coveralls==2.2.0
|
||||
pip install coverage==4.5.4
|
||||
coveralls
|
||||
pip install coveralls==3.0.1
|
||||
pip install coverage==5.5
|
||||
coveralls --service=github
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
||||
|
||||
|
24
.github/workflows/semgrep.yml
vendored
Normal file
24
.github/workflows/semgrep.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Semgrep
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
jobs:
|
||||
semgrep:
|
||||
name: Frappe Linter
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup python3
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Run semgrep
|
||||
run: |
|
||||
python -m pip install -q semgrep
|
||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
|
||||
semgrep --config="r/python.lang.correctness" --quiet --error $files
|
||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
|
@ -39,6 +39,10 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a
|
||||
|
||||
---
|
||||
|
||||
### Containerized Installation
|
||||
|
||||
Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
|
||||
|
||||
### Full Install
|
||||
|
||||
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
|
||||
|
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.0.0-dev'
|
||||
__version__ = '13.1.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
@ -12,6 +12,7 @@
|
||||
"frozen_accounts_modifier",
|
||||
"determine_address_tax_category_from",
|
||||
"over_billing_allowance",
|
||||
"role_allowed_to_over_bill",
|
||||
"column_break_4",
|
||||
"credit_controller",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
@ -226,6 +227,13 @@
|
||||
"fieldname": "delete_linked_ledger_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
|
||||
},
|
||||
{
|
||||
"description": "Users with this role are allowed to over bill above the allowance percentage",
|
||||
"fieldname": "role_allowed_to_over_bill",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Over Bill ",
|
||||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -233,7 +241,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-05 13:04:00.118892",
|
||||
"modified": "2021-03-11 18:52:05.601996",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
@ -78,8 +78,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
if (
|
||||
frm.doc.bank_account &&
|
||||
frm.doc.bank_statement_from_date &&
|
||||
frm.doc.bank_statement_to_date &&
|
||||
frm.doc.bank_statement_closing_balance
|
||||
frm.doc.bank_statement_to_date
|
||||
) {
|
||||
frm.trigger("render_chart");
|
||||
frm.trigger("render");
|
||||
|
@ -39,13 +39,13 @@
|
||||
"depends_on": "eval: doc.bank_account",
|
||||
"fieldname": "bank_statement_from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Bank Statement From Date"
|
||||
"label": "From Date"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.bank_statement_from_date",
|
||||
"fieldname": "bank_statement_to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Bank Statement To Date"
|
||||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
@ -63,11 +63,10 @@
|
||||
"depends_on": "eval: doc.bank_statement_to_date",
|
||||
"fieldname": "bank_statement_closing_balance",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Bank Statement Closing Balance",
|
||||
"label": "Closing Balance",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.bank_statement_closing_balance",
|
||||
"fieldname": "section_break_1",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Reconcile"
|
||||
@ -90,7 +89,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-02 01:35:53.043578",
|
||||
"modified": "2021-04-21 11:13:49.831769",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Reconciliation Tool",
|
||||
|
@ -21,21 +21,17 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.docstatus==1) {
|
||||
frappe.db.get_value("Journal Entry Account", {
|
||||
'reference_type': 'Exchange Rate Revaluation',
|
||||
'reference_name': frm.doc.name,
|
||||
'docstatus': 1
|
||||
}, "sum(debit) as sum", (r) =>{
|
||||
let total_amt = 0;
|
||||
frm.doc.accounts.forEach(d=> {
|
||||
total_amt = total_amt + d['new_balance_in_base_currency'];
|
||||
});
|
||||
if(total_amt !== r.sum) {
|
||||
frappe.call({
|
||||
method: 'check_journal_entry_condition',
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.add_custom_button(__('Journal Entry'), function() {
|
||||
return frm.events.make_jv(frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
}, 'Journal Entry');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -27,6 +27,23 @@ class ExchangeRateRevaluation(Document):
|
||||
if not (self.company and self.posting_date):
|
||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_journal_entry_condition(self):
|
||||
total_debit = frappe.db.get_value("Journal Entry Account", {
|
||||
'reference_type': 'Exchange Rate Revaluation',
|
||||
'reference_name': self.name,
|
||||
'docstatus': 1
|
||||
}, "sum(debit) as sum")
|
||||
|
||||
total_amt = 0
|
||||
for d in self.accounts:
|
||||
total_amt = total_amt + d.new_balance_in_base_currency
|
||||
|
||||
if total_amt != total_debit:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_accounts_data(self, account=None):
|
||||
accounts = []
|
||||
|
@ -592,6 +592,7 @@ class JournalEntry(AccountsController):
|
||||
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding_invoices(self):
|
||||
self.set('accounts', [])
|
||||
total = 0
|
||||
|
@ -582,7 +582,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
|
||||
if(frm.doc.payment_type == "Receive")
|
||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount);
|
||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1);
|
||||
else
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
},
|
||||
@ -743,7 +743,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
});
|
||||
},
|
||||
|
||||
allocate_party_amount_against_ref_docs: function(frm, paid_amount) {
|
||||
allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) {
|
||||
var total_positive_outstanding_including_order = 0;
|
||||
var total_negative_outstanding = 0;
|
||||
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
||||
@ -800,22 +800,15 @@ frappe.ui.form.on('Payment Entry', {
|
||||
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
||||
row.allocated_amount = 0;
|
||||
|
||||
} else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) {
|
||||
if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
|
||||
if (row.outstanding_amount >= allocated_positive_outstanding) {
|
||||
row.allocated_amount = allocated_positive_outstanding;
|
||||
} else {
|
||||
row.allocated_amount = row.outstanding_amount;
|
||||
}
|
||||
|
||||
} else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) {
|
||||
if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) {
|
||||
row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ?
|
||||
allocated_positive_outstanding : row.outstanding_amount;
|
||||
allocated_positive_outstanding -= flt(row.allocated_amount);
|
||||
} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
|
||||
if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) {
|
||||
row.allocated_amount = -1*allocated_negative_outstanding;
|
||||
} else {
|
||||
row.allocated_amount = row.outstanding_amount;
|
||||
};
|
||||
|
||||
} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
|
||||
row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ?
|
||||
-1*allocated_negative_outstanding : row.outstanding_amount;
|
||||
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
|
||||
}
|
||||
}
|
||||
|
@ -16,28 +16,8 @@ class POSClosingEntry(StatusUpdater):
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
|
||||
self.validate_pos_closing()
|
||||
self.validate_pos_invoices()
|
||||
|
||||
def validate_pos_closing(self):
|
||||
user = frappe.db.sql("""
|
||||
SELECT name FROM `tabPOS Closing Entry`
|
||||
WHERE
|
||||
user = %(user)s AND docstatus = 1 AND pos_profile = %(profile)s AND
|
||||
(period_start_date between %(start)s and %(end)s OR period_end_date between %(start)s and %(end)s)
|
||||
""", {
|
||||
'user': self.user,
|
||||
'profile': self.pos_profile,
|
||||
'start': self.period_start_date,
|
||||
'end': self.period_end_date
|
||||
})
|
||||
|
||||
if user:
|
||||
bold_already_exists = frappe.bold(_("already exists"))
|
||||
bold_user = frappe.bold(self.user)
|
||||
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
||||
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
||||
|
||||
def validate_pos_invoices(self):
|
||||
invalid_rows = []
|
||||
for d in self.pos_transactions:
|
||||
@ -89,8 +69,8 @@ class POSClosingEntry(StatusUpdater):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
|
||||
return [c['user'] for c in cashiers_list]
|
||||
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1)
|
||||
return [c for c in cashiers_list]
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pos_invoices(start, end, pos_profile, user):
|
||||
|
@ -96,30 +96,45 @@ class POSInvoice(SalesInvoice):
|
||||
if paid_amt and pay.amount != paid_amt:
|
||||
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
if self.is_return:
|
||||
return
|
||||
def validate_pos_reserved_serial_nos(self, item):
|
||||
serial_nos = get_serial_nos(item.serial_no)
|
||||
filters = {"item_code": item.item_code, "warehouse": item.warehouse}
|
||||
if item.batch_no:
|
||||
filters["batch_no"] = item.batch_no
|
||||
|
||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||
error_msg = []
|
||||
for d in self.get('items'):
|
||||
msg = ""
|
||||
if d.serial_no:
|
||||
filters = { "item_code": d.item_code, "warehouse": d.warehouse }
|
||||
if d.batch_no:
|
||||
filters["batch_no"] = d.batch_no
|
||||
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||
|
||||
bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
|
||||
if len(invalid_serial_nos) == 1:
|
||||
msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(d.idx, bold_invalid_serial_nos))
|
||||
frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
|
||||
elif invalid_serial_nos:
|
||||
msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(d.idx, bold_invalid_serial_nos))
|
||||
frappe.throw(_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
|
||||
|
||||
def validate_delivered_serial_nos(self, item):
|
||||
serial_nos = get_serial_nos(item.serial_no)
|
||||
delivered_serial_nos = frappe.db.get_list('Serial No', {
|
||||
'item_code': item.item_code,
|
||||
'name': ['in', serial_nos],
|
||||
'sales_invoice': ['is', 'set']
|
||||
}, pluck='name')
|
||||
|
||||
if delivered_serial_nos:
|
||||
bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos))
|
||||
frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.")
|
||||
.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
if self.is_return:
|
||||
return
|
||||
|
||||
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
|
||||
for d in self.get('items'):
|
||||
if d.serial_no:
|
||||
self.validate_pos_reserved_serial_nos(d)
|
||||
self.validate_delivered_serial_nos(d)
|
||||
else:
|
||||
if allow_negative_stock:
|
||||
return
|
||||
@ -127,15 +142,11 @@ class POSInvoice(SalesInvoice):
|
||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||
if flt(available_stock) <= 0:
|
||||
msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse))
|
||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
|
||||
.format(d.idx, item_code, warehouse), title=_("Item Unavailable"))
|
||||
elif flt(available_stock) < flt(d.qty):
|
||||
msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
|
||||
.format(d.idx, item_code, warehouse, qty))
|
||||
if msg:
|
||||
error_msg.append(msg)
|
||||
|
||||
if error_msg:
|
||||
frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
|
||||
frappe.throw(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
|
||||
.format(d.idx, item_code, warehouse, available_stock), title=_("Item Unavailable"))
|
||||
|
||||
def validate_serialised_or_batched_item(self):
|
||||
error_msg = []
|
||||
@ -202,9 +213,8 @@ class POSInvoice(SalesInvoice):
|
||||
for d in self.get("items"):
|
||||
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
||||
if not is_stock_item:
|
||||
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format(
|
||||
d.idx, frappe.bold(d.item_code)
|
||||
), title=_("Invalid Item"))
|
||||
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ")
|
||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
|
||||
def validate_mode_of_payment(self):
|
||||
if len(self.payments) == 0:
|
||||
|
@ -10,10 +10,12 @@ from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
class TestPOSInvoice(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def tearDown(self):
|
||||
@ -320,6 +322,34 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos2.insert)
|
||||
|
||||
def test_delivered_serialized_item_transaction(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
se = make_serialized_item(company='_Test Company',
|
||||
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
|
||||
si = create_sales_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||
|
||||
si.get("items")[0].serial_no = serial_nos[0]
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||
|
||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos2.insert)
|
||||
|
||||
def test_loyalty_points(self):
|
||||
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
|
||||
|
@ -0,0 +1,37 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-04-19 14:56:06.652327",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"field",
|
||||
"fieldname"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Fieldname"
|
||||
},
|
||||
{
|
||||
"fieldname": "field",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Field"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-21 11:12:54.632093",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Search Fields",
|
||||
"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 POSSearchFields(Document):
|
||||
pass
|
@ -1,9 +1,17 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor'];
|
||||
let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series",
|
||||
"default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series",
|
||||
"serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account",
|
||||
"deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail",
|
||||
"web_long_description", "hub_sync_id"]
|
||||
|
||||
frappe.ui.form.on('POS Settings', {
|
||||
onload: function(frm) {
|
||||
frm.trigger("get_invoice_fields");
|
||||
frm.trigger("add_search_options");
|
||||
},
|
||||
|
||||
get_invoice_fields: function(frm) {
|
||||
@ -21,6 +29,38 @@ frappe.ui.form.on('POS Settings', {
|
||||
);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
add_search_options: function(frm) {
|
||||
frappe.model.with_doctype("Item", () => {
|
||||
var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
|
||||
if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) {
|
||||
return [d.label];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
fields.unshift('');
|
||||
frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("POS Search Fields", {
|
||||
field: function(frm, doctype, name) {
|
||||
var doc = frappe.get_doc(doctype, name);
|
||||
var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
|
||||
if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) {
|
||||
return d;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})[0];
|
||||
|
||||
doc.fieldname = df.fieldname;
|
||||
frm.refresh_field("fields");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -5,7 +5,8 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"invoice_fields"
|
||||
"invoice_fields",
|
||||
"pos_search_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -13,11 +14,17 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Field",
|
||||
"options": "POS Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_search_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Search Fields",
|
||||
"options": "POS Search Fields"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-01 15:46:41.478928",
|
||||
"modified": "2021-04-19 14:56:24.465218",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Settings",
|
||||
|
@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
||||
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
||||
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
||||
root_name = frappe.db.get_list(parenttype,
|
||||
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
|
||||
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True)
|
||||
|
||||
if root_name and root_name[0][0]:
|
||||
parent_groups.append(root_name[0][0])
|
||||
|
@ -38,22 +38,22 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}</td>
|
||||
{{ frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}</td>
|
||||
{{ frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}</td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or " " }}</b></td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}
|
||||
{{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}
|
||||
{{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }}
|
||||
{{ frappe.utils.fmt_money(row.balance, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -78,10 +78,10 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ aging.range1 }}</td>
|
||||
<td>{{ aging.range2 }}</td>
|
||||
<td>{{ aging.range3 }}</td>
|
||||
<td>{{ aging.range4 }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(aging.range1, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(aging.range2, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(aging.range3, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(aging.range4, currency=filters.presentation_currency) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -4,10 +4,12 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa
|
||||
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from erpnext import get_company_currency
|
||||
from erpnext.accounts.party import get_party_account_currency
|
||||
|
||||
from frappe.utils.print_format import report_to_pdf
|
||||
from frappe.utils.pdf import get_pdf
|
||||
@ -29,7 +31,7 @@ class ProcessStatementOfAccounts(Document):
|
||||
validate_template(self.body)
|
||||
|
||||
if not self.customers:
|
||||
frappe.throw(frappe._('Customers not selected.'))
|
||||
frappe.throw(_('Customers not selected.'))
|
||||
|
||||
if self.enable_auto_email:
|
||||
self.to_date = self.start_date
|
||||
@ -58,22 +60,24 @@ def get_report_pdf(doc, consolidated=True):
|
||||
aging[0]['ageing_based_on'] = doc.ageing_based_on
|
||||
|
||||
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
|
||||
presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \
|
||||
or doc.currency or get_company_currency(doc.company)
|
||||
|
||||
filters= frappe._dict({
|
||||
'from_date': doc.from_date,
|
||||
'to_date': doc.to_date,
|
||||
'company': doc.company,
|
||||
'finance_book': doc.finance_book if doc.finance_book else None,
|
||||
"account": doc.account if doc.account else None,
|
||||
'account': doc.account if doc.account else None,
|
||||
'party_type': 'Customer',
|
||||
'party': [entry.customer],
|
||||
'presentation_currency': presentation_currency,
|
||||
'group_by': doc.group_by,
|
||||
'currency': doc.currency,
|
||||
'cost_center': [cc.cost_center_name for cc in doc.cost_center],
|
||||
'project': [p.project_name for p in doc.project],
|
||||
'show_opening_entries': 0,
|
||||
'include_default_book_entries': 0,
|
||||
'show_cancelled_entries': 1,
|
||||
'tax_id': tax_id if tax_id else None
|
||||
})
|
||||
col, res = get_soa(filters)
|
||||
@ -167,7 +171,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
if customer_collection == 'Sales Person':
|
||||
customers = get_customers_based_on_sales_person(collection_name)
|
||||
if not bool(customers):
|
||||
frappe.throw('No Customers found with selected options.')
|
||||
frappe.throw(_('No Customers found with selected options.'))
|
||||
else:
|
||||
if customer_collection == 'Sales Partner':
|
||||
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||
@ -199,14 +203,14 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
|
||||
|
||||
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
||||
if billing_and_primary:
|
||||
frappe.throw('No billing email found for customer: '+ customer_name)
|
||||
frappe.throw(_("No billing email found for customer: {0}").format(customer_name))
|
||||
else:
|
||||
return ''
|
||||
|
||||
if billing_and_primary:
|
||||
primary_email = frappe.get_value('Customer', customer_name, 'email_id')
|
||||
if primary_email is None and int(primary_mandatory):
|
||||
frappe.throw('No primary email found for customer: '+ customer_name)
|
||||
frappe.throw(_("No primary email found for customer: {0}").format(customer_name))
|
||||
return [primary_email or '', billing_email[0][0]]
|
||||
else:
|
||||
return billing_email[0][0] or ''
|
||||
|
@ -9,7 +9,7 @@ from frappe.utils import cstr
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.model.document import Document
|
||||
|
||||
pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group'
|
||||
pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group',
|
||||
'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from',
|
||||
'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier',
|
||||
'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules']
|
||||
|
@ -514,6 +514,28 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.events.add_custom_buttons(frm);
|
||||
},
|
||||
|
||||
add_custom_buttons: function(frm) {
|
||||
if (frm.doc.per_received < 100) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frm.events.make_purchase_receipt(frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frappe.route_options = {
|
||||
'purchase_invoice': frm.doc.name
|
||||
}
|
||||
|
||||
frappe.set_route("List", "Purchase Receipt", "List")
|
||||
}, __('View'));
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
if(frm.doc.__onload && frm.is_new()) {
|
||||
if(frm.doc.supplier) {
|
||||
@ -539,5 +561,13 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
update_stock: function(frm) {
|
||||
hide_fields(frm.doc);
|
||||
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
|
||||
},
|
||||
|
||||
make_purchase_receipt: function(frm) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
|
||||
frm: frm,
|
||||
freeze_message: __("Creating Purchase Receipt ...")
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -163,7 +163,8 @@
|
||||
"to_date",
|
||||
"column_break_114",
|
||||
"auto_repeat",
|
||||
"update_auto_repeat_reference"
|
||||
"update_auto_repeat_reference",
|
||||
"per_received"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -1364,6 +1365,15 @@
|
||||
"print_hide": 1,
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"fieldname": "per_received",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 1,
|
||||
"label": "Per Received",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
|
@ -1207,3 +1207,41 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_receipt(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
|
||||
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
|
||||
target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
|
||||
flt(obj.rate) * flt(source_parent.conversion_rate)
|
||||
|
||||
doc = get_mapped_doc("Purchase Invoice", source_name, {
|
||||
"Purchase Invoice": {
|
||||
"doctype": "Purchase Receipt",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
},
|
||||
"Purchase Invoice Item": {
|
||||
"doctype": "Purchase Receipt Item",
|
||||
"field_map": {
|
||||
"name": "purchase_invoice_item",
|
||||
"parent": "purchase_invoice",
|
||||
"bom": "bom",
|
||||
"purchase_order": "purchase_order",
|
||||
"po_detail": "purchase_order_item",
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item"
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges"
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
return doc
|
||||
|
@ -607,6 +607,7 @@
|
||||
"oldfieldname": "purchase_order",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Purchase Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@ -853,7 +854,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-23 00:59:52.614805",
|
||||
"modified": "2021-03-30 09:02:39.256602",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
@ -118,6 +118,7 @@
|
||||
"in_words",
|
||||
"total_advance",
|
||||
"outstanding_amount",
|
||||
"disable_rounded_total",
|
||||
"advances_section",
|
||||
"allocate_advances_automatically",
|
||||
"get_advances",
|
||||
@ -1109,6 +1110,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "base_rounding_adjustment",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@ -1120,6 +1122,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "base_rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@ -1168,6 +1171,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "rounding_adjustment",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@ -1180,6 +1184,7 @@
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@ -1945,6 +1950,13 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Set Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "grand_total",
|
||||
"fieldname": "disable_rounded_total",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Rounded Total"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@ -1957,7 +1969,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-03-31 15:42:26.261540",
|
||||
"modified": "2021-04-15 23:57:58.766651",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -46,7 +46,6 @@ class SalesInvoice(SellingController):
|
||||
'target_parent_dt': 'Sales Order',
|
||||
'target_parent_field': 'per_billed',
|
||||
'source_field': 'amount',
|
||||
'join_field': 'so_detail',
|
||||
'percent_join_field': 'sales_order',
|
||||
'status_field': 'billing_status',
|
||||
'keyword': 'Billed',
|
||||
@ -276,7 +275,7 @@ class SalesInvoice(SellingController):
|
||||
pluck="pos_closing_entry"
|
||||
)
|
||||
if pos_closing_entry:
|
||||
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format(
|
||||
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
|
||||
frappe.bold("Consolidated Sales Invoice"),
|
||||
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
|
||||
)
|
||||
@ -549,12 +548,12 @@ class SalesInvoice(SellingController):
|
||||
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
||||
|
||||
if account.report_type != "Balance Sheet":
|
||||
msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To"))
|
||||
msg = _("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To")) + " "
|
||||
msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
|
||||
frappe.throw(msg, title=_("Invalid Account"))
|
||||
|
||||
if self.customer and account.account_type != "Receivable":
|
||||
msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To"))
|
||||
msg = _("Please ensure {} account is a Receivable account.").format(frappe.bold("Debit To")) + " "
|
||||
msg += _("Change the account type to Receivable or select a different account.")
|
||||
frappe.throw(msg, title=_("Invalid Account"))
|
||||
|
||||
|
@ -1879,7 +1879,17 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_einvoice_submission_without_irn(self):
|
||||
# init
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
||||
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||
einvoice_settings.enable = 1
|
||||
einvoice_settings.applicable_from = nowdate()
|
||||
einvoice_settings.append('credentials', {
|
||||
'company': '_Test Company',
|
||||
'gstin': '27AAECE4835E1ZR',
|
||||
'username': 'test',
|
||||
'password': 'test'
|
||||
})
|
||||
einvoice_settings.save()
|
||||
|
||||
country = frappe.flags.country
|
||||
frappe.flags.country = 'India'
|
||||
|
||||
@ -1890,7 +1900,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.submit()
|
||||
|
||||
# reset
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
||||
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||
einvoice_settings.enable = 0
|
||||
frappe.flags.country = country
|
||||
|
||||
def test_einvoice_json(self):
|
||||
|
@ -251,7 +251,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
threshold = tax_details.get('threshold', 0)
|
||||
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
||||
|
||||
if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||
if ldc and is_valid_certificate(
|
||||
ldc.valid_from, ldc.valid_upto,
|
||||
inv.posting_date, tax_deducted,
|
||||
|
@ -87,50 +87,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_single_threshold_tds_with_previous_vouchers(self):
|
||||
invoices = []
|
||||
frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS")
|
||||
pi = create_purchase_invoice(supplier="Test TDS Supplier2")
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
pi = create_purchase_invoice(supplier="Test TDS Supplier2")
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
self.assertEqual(pi.taxes_and_charges_deducted, 2000)
|
||||
self.assertEqual(pi.grand_total, 8000)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self):
|
||||
invoices = []
|
||||
doc = create_supplier(supplier_name = "Test TDS Supplier ABC",
|
||||
tax_withholding_category="Single Threshold TDS")
|
||||
supplier = doc.name
|
||||
|
||||
pi = create_purchase_invoice(supplier=supplier)
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
# TDS not applied
|
||||
pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True)
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
pi = create_purchase_invoice(supplier=supplier)
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
self.assertEqual(pi.taxes_and_charges_deducted, 2000)
|
||||
self.assertEqual(pi.grand_total, 8000)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_cumulative_threshold_tcs(self):
|
||||
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
|
||||
invoices = []
|
||||
|
@ -18,7 +18,8 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd
|
||||
gl_map = process_gl_map(gl_map, merge_entries)
|
||||
if gl_map and len(gl_map) > 1:
|
||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||
else:
|
||||
# Post GL Map proccess there may no be any GL Entries
|
||||
elif gl_map:
|
||||
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
|
||||
else:
|
||||
make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
||||
|
@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports['Billed Items To Be Received'] = {
|
||||
'filters': [
|
||||
{
|
||||
'label': __('Company'),
|
||||
'fieldname': 'company',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Company',
|
||||
'reqd': 1,
|
||||
'default': frappe.defaults.get_default('Company')
|
||||
},
|
||||
{
|
||||
'label': __('As on Date'),
|
||||
'fieldname': 'posting_date',
|
||||
'fieldtype': 'Date',
|
||||
'reqd': 1,
|
||||
'default': get_today()
|
||||
},
|
||||
{
|
||||
'label': __('Purchase Invoice'),
|
||||
'fieldname': 'purchase_invoice',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Purchase Invoice'
|
||||
}
|
||||
]
|
||||
};
|
@ -0,0 +1,39 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-03-30 09:35:38.683028",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-03-31 08:48:30.944429",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Billed Items To Be Received",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"query": "",
|
||||
"ref_doctype": "Purchase Invoice",
|
||||
"report_name": "Billed Items To Be Received",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
data = get_data(filters) or []
|
||||
columns = get_columns()
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_data(report_filters):
|
||||
filters = get_report_filters(report_filters)
|
||||
fields = get_report_fields()
|
||||
|
||||
return frappe.get_all('Purchase Invoice',
|
||||
fields= fields, filters=filters)
|
||||
|
||||
def get_report_filters(report_filters):
|
||||
filters = [['Purchase Invoice','company','=',report_filters.get('company')],
|
||||
['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1],
|
||||
['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]]
|
||||
|
||||
if report_filters.get('purchase_invoice'):
|
||||
filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]])
|
||||
|
||||
return filters
|
||||
|
||||
def get_report_fields():
|
||||
fields = []
|
||||
for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']:
|
||||
fields.append('`tabPurchase Invoice`.`{}`'.format(p_field))
|
||||
|
||||
for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']:
|
||||
fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field))
|
||||
|
||||
return fields
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{
|
||||
'label': _('Purchase Invoice'),
|
||||
'fieldname': 'name',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Purchase Invoice',
|
||||
'width': 170
|
||||
},
|
||||
{
|
||||
'label': _('Supplier'),
|
||||
'fieldname': 'supplier',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Supplier',
|
||||
'width': 120
|
||||
},
|
||||
{
|
||||
'label': _('Posting Date'),
|
||||
'fieldname': 'posting_date',
|
||||
'fieldtype': 'Date',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Item Code'),
|
||||
'fieldname': 'item_code',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Item',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Item Name'),
|
||||
'fieldname': 'item_name',
|
||||
'fieldtype': 'Data',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('UOM'),
|
||||
'fieldname': 'uom',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'UOM',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Invoiced Qty'),
|
||||
'fieldname': 'qty',
|
||||
'fieldtype': 'Float',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Received Qty'),
|
||||
'fieldname': 'received_qty',
|
||||
'fieldtype': 'Float',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Rate'),
|
||||
'fieldname': 'rate',
|
||||
'fieldtype': 'Currency',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Amount'),
|
||||
'fieldname': 'amount',
|
||||
'fieldtype': 'Currency',
|
||||
'width': 100
|
||||
}
|
||||
]
|
@ -435,6 +435,35 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
po.load_from_db()
|
||||
self.assertEqual(po.get("items")[0].received_qty, 5)
|
||||
|
||||
def test_purchase_order_invoice_receipt_workflow(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt
|
||||
|
||||
po = create_purchase_order()
|
||||
pi = make_pi_from_po(po.name)
|
||||
|
||||
pi.submit()
|
||||
|
||||
pr = make_purchase_receipt(pi.name)
|
||||
pr.submit()
|
||||
|
||||
pi.load_from_db()
|
||||
|
||||
self.assertEquals(pi.per_received, 100.00)
|
||||
self.assertEquals(pi.items[0].qty, pi.items[0].received_qty)
|
||||
|
||||
po.load_from_db()
|
||||
|
||||
self.assertEquals(po.per_received, 100.00)
|
||||
self.assertEquals(po.per_billed, 100.00)
|
||||
|
||||
pr.cancel()
|
||||
|
||||
pi.load_from_db()
|
||||
pi.cancel()
|
||||
|
||||
po.load_from_db()
|
||||
po.cancel()
|
||||
|
||||
def test_make_purchase_invoice(self):
|
||||
po = create_purchase_order(do_not_submit=True)
|
||||
|
||||
|
129
erpnext/change_log/v13/v13_1_0.md
Normal file
129
erpnext/change_log/v13/v13_1_0.md
Normal file
@ -0,0 +1,129 @@
|
||||
# Version 13.1.0 Release Notes
|
||||
|
||||
### Features
|
||||
|
||||
- Recursive pricing rule ([#24922](https://github.com/frappe/erpnext/pull/24922))
|
||||
- Discount configuration on early payments ([#24586](https://github.com/frappe/erpnext/pull/24586))
|
||||
- Bulk e-invoice generation ([#24969](https://github.com/frappe/erpnext/pull/24969))
|
||||
- Employee Self Service ([#24408](https://github.com/frappe/erpnext/pull/24408))
|
||||
- Share doc with employee approvers if they don't have access ([#25190](https://github.com/frappe/erpnext/pull/25190))
|
||||
- Price margin in buying ([#24685](https://github.com/frappe/erpnext/pull/24685))
|
||||
- Allow changing Work Stations in Work Order & Job Card ([#24897](https://github.com/frappe/erpnext/pull/24897))
|
||||
- Add document type field for e-invoicing (Italy) ([#25256](https://github.com/frappe/erpnext/pull/25256))
|
||||
- Add checkbox for disabling leave notification in HR Settings ([#24877](https://github.com/frappe/erpnext/pull/24877))
|
||||
- Enhancements in Material Request Plan Item in Production Plan ([#25025](https://github.com/frappe/erpnext/pull/25025))
|
||||
|
||||
|
||||
### Fixes and Enhancements
|
||||
- Mode of payments disappear on loading draft pos invoice ([#24917](https://github.com/frappe/erpnext/pull/24917))
|
||||
- Sales order not saving due type mismatch in promo scheme (#24748) ([#25222](https://github.com/frappe/erpnext/pull/25222))
|
||||
- Zero amount completed delivery notes being shown in Sales Invoice get items ([#25317](https://github.com/frappe/erpnext/pull/25317))
|
||||
- Incorrect status creating PR from PO after creating PI ([#25109](https://github.com/frappe/erpnext/pull/25109))
|
||||
- Precision and formatted document for stock level in item dashboard. ([#24921](https://github.com/frappe/erpnext/pull/24921))
|
||||
- Precision issues while allocating advance amount ([#25086](https://github.com/frappe/erpnext/pull/25086))
|
||||
- Round off final tax amount instead of current tax amount ([#25188](https://github.com/frappe/erpnext/pull/25188))
|
||||
- Redesign fixes ([#24896](https://github.com/frappe/erpnext/pull/24896))
|
||||
- TDS check getting checked after reload ([#24972](https://github.com/frappe/erpnext/pull/24972))
|
||||
- Github Action not failing when tests fail ([#24867](https://github.com/frappe/erpnext/pull/24867))
|
||||
- Calculate 80g certificate amount on validate for memberships ([#24925](https://github.com/frappe/erpnext/pull/24925))
|
||||
- Purchase from registered composition dealer ([#25040](https://github.com/frappe/erpnext/pull/25040))
|
||||
- Reduce number of queries for checking if future SL entry exists ([#24881](https://github.com/frappe/erpnext/pull/24881))
|
||||
- Remove unwanted parameter in calculate_rate_and_amount ([#24883](https://github.com/frappe/erpnext/pull/24883))
|
||||
- Membership renewal validation ([#24963](https://github.com/frappe/erpnext/pull/24963))
|
||||
- Not able to save material request ([#25112](https://github.com/frappe/erpnext/pull/25112))
|
||||
- POS print receipt ([#25330](https://github.com/frappe/erpnext/pull/25330))
|
||||
- Supplier was not able to Submit RFQ due to insufficient permission ([#24622](https://github.com/frappe/erpnext/pull/24622))
|
||||
- Unequal debit and credit issue on RCM Invoice ([#24836](https://github.com/frappe/erpnext/pull/24836))
|
||||
- Picked Qty conversion from Stock Qty to Qty while creating DN from Pick List ([#25105](https://github.com/frappe/erpnext/pull/25105))
|
||||
- Salary Structure object has no attribute set_totals ([#25113](https://github.com/frappe/erpnext/pull/25113))
|
||||
- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24916](https://github.com/frappe/erpnext/pull/24916))
|
||||
- Add method for regional round off account back ([#24893](https://github.com/frappe/erpnext/pull/24893))
|
||||
- Employee profile pic upload access for erpnext user ([#25022](https://github.com/frappe/erpnext/pull/25022))
|
||||
- Make filters for payroll entry ([#25386](https://github.com/frappe/erpnext/pull/25386))
|
||||
- Fix dynamically changing grid properties ([#25310](https://github.com/frappe/erpnext/pull/25310))
|
||||
- Consider paid repayment entries in subsequent loan repayments ([#25271](https://github.com/frappe/erpnext/pull/25271))
|
||||
- Allow duplicate additional salaries ([#24842](https://github.com/frappe/erpnext/pull/24842))
|
||||
- Object referencing the same address issue ([#25159](https://github.com/frappe/erpnext/pull/25159))
|
||||
- Validating party currency with doc currency ([#24318](https://github.com/frappe/erpnext/pull/24318))
|
||||
- Non Profit fixes ([#25060](https://github.com/frappe/erpnext/pull/25060))
|
||||
- Additional Salary component amount not getting set ([#25356](https://github.com/frappe/erpnext/pull/25356))
|
||||
- Allow user to update exchange rate in Multi-currency LCV ([#24912](https://github.com/frappe/erpnext/pull/24912))
|
||||
- Allow creating stock entry based on work order for customer provided items ([#24885](https://github.com/frappe/erpnext/pull/24885))
|
||||
- Create property setters for shorter naming series on setup ([#25128](https://github.com/frappe/erpnext/pull/25128))
|
||||
- Add GST category field in Delivery Note ([#25053](https://github.com/frappe/erpnext/pull/25053))
|
||||
- Ignore Permission for Leave Ledger Entry ([#25172](https://github.com/frappe/erpnext/pull/25172))
|
||||
- Pending shortfall update on processing loan security shortfall ([#24971](https://github.com/frappe/erpnext/pull/24971))
|
||||
- Added flag for dont_fetch_price_list_rate in transaction ([#25041](https://github.com/frappe/erpnext/pull/25041))
|
||||
- Exchange Rate not getting set in Salary Slip ([#25004](https://github.com/frappe/erpnext/pull/25004))
|
||||
- Repost not completed backdated transactions ([#24980](https://github.com/frappe/erpnext/pull/24980))
|
||||
- frappe.whitelist for doc methods ([#25230](https://github.com/frappe/erpnext/pull/25230))
|
||||
- Opportunity-quotation mapping order status ([#25001](https://github.com/frappe/erpnext/pull/25001))
|
||||
- GST on freight charge in e-invoicing ([#25000](https://github.com/frappe/erpnext/pull/25000))
|
||||
- Role to override maintain same rate check in transactions ([#25193](https://github.com/frappe/erpnext/pull/25193))
|
||||
- Added blank option for status in report related to issue ([#25082](https://github.com/frappe/erpnext/pull/25082))
|
||||
- Cashier query in POS Opening/Closing Entry ([#25399](https://github.com/frappe/erpnext/pull/25399))
|
||||
- Lead Source's module ([#24583](https://github.com/frappe/erpnext/pull/24583))
|
||||
- Hide alt tag if item is not shown in website ([#24937](https://github.com/frappe/erpnext/pull/24937))
|
||||
- Ignore Customer Group Perm on All Products page ([#25397](https://github.com/frappe/erpnext/pull/25397))
|
||||
- Give first preference to loan security on repayment ([#25212](https://github.com/frappe/erpnext/pull/25212))
|
||||
- Add shortfall ratio in Loan Security Shortfall ([#25138](https://github.com/frappe/erpnext/pull/25138))
|
||||
- Condition for SLA status banner ([#25261](https://github.com/frappe/erpnext/pull/25261))
|
||||
- Component amount calculation based on formula with abbr not working ([#25117](https://github.com/frappe/erpnext/pull/25117))
|
||||
- Remove gst name validation for purchase Invoice ([#25235](https://github.com/frappe/erpnext/pull/25235))
|
||||
- Do not fetch stopped MR in production plan ([#25063](https://github.com/frappe/erpnext/pull/25063))
|
||||
- Backport missing commits to develop branch ([#25305](https://github.com/frappe/erpnext/pull/25305))
|
||||
- UOM length unit in global setup list is empty ([#24855](https://github.com/frappe/erpnext/pull/24855))
|
||||
- Round total quantity in job card ([#25240](https://github.com/frappe/erpnext/pull/25240))
|
||||
- Default total_estimated_cost to zero ([#24939](https://github.com/frappe/erpnext/pull/24939))
|
||||
- Serial no refresh issue ([#25127](https://github.com/frappe/erpnext/pull/25127))
|
||||
- Correct calculation for discount amount when margin is set ([#25179](https://github.com/frappe/erpnext/pull/25179))
|
||||
- Get correct holiday list when calculating dates; test fixes ([#24901](https://github.com/frappe/erpnext/pull/24901))
|
||||
- POS print receipt ([#24924](https://github.com/frappe/erpnext/pull/24924))
|
||||
- Condition for setting agreement status ([#25255](https://github.com/frappe/erpnext/pull/25255))
|
||||
- Loan Repayment entry cancellation on salary slip cancel ([#24879](https://github.com/frappe/erpnext/pull/24879))
|
||||
- Add company validation for e-invoicing ([#25349](https://github.com/frappe/erpnext/pull/25349))
|
||||
- Query values incorrectly escaped while back updating Quality Inspection ([#25118](https://github.com/frappe/erpnext/pull/25118))
|
||||
- Update Bin via Update Item on Purchase/Sales Order ([#23509](https://github.com/frappe/erpnext/pull/23509))
|
||||
- Declare data before assigning ([#25287](https://github.com/frappe/erpnext/pull/25287))
|
||||
- Do not set standard link in Sales Invoice as custom ([#25096](https://github.com/frappe/erpnext/pull/25096))
|
||||
- Hide serial and batch selector in Stock Entry ([#25107](https://github.com/frappe/erpnext/pull/25107))
|
||||
- Taxable value including Freight and Forwarding charges in GSTR-1 Report ([#25290](https://github.com/frappe/erpnext/pull/25290))
|
||||
- Remove nonexistent method from pick list ([#25279](https://github.com/frappe/erpnext/pull/25279))
|
||||
- Allow zero valuation in stock reconciliation ([#24888](https://github.com/frappe/erpnext/pull/24888))
|
||||
- Place of supply of e-invoicing ([#25148](https://github.com/frappe/erpnext/pull/25148))
|
||||
- Delivery note print error ([#25080](https://github.com/frappe/erpnext/pull/25080))
|
||||
- Fix Payment references from disappearing on adding Cost Center in Payment Entry ([#24831](https://github.com/frappe/erpnext/pull/24831))
|
||||
- Company field in Warehouse ([#25196](https://github.com/frappe/erpnext/pull/25196))
|
||||
- Available employee for selection ([#25378](https://github.com/frappe/erpnext/pull/25378))
|
||||
- Cannot set qty to less than zero ([#25258](https://github.com/frappe/erpnext/pull/25258))
|
||||
- Don't delete mode of payment account details while deleting comp… ([#25217](https://github.com/frappe/erpnext/pull/25217))
|
||||
- Exclude current doc while validation. ([#24914](https://github.com/frappe/erpnext/pull/24914))
|
||||
- POS Opening Entry with empty balance detail rows ([#24876](https://github.com/frappe/erpnext/pull/24876))
|
||||
- Unable to submit stock entry ([#25033](https://github.com/frappe/erpnext/pull/25033))
|
||||
- BOM cost test case ([#25242](https://github.com/frappe/erpnext/pull/25242))
|
||||
- Filter for employees in salary slip ([#25361](https://github.com/frappe/erpnext/pull/25361))
|
||||
- Added correct path in hooks ([#24862](https://github.com/frappe/erpnext/pull/24862))
|
||||
- Patch regional fields for old companies ([#24988](https://github.com/frappe/erpnext/pull/24988))
|
||||
- consolidated sales invoice posting date ([#25119](https://github.com/frappe/erpnext/pull/25119))
|
||||
- Don't set "Company:company:default_currency" as default for currency link fields ([#25095](https://github.com/frappe/erpnext/pull/25095))
|
||||
- Healthcare lab module rename fields ([#25276](https://github.com/frappe/erpnext/pull/25276))
|
||||
- Error message compensatory leave request ([#25206](https://github.com/frappe/erpnext/pull/25206))
|
||||
- Adding company link to e invoice settings patch condition ([#25301](https://github.com/frappe/erpnext/pull/25301))
|
||||
- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900))
|
||||
- Set correct ack no. on irn generation ([#25251](https://github.com/frappe/erpnext/pull/25251))
|
||||
- Report Issue Summary fix for zero issues ([#24934](https://github.com/frappe/erpnext/pull/24934))
|
||||
- Validation msg for TransDocNo e-invoicing ([#25121](https://github.com/frappe/erpnext/pull/25121))
|
||||
- Correct state code for 'Other Territory' ([#24993](https://github.com/frappe/erpnext/pull/24993))
|
||||
- Commit individual SLE rename for large datasets (develop) ([#25084](https://github.com/frappe/erpnext/pull/25084))
|
||||
- Remove shipping address GSTIN validation for e-invoice ([#25153](https://github.com/frappe/erpnext/pull/25153))
|
||||
- Period list for exponential smoothing forecasting report ([#24982](https://github.com/frappe/erpnext/pull/24982))
|
||||
- Customer creation from shopping cart ([#25136](https://github.com/frappe/erpnext/pull/25136))
|
||||
- Simplified logic for additional salary ([#24824](https://github.com/frappe/erpnext/pull/24824))
|
||||
- Item wise tax rate for consolidated POS invoice ([#25029](https://github.com/frappe/erpnext/pull/25029))
|
||||
- Column width in Recruitment analytics report ([#25003](https://github.com/frappe/erpnext/pull/25003))
|
||||
- Filter Bank Account drop-down list in Bank Reconciliation Tool ([#24873](https://github.com/frappe/erpnext/pull/24873))
|
||||
- Payroll issues ([#24540](https://github.com/frappe/erpnext/pull/24540))
|
||||
- PO not created against all selected suppliers (drop shipping) ([#24863](https://github.com/frappe/erpnext/pull/24863))
|
||||
- Can't multiply sequence by non-int of type 'float' ([#25092](https://github.com/frappe/erpnext/pull/25092))
|
||||
- Make Discharge Schedule Date as Datetime ([#24940](https://github.com/frappe/erpnext/pull/24940))
|
||||
- Serial no trim issue ([#24949](https://github.com/frappe/erpnext/pull/24949))
|
@ -90,6 +90,8 @@ class AccountsController(TransactionBase):
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
|
||||
self.validate_date_with_fiscal_year()
|
||||
self.validate_party_accounts()
|
||||
|
||||
self.validate_inter_company_reference()
|
||||
|
||||
self.set_incoming_rate()
|
||||
@ -233,6 +235,23 @@ class AccountsController(TransactionBase):
|
||||
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
|
||||
self.meta.get_label(date_field), self)
|
||||
|
||||
def validate_party_accounts(self):
|
||||
if self.doctype not in ('Sales Invoice', 'Purchase Invoice'):
|
||||
return
|
||||
|
||||
if self.doctype == 'Sales Invoice':
|
||||
party_account_field = 'debit_to'
|
||||
item_field = 'income_account'
|
||||
else:
|
||||
party_account_field = 'credit_to'
|
||||
item_field = 'expense_account'
|
||||
|
||||
for item in self.get('items'):
|
||||
if item.get(item_field) == self.get(party_account_field):
|
||||
frappe.throw(_("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(item.idx,
|
||||
frappe.bold(frappe.unscrub(item_field)), item.get(item_field),
|
||||
frappe.bold(frappe.unscrub(party_account_field)), self.get(party_account_field)))
|
||||
|
||||
def validate_inter_company_reference(self):
|
||||
if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
||||
return
|
||||
@ -240,7 +259,7 @@ class AccountsController(TransactionBase):
|
||||
if self.is_internal_transfer():
|
||||
if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
|
||||
or self.get('inter_company_order_reference')):
|
||||
msg = _("Internal Sale or Delivery Reference missing. ")
|
||||
msg = _("Internal Sale or Delivery Reference missing.")
|
||||
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||
|
||||
@ -717,7 +736,9 @@ class AccountsController(TransactionBase):
|
||||
total_billed_amt = abs(total_billed_amt)
|
||||
max_allowed_amt = abs(max_allowed_amt)
|
||||
|
||||
if total_billed_amt - max_allowed_amt > 0.01:
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
|
||||
|
||||
if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
|
||||
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
|
||||
.format(item.item_code, item.idx, max_allowed_amt))
|
||||
|
||||
|
@ -262,7 +262,8 @@ def copy_attributes_to_variant(item, variant):
|
||||
# copy non no-copy fields
|
||||
|
||||
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
|
||||
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"]
|
||||
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate",
|
||||
"has_variants", "attributes"]
|
||||
|
||||
if item.variant_based_on=='Manufacturer':
|
||||
# don't copy manufacturer values if based on part no
|
||||
|
@ -713,7 +713,9 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||
return [(d,) for d in set(taxes)]
|
||||
|
||||
|
||||
def get_fields(doctype, fields=[]):
|
||||
def get_fields(doctype, fields=None):
|
||||
if fields is None:
|
||||
fields = []
|
||||
meta = frappe.get_meta(doctype)
|
||||
fields.extend(meta.get_search_fields())
|
||||
|
||||
|
@ -201,10 +201,14 @@ class StatusUpdater(Document):
|
||||
get_allowance_for(item['item_code'], self.item_allowance,
|
||||
self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
|
||||
|
||||
role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive')
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
|
||||
role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill
|
||||
|
||||
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
|
||||
item[args['target_ref_field']]) * 100
|
||||
|
||||
if overflow_percent - allowance > 0.01:
|
||||
if overflow_percent - allowance > 0.01 and role not in frappe.get_roles():
|
||||
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
|
||||
item['reduce_by'] = item[args['target_field']] - item['max_allowed']
|
||||
|
||||
@ -371,10 +375,12 @@ class StatusUpdater(Document):
|
||||
ref_doc.db_set("per_billed", per_billed)
|
||||
ref_doc.set_status(update=True)
|
||||
|
||||
def get_allowance_for(item_code, item_allowance={}, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
|
||||
def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
|
||||
"""
|
||||
Returns the allowance for the item, if not set, returns global allowance
|
||||
"""
|
||||
if item_allowance is None:
|
||||
item_allowance = {}
|
||||
if qty_or_amount == "qty":
|
||||
if item_allowance.get(item_code, frappe._dict()).get("qty"):
|
||||
return item_allowance[item_code].qty, item_allowance, global_qty_allowance, global_amount_allowance
|
||||
|
@ -117,7 +117,6 @@ class StockController(AccountsController):
|
||||
"account": expense_account,
|
||||
"against": warehouse_account[sle.warehouse]["account"],
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or self.get('project'),
|
||||
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
||||
"credit": flt(sle.stock_value_difference, precision),
|
||||
"project": item_row.get("project") or self.get("project"),
|
||||
@ -483,7 +482,7 @@ class StockController(AccountsController):
|
||||
)
|
||||
message += "<br><br>"
|
||||
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
|
||||
message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||
return message
|
||||
|
||||
def repost_future_sle_and_gle(self):
|
||||
|
@ -41,7 +41,7 @@ class CourseEnrollment(Document):
|
||||
frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format(
|
||||
get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry'))
|
||||
|
||||
def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status):
|
||||
def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status, time_taken):
|
||||
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
|
||||
result_data = []
|
||||
for key in answers:
|
||||
@ -66,7 +66,8 @@ class CourseEnrollment(Document):
|
||||
"activity_date": frappe.utils.datetime.datetime.now(),
|
||||
"result": result_data,
|
||||
"score": score,
|
||||
"status": status
|
||||
"status": status,
|
||||
"time_taken": time_taken
|
||||
}).insert(ignore_permissions = True)
|
||||
|
||||
def add_activity(self, content_type, content):
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
@ -12,7 +13,10 @@
|
||||
"quiz_configuration_section",
|
||||
"passing_score",
|
||||
"max_attempts",
|
||||
"grading_basis"
|
||||
"grading_basis",
|
||||
"column_break_7",
|
||||
"is_time_bound",
|
||||
"duration"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -58,9 +62,26 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Grading Basis",
|
||||
"options": "Latest Highest Score\nLatest Attempt"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_time_bound",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Time-Bound"
|
||||
},
|
||||
{
|
||||
"depends_on": "is_time_bound",
|
||||
"fieldname": "duration",
|
||||
"fieldtype": "Duration",
|
||||
"label": "Duration"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"modified": "2019-06-12 12:23:57.020508",
|
||||
"links": [],
|
||||
"modified": "2020-12-24 15:41:35.043262",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Quiz",
|
||||
|
@ -1,490 +1,163 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "format:EDU-QA-{YYYY}-{#####}",
|
||||
"beta": 1,
|
||||
"creation": "2018-10-15 15:48:40.482821",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enrollment",
|
||||
"student",
|
||||
"column_break_3",
|
||||
"course",
|
||||
"section_break_5",
|
||||
"quiz",
|
||||
"column_break_7",
|
||||
"status",
|
||||
"section_break_9",
|
||||
"result",
|
||||
"section_break_11",
|
||||
"activity_date",
|
||||
"score",
|
||||
"column_break_14",
|
||||
"time_taken"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "enrollment",
|
||||
"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": "Enrollment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Course Enrollment",
|
||||
"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": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "enrollment.student",
|
||||
"fieldname": "student",
|
||||
"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": "Student",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "enrollment.course",
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Course",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Course",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "quiz",
|
||||
"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": "Quiz",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Quiz",
|
||||
"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": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nPass\nFail",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "result",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Result",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Quiz Result",
|
||||
"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": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "activity_date",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Activity Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "score",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Score",
|
||||
"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": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "time_taken",
|
||||
"fieldtype": "Duration",
|
||||
"label": "Time Taken",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-11-25 19:05:52.434437",
|
||||
"links": [],
|
||||
"modified": "2020-12-24 15:41:20.085380",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Quiz Activity",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Academics User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "LMS User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Instructor",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
@ -114,7 +114,7 @@ class Student(Document):
|
||||
status = check_content_completion(content.name, content.doctype, course_enrollment_name)
|
||||
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status})
|
||||
elif content.doctype == 'Quiz':
|
||||
status, score, result = check_quiz_completion(content, course_enrollment_name)
|
||||
status, score, result, time_taken = check_quiz_completion(content, course_enrollment_name)
|
||||
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result})
|
||||
return progress
|
||||
|
||||
|
@ -194,7 +194,7 @@ def add_activity(course, content_type, content, program):
|
||||
return enrollment.add_activity(content_type, content)
|
||||
|
||||
@frappe.whitelist()
|
||||
def evaluate_quiz(quiz_response, quiz_name, course, program):
|
||||
def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken):
|
||||
import json
|
||||
|
||||
student = get_current_student()
|
||||
@ -209,7 +209,7 @@ def evaluate_quiz(quiz_response, quiz_name, course, program):
|
||||
if student:
|
||||
enrollment = get_or_create_course_enrollment(course, program)
|
||||
if quiz.allowed_attempt(enrollment, quiz_name):
|
||||
enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status)
|
||||
enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status, time_taken)
|
||||
return {'result': result, 'score': score, 'status': status}
|
||||
else:
|
||||
return None
|
||||
@ -219,8 +219,9 @@ def get_quiz(quiz_name, course):
|
||||
try:
|
||||
quiz = frappe.get_doc("Quiz", quiz_name)
|
||||
questions = quiz.get_questions()
|
||||
duration = quiz.duration
|
||||
except:
|
||||
frappe.throw(_("Quiz {0} does not exist").format(quiz_name))
|
||||
frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError)
|
||||
return None
|
||||
|
||||
questions = [{
|
||||
@ -232,12 +233,20 @@ def get_quiz(quiz_name, course):
|
||||
} for question in questions]
|
||||
|
||||
if has_super_access():
|
||||
return {'questions': questions, 'activity': None}
|
||||
return {
|
||||
'questions': questions,
|
||||
'activity': None,
|
||||
'duration':duration
|
||||
}
|
||||
|
||||
student = get_current_student()
|
||||
course_enrollment = get_enrollment("course", course, student.name)
|
||||
status, score, result = check_quiz_completion(quiz, course_enrollment)
|
||||
return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}}
|
||||
status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment)
|
||||
return {
|
||||
'questions': questions,
|
||||
'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken},
|
||||
'duration': quiz.duration
|
||||
}
|
||||
|
||||
def get_topic_progress(topic, course_name, program):
|
||||
"""
|
||||
@ -361,15 +370,23 @@ def check_content_completion(content_name, content_type, enrollment_name):
|
||||
return False
|
||||
|
||||
def check_quiz_completion(quiz, enrollment_name):
|
||||
attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"])
|
||||
attempts = frappe.get_all("Quiz Activity",
|
||||
filters={
|
||||
'enrollment': enrollment_name,
|
||||
'quiz': quiz.name
|
||||
},
|
||||
fields=["name", "activity_date", "score", "status", "time_taken"]
|
||||
)
|
||||
status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts)
|
||||
score = None
|
||||
result = None
|
||||
time_taken = None
|
||||
if attempts:
|
||||
if quiz.grading_basis == 'Last Highest Score':
|
||||
attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True)
|
||||
score = attempts[0]['score']
|
||||
result = attempts[0]['status']
|
||||
time_taken = attempts[0]['time_taken']
|
||||
if result == 'Pass':
|
||||
status = True
|
||||
return status, score, result
|
||||
return status, score, result, time_taken
|
@ -17,10 +17,12 @@ class AmazonMWSSettings(Document):
|
||||
else:
|
||||
self.enable_sync = 0
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_products_details(self):
|
||||
if self.enable_amazon == 1:
|
||||
frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details')
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_order_details(self):
|
||||
if self.enable_amazon == 1:
|
||||
after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d")
|
||||
|
@ -1,206 +1,64 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2018-07-12 12:07:36.932333",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"service_unit",
|
||||
"check_in",
|
||||
"left",
|
||||
"check_out",
|
||||
"invoiced"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "service_unit",
|
||||
"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": "Healthcare Service Unit",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Healthcare Service Unit",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "check_in",
|
||||
"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": "Check In",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Check In"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "left",
|
||||
"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": "Left",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "check_out",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Check Out",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Check Out"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "invoiced",
|
||||
"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": "Invoiced",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-11-04 03:33:26.958713",
|
||||
"links": [],
|
||||
"modified": "2021-03-18 15:08:54.634132",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Inpatient Occupancy",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Healthcare",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
@ -185,7 +185,7 @@
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
"label": "Admitted Datetime",
|
||||
"read_only": 1
|
||||
"permlevel": 2
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.expected_length_of_stay > 0)",
|
||||
@ -312,7 +312,7 @@
|
||||
"fieldname": "inpatient_occupancies",
|
||||
"fieldtype": "Table",
|
||||
"options": "Inpatient Occupancy",
|
||||
"read_only": 1
|
||||
"permlevel": 2
|
||||
},
|
||||
{
|
||||
"fieldname": "btn_transfer",
|
||||
@ -407,12 +407,12 @@
|
||||
"fieldname": "discharge_datetime",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Discharge Date",
|
||||
"read_only": 1
|
||||
"permlevel": 2
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-18 14:44:11.689956",
|
||||
"modified": "2021-03-18 15:59:17.318988",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Inpatient Record",
|
||||
@ -465,6 +465,37 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Nursing User"
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"permlevel": 2,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Healthcare Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"permlevel": 2,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Physician",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"permlevel": 2,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Nursing User",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"restrict_to_domain": "Healthcare",
|
||||
|
@ -50,6 +50,7 @@ class TherapyType(Document):
|
||||
|
||||
self.db_set('change_in_item', 0)
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_exercises(self):
|
||||
exercises = self.get_exercises_for_body_parts()
|
||||
last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,])
|
||||
|
@ -66,7 +66,7 @@ class CompensatoryLeaveRequest(Document):
|
||||
|
||||
else:
|
||||
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
|
||||
self.leave_allocation=leave_allocation.name
|
||||
self.db_set("leave_allocation", leave_allocation.name)
|
||||
else:
|
||||
frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
|
||||
|
||||
|
@ -34,7 +34,7 @@ frappe.ui.form.on('Employee Advance', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('salary_component', function(doc) {
|
||||
frm.set_query('salary_component', function() {
|
||||
return {
|
||||
filters: {
|
||||
"type": "Deduction"
|
||||
@ -44,34 +44,35 @@ frappe.ui.form.on('Employee Advance', {
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.docstatus===1
|
||||
&& (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount))
|
||||
&& frappe.model.can_create("Payment Entry")) {
|
||||
if (frm.doc.docstatus === 1 &&
|
||||
(flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) &&
|
||||
frappe.model.can_create("Payment Entry")) {
|
||||
frm.add_custom_button(__('Payment'),
|
||||
function() { frm.events.make_payment_entry(frm); }, __('Create'));
|
||||
}
|
||||
else if (
|
||||
frm.doc.docstatus === 1
|
||||
&& flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount)
|
||||
&& frappe.model.can_create("Expense Claim")
|
||||
function () {
|
||||
frm.events.make_payment_entry(frm);
|
||||
}, __('Create'));
|
||||
} else if (
|
||||
frm.doc.docstatus === 1 &&
|
||||
flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) &&
|
||||
frappe.model.can_create("Expense Claim")
|
||||
) {
|
||||
frm.add_custom_button(
|
||||
__("Expense Claim"),
|
||||
function() {
|
||||
function () {
|
||||
frm.events.make_expense_claim(frm);
|
||||
},
|
||||
__('Create')
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus === 1
|
||||
&& (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
|
||||
if (frm.doc.docstatus === 1 &&
|
||||
(flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
|
||||
|
||||
if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")){
|
||||
if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) {
|
||||
frm.add_custom_button(__("Return"), function() {
|
||||
frm.trigger('make_return_entry');
|
||||
}, __('Create'));
|
||||
}else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
|
||||
} else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) {
|
||||
frm.add_custom_button(__("Deduction from salary"), function() {
|
||||
frm.events.make_deduction_via_additional_salary(frm);
|
||||
}, __('Create'));
|
||||
@ -79,13 +80,13 @@ frappe.ui.form.on('Employee Advance', {
|
||||
}
|
||||
},
|
||||
|
||||
make_deduction_via_additional_salary: function(frm){
|
||||
make_deduction_via_additional_salary: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.employee_advance.employee_advance.create_return_through_additional_salary",
|
||||
args: {
|
||||
doc: frm.doc
|
||||
},
|
||||
callback: function (r){
|
||||
callback: function(r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
@ -94,7 +95,7 @@ frappe.ui.form.on('Employee Advance', {
|
||||
|
||||
make_payment_entry: function(frm) {
|
||||
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
||||
if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
|
||||
if (frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
|
||||
method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry";
|
||||
}
|
||||
return frappe.call({
|
||||
@ -148,7 +149,7 @@ frappe.ui.form.on('Employee Advance', {
|
||||
});
|
||||
},
|
||||
|
||||
employee: function (frm) {
|
||||
employee: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frappe.run_serially([
|
||||
() => frm.trigger('get_employee_currency'),
|
||||
@ -199,7 +200,7 @@ frappe.ui.form.on('Employee Advance', {
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property('exchange_rate', 'hidden', 1);
|
||||
frm.set_df_property("exchange_rate", "description", "" );
|
||||
frm.set_df_property("exchange_rate", "description", "");
|
||||
}
|
||||
frm.refresh_fields();
|
||||
}
|
||||
@ -215,8 +216,8 @@ frappe.ui.form.on('Employee Advance', {
|
||||
callback: function(r) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property('exchange_rate', 'hidden', 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
|
||||
" = [?] " + company_currency);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
0
erpnext/hr/doctype/employee_referral/__init__.py
Normal file
0
erpnext/hr/doctype/employee_referral/__init__.py
Normal file
68
erpnext/hr/doctype/employee_referral/employee_referral.js
Normal file
68
erpnext/hr/doctype/employee_referral/employee_referral.js
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Employee Referral", {
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.docstatus === 1 && frm.doc.status === "Pending") {
|
||||
frm.add_custom_button(__("Reject Employee Referral"), function() {
|
||||
frappe.confirm(
|
||||
__("Are you sure you want to reject the Employee Referral?"),
|
||||
function() {
|
||||
frm.doc.status = "Rejected";
|
||||
frm.dirty();
|
||||
frm.save_or_update();
|
||||
},
|
||||
function() {
|
||||
window.close();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Create Job Applicant"), function() {
|
||||
frm.events.create_job_applicant(frm);
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
|
||||
// To check whether Payment is done or not
|
||||
if (frm.doc.docstatus === 1 && frm.doc.status === "Accepted") {
|
||||
frappe.db.get_list("Additional Salary", {
|
||||
filters: {
|
||||
ref_docname: cur_frm.doc.name,
|
||||
docstatus: 1
|
||||
},
|
||||
fields: ["count(name) as additional_salary_count"]
|
||||
}).then((data) => {
|
||||
|
||||
let additional_salary_count = data[0].additional_salary_count;
|
||||
|
||||
if (frm.doc.is_applicable_for_referral_bonus && !additional_salary_count) {
|
||||
frm.add_custom_button(__("Create Additional Salary"), function() {
|
||||
frm.events.create_additional_salary(frm);
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
},
|
||||
create_job_applicant: function(frm) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.hr.doctype.employee_referral.employee_referral.create_job_applicant",
|
||||
frm: frm
|
||||
});
|
||||
},
|
||||
|
||||
create_additional_salary: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.employee_referral.employee_referral.create_additional_salary",
|
||||
args: {
|
||||
doc: frm.doc
|
||||
},
|
||||
callback: function (r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
294
erpnext/hr/doctype/employee_referral/employee_referral.json
Normal file
294
erpnext/hr/doctype/employee_referral/employee_referral.json
Normal file
@ -0,0 +1,294 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "format:HR-REF-{####}",
|
||||
"creation": "2021-03-23 14:54:45.047051",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"first_name",
|
||||
"last_name",
|
||||
"full_name",
|
||||
"email",
|
||||
"contact_no",
|
||||
"resume",
|
||||
"resume_link",
|
||||
"column_break_6",
|
||||
"date",
|
||||
"status",
|
||||
"for_designation",
|
||||
"current_employer",
|
||||
"current_job_title",
|
||||
"referrer_details_section",
|
||||
"referrer",
|
||||
"referrer_name",
|
||||
"column_break_14",
|
||||
"is_applicable_for_referral_bonus",
|
||||
"referral_payment_status",
|
||||
"department",
|
||||
"additional_information_section",
|
||||
"qualification_reason",
|
||||
"work_references",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "first_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "First Name ",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "last_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Last Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "full_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Full Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "contact_no",
|
||||
"fieldtype": "Data",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Contact No.",
|
||||
"options": "Phone"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_employer",
|
||||
"fieldtype": "Data",
|
||||
"label": "Current Employer "
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "Pending\nIn Process\nAccepted\nRejected",
|
||||
"permlevel": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "current_job_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Current Job Title"
|
||||
},
|
||||
{
|
||||
"fieldname": "resume",
|
||||
"fieldtype": "Attach",
|
||||
"label": "Resume"
|
||||
},
|
||||
{
|
||||
"fieldname": "referrer_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Referrer Details"
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.department",
|
||||
"fieldname": "department",
|
||||
"fieldtype": "Link",
|
||||
"label": "Department",
|
||||
"options": "Department",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_information_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Additional Information "
|
||||
},
|
||||
{
|
||||
"fieldname": "work_references",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Work References"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Employee Referral",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "for_designation",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "For Designation ",
|
||||
"options": "Designation",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "email",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Email",
|
||||
"options": "Email",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "is_applicable_for_referral_bonus",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Applicable for Referral Bonus"
|
||||
},
|
||||
{
|
||||
"fieldname": "qualification_reason",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Why is this Candidate Qualified for this Position?"
|
||||
},
|
||||
{
|
||||
"fieldname": "referrer",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Referrer",
|
||||
"options": "Employee",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "referrer.employee_name",
|
||||
"fieldname": "referrer_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Referrer Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "resume_link",
|
||||
"fieldtype": "Data",
|
||||
"label": "Resume Link"
|
||||
},
|
||||
{
|
||||
"fieldname": "referral_payment_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Referral Bonus Payment Status",
|
||||
"options": "\nUnpaid\nPaid",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-26 21:21:38.094086",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Referral",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"permlevel": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"permlevel": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"permlevel": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "full_name"
|
||||
}
|
71
erpnext/hr/doctype/employee_referral/employee_referral.py
Normal file
71
erpnext/hr/doctype/employee_referral/employee_referral.py
Normal file
@ -0,0 +1,71 @@
|
||||
# -*- 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 import _
|
||||
from frappe.utils import get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
|
||||
class EmployeeReferral(Document):
|
||||
def validate(self):
|
||||
self.set_full_name()
|
||||
self.set_referral_bonus_payment_status()
|
||||
|
||||
def set_full_name(self):
|
||||
self.full_name = " ".join(filter(None, [self.first_name, self.last_name]))
|
||||
|
||||
def set_referral_bonus_payment_status(self):
|
||||
if not self.is_applicable_for_referral_bonus:
|
||||
self.referral_payment_status = ""
|
||||
else:
|
||||
if not self.referral_payment_status:
|
||||
self.referral_payment_status = "Unpaid"
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_job_applicant(source_name, target_doc=None):
|
||||
emp_ref = frappe.get_doc("Employee Referral", source_name)
|
||||
#just for Api call if some set status apart from default Status
|
||||
status = emp_ref.status
|
||||
if emp_ref.status in ["Pending", "In process"]:
|
||||
status = "Open"
|
||||
|
||||
job_applicant = frappe.new_doc("Job Applicant")
|
||||
job_applicant.employee_referral = emp_ref.name
|
||||
job_applicant.status = status
|
||||
job_applicant.applicant_name = emp_ref.full_name
|
||||
job_applicant.email_id = emp_ref.email
|
||||
job_applicant.phone_number = emp_ref.contact_no
|
||||
job_applicant.resume_attachment = emp_ref.resume
|
||||
job_applicant.resume_link = emp_ref.resume_link
|
||||
job_applicant.save()
|
||||
|
||||
frappe.msgprint(_("Job Applicant {0} created successfully.").format(
|
||||
get_link_to_form("Job Applicant", job_applicant.name)),
|
||||
title=_("Success"), indicator="green")
|
||||
|
||||
emp_ref.db_set("status", "In Process")
|
||||
|
||||
return job_applicant
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_additional_salary(doc):
|
||||
import json
|
||||
from six import string_types
|
||||
|
||||
if isinstance(doc, string_types):
|
||||
doc = frappe._dict(json.loads(doc))
|
||||
|
||||
if not frappe.db.exists("Additional Salary", {"ref_docname": doc.name}):
|
||||
additional_salary = frappe.new_doc("Additional Salary")
|
||||
additional_salary.employee = doc.referrer
|
||||
additional_salary.company = frappe.db.get_value("Employee", doc.referrer, "company")
|
||||
additional_salary.overwrite_salary_structure_amount = 0
|
||||
additional_salary.ref_doctype = doc.doctype
|
||||
additional_salary.ref_docname = doc.name
|
||||
|
||||
return additional_salary
|
||||
|
@ -0,0 +1,15 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'employee_referral',
|
||||
'non_standard_fieldnames': {
|
||||
'Additional Salary': 'ref_docname'
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
'items': ['Job Applicant', 'Additional Salary']
|
||||
},
|
||||
|
||||
]
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
frappe.listview_settings['Employee Referral'] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function (doc) {
|
||||
if (doc.status == "Pending") {
|
||||
return [__(doc.status), "grey", "status,=," + doc.status];
|
||||
} else if (doc.status == "In Process") {
|
||||
return [__(doc.status), "orange", "status,=," + doc.status];
|
||||
} else if (doc.status == "Accepted") {
|
||||
return [__(doc.status), "green", "status,=," + doc.status];
|
||||
} else if (doc.status == "Rejected") {
|
||||
return [__(doc.status), "red", "status,=," + doc.status];
|
||||
}
|
||||
},
|
||||
};
|
@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.utils import today
|
||||
from erpnext.hr.doctype.designation.test_designation import create_designation
|
||||
from erpnext.hr.doctype.employee_referral.employee_referral import create_job_applicant, create_additional_salary
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
import unittest
|
||||
|
||||
class TestEmployeeReferral(unittest.TestCase):
|
||||
def test_workflow_and_status_sync(self):
|
||||
emp_ref = create_employee_referral()
|
||||
|
||||
#Check Initial status
|
||||
self.assertTrue(emp_ref.status, "Pending")
|
||||
|
||||
job_applicant = create_job_applicant(emp_ref.name)
|
||||
|
||||
|
||||
#Check status sync
|
||||
emp_ref.reload()
|
||||
self.assertTrue(emp_ref.status, "In Process")
|
||||
|
||||
job_applicant.reload()
|
||||
job_applicant.status = "Rejected"
|
||||
job_applicant.save()
|
||||
|
||||
emp_ref.reload()
|
||||
self.assertTrue(emp_ref.status, "Rejected")
|
||||
|
||||
job_applicant.reload()
|
||||
job_applicant.status = "Accepted"
|
||||
job_applicant.save()
|
||||
|
||||
emp_ref.reload()
|
||||
self.assertTrue(emp_ref.status, "Accepted")
|
||||
|
||||
|
||||
# Check for Referral reference in additional salary
|
||||
|
||||
add_sal = create_additional_salary(emp_ref)
|
||||
self.assertTrue(add_sal.ref_docname, emp_ref.name)
|
||||
|
||||
|
||||
def create_employee_referral():
|
||||
emp_ref = frappe.new_doc("Employee Referral")
|
||||
emp_ref.first_name = "Mahesh"
|
||||
emp_ref.last_name = "Singh"
|
||||
emp_ref.email = "a@b.c"
|
||||
emp_ref.date = today()
|
||||
emp_ref.for_designation = create_designation().name
|
||||
emp_ref.referrer = make_employee("testassetmovemp@example.com", company="_Test Company")
|
||||
emp_ref.is_applicable_for_employee_referral_compensation = 1
|
||||
emp_ref.save()
|
||||
emp_ref.submit()
|
||||
|
||||
return emp_ref
|
@ -16,6 +16,7 @@ class HolidayList(Document):
|
||||
self.validate_days()
|
||||
self.total_holidays = len(self.holidays)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_weekly_off_dates(self):
|
||||
self.validate_values()
|
||||
date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
|
||||
@ -61,6 +62,7 @@ class HolidayList(Document):
|
||||
|
||||
return date_list
|
||||
|
||||
@frappe.whitelist()
|
||||
def clear_table(self):
|
||||
self.set('holidays', [])
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
"retirement_age",
|
||||
"emp_created_by",
|
||||
"column_break_4",
|
||||
"standard_working_hours",
|
||||
"stop_birthday_reminders",
|
||||
"expense_approver_mandatory_in_expense_claim",
|
||||
"leave_settings",
|
||||
@ -143,13 +144,18 @@
|
||||
"fieldname": "send_leave_notification",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send Leave Notification"
|
||||
},
|
||||
{
|
||||
"fieldname": "standard_working_hours",
|
||||
"fieldtype": "Int",
|
||||
"label": "Standard Working Hours"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-14 02:04:22.907159",
|
||||
"modified": "2021-04-26 10:52:56.192773",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "HR Settings",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"job_title",
|
||||
"source",
|
||||
"source_name",
|
||||
"employee_referral",
|
||||
"applicant_rating",
|
||||
"section_break_6",
|
||||
"notes",
|
||||
@ -152,13 +153,20 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "employee_referral",
|
||||
"fieldtype": "Link",
|
||||
"label": "Employee Referral",
|
||||
"options": "Employee Referral",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-18 12:39:02.557563",
|
||||
"modified": "2021-03-24 15:51:11.117517",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Job Applicant",
|
||||
|
@ -28,10 +28,21 @@ class JobApplicant(Document):
|
||||
if self.email_id:
|
||||
validate_email_address(self.email_id, True)
|
||||
|
||||
if self.employee_referral:
|
||||
self.set_status_for_employee_referral()
|
||||
|
||||
if not self.applicant_name and self.email_id:
|
||||
guess = self.email_id.split('@')[0]
|
||||
self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')])
|
||||
|
||||
def set_status_for_employee_referral(self):
|
||||
emp_ref = frappe.get_doc("Employee Referral", self.employee_referral)
|
||||
if self.status in ["Open", "Replied", "Hold"]:
|
||||
emp_ref.db_set("status", "In Process")
|
||||
elif self.status in ["Accepted", "Rejected"]:
|
||||
emp_ref.db_set("status", self.status)
|
||||
|
||||
|
||||
def check_email_id_is_unique(self):
|
||||
if self.email_id:
|
||||
names = frappe.db.sql_list("""select name from `tabJob Applicant`
|
||||
|
@ -218,8 +218,7 @@
|
||||
"fieldname": "leave_policy_assignment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Leave Policy Assignment",
|
||||
"options": "Leave Policy Assignment",
|
||||
"read_only": 1
|
||||
"options": "Leave Policy Assignment"
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.company",
|
||||
@ -236,7 +235,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-04 18:46:13.184104",
|
||||
"modified": "2021-04-14 15:28:26.335104",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Allocation",
|
||||
|
@ -29,6 +29,7 @@ class LeaveControlPanel(Document):
|
||||
frappe.throw(_("{0} is required").format(self.meta.get_label(f)))
|
||||
self.validate_from_to_dates('from_date', 'to_date')
|
||||
|
||||
@frappe.whitelist()
|
||||
def allocate_leave(self):
|
||||
self.validate_values()
|
||||
leave_allocated_for = []
|
||||
|
@ -520,6 +520,15 @@
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Referral",
|
||||
"link_to": "Employee Referral",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
@ -814,7 +823,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-03-24 17:35:21.483297",
|
||||
"modified": "2021-04-26 13:36:15.413819",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "HR",
|
||||
|
@ -360,13 +360,14 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-10 09:28:21.946972",
|
||||
"modified": "2021-04-19 18:10:32.360818",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
@ -212,15 +212,17 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-01 10:21:44.413353",
|
||||
"modified": "2021-04-19 18:24:40.119647",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Application",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
@ -235,6 +237,7 @@
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
|
@ -154,13 +154,14 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-10 10:03:41.502210",
|
||||
"modified": "2021-04-19 18:09:32.175355",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Disbursement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
@ -175,6 +176,7 @@
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
@ -185,13 +185,14 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-10 00:15:21.544140",
|
||||
"modified": "2021-04-19 18:26:38.871889",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Interest Accrual",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
@ -206,6 +207,7 @@
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
@ -248,13 +248,14 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-10 10:00:31.859076",
|
||||
"modified": "2021-04-19 18:10:00.935364",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Repayment",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
@ -269,6 +270,7 @@
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
@ -160,13 +160,14 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-04 22:38:19.894488",
|
||||
"modified": "2021-04-19 18:23:16.953305",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Security Pledge",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
@ -181,6 +182,7 @@
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
@ -126,13 +126,14 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-04 22:39:57.756146",
|
||||
"modified": "2021-04-19 18:12:01.401744",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Security Unpledge",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
@ -147,6 +148,7 @@
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
@ -154,13 +154,14 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-17 06:51:26.082879",
|
||||
"modified": "2021-04-19 18:10:57.368490",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Type",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
@ -116,13 +116,14 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-26 07:13:43.663924",
|
||||
"modified": "2021-04-19 18:11:27.759862",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Write Off",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
@ -137,6 +138,7 @@
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
@ -43,7 +43,6 @@ def get_charts():
|
||||
return [{
|
||||
"doctype": "Dashboard Chart",
|
||||
"based_on": "modified",
|
||||
"time_interval": "Yearly",
|
||||
"chart_type": "Sum",
|
||||
"chart_name": _("Produced Quantity"),
|
||||
"name": "Produced Quantity",
|
||||
@ -60,7 +59,6 @@ def get_charts():
|
||||
}, {
|
||||
"doctype": "Dashboard Chart",
|
||||
"based_on": "creation",
|
||||
"time_interval": "Yearly",
|
||||
"chart_type": "Sum",
|
||||
"chart_name": _("Completed Operation"),
|
||||
"name": "Completed Operation",
|
||||
|
@ -53,7 +53,9 @@ class BOMUpdateTool(Document):
|
||||
rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2 and parenttype='BOM'""",
|
||||
(self.new_bom, unit_cost, unit_cost, self.current_bom))
|
||||
|
||||
def get_parent_boms(self, bom, bom_list=[]):
|
||||
def get_parent_boms(self, bom, bom_list=None):
|
||||
if bom_list is None:
|
||||
bom_list = []
|
||||
data = frappe.db.sql("""SELECT DISTINCT parent FROM `tabBOM Item`
|
||||
WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""", bom)
|
||||
|
||||
|
@ -561,7 +561,6 @@ def get_material_request_items(row, sales_order, company,
|
||||
'item_name': row.item_name,
|
||||
'quantity': required_qty,
|
||||
'required_bom_qty': total_qty,
|
||||
'description': row.description,
|
||||
'stock_uom': row.get("stock_uom"),
|
||||
'warehouse': warehouse or row.get('source_warehouse') \
|
||||
or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"),
|
||||
@ -766,7 +765,7 @@ def get_items_for_material_requests(doc, warehouses=None):
|
||||
to_enable = frappe.bold(_("Ignore Existing Projected Quantity"))
|
||||
warehouse = frappe.bold(doc.get('for_warehouse'))
|
||||
message = _("As there are sufficient raw materials, Material Request is not required for Warehouse {0}.").format(warehouse) + "<br><br>"
|
||||
message += _(" If you still want to proceed, please enable {0}.").format(to_enable)
|
||||
message += _("If you still want to proceed, please enable {0}.").format(to_enable)
|
||||
|
||||
frappe.msgprint(message, title=_("Note"))
|
||||
|
||||
|
@ -693,7 +693,7 @@ execute:frappe.reload_doctype('Dashboard')
|
||||
execute:frappe.reload_doc('desk', 'doctype', 'number_card_link')
|
||||
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
|
||||
erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
|
||||
erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25
|
||||
erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2021-04-16
|
||||
erpnext.patches.v12_0.update_bom_in_so_mr
|
||||
execute:frappe.delete_doc("Report", "Department Analytics")
|
||||
execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
|
||||
@ -772,5 +772,7 @@ erpnext.patches.v12_0.purchase_receipt_status
|
||||
erpnext.patches.v13_0.fix_non_unique_represents_company
|
||||
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
||||
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
|
||||
erpnext.patches.v13_0.update_shipment_status
|
||||
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
|
||||
erpnext.patches.v13_0.germany_make_custom_fields
|
||||
erpnext.patches.v13_0.germany_fill_debtor_creditor_number
|
||||
|
@ -38,16 +38,37 @@ def execute():
|
||||
""".format(doctype), {'parentfield': parentfield})
|
||||
|
||||
# copy renamed child table fields (fields were already renamed in old doctype json, hence sql)
|
||||
frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_name = test_name""")
|
||||
frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_event = test_event""")
|
||||
frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_uom = test_uom""")
|
||||
frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_comment = test_comment""")
|
||||
rename_fields = {
|
||||
'lab_test_name': 'test_name',
|
||||
'lab_test_event': 'test_event',
|
||||
'lab_test_uom': 'test_uom',
|
||||
'lab_test_comment': 'test_comment'
|
||||
}
|
||||
|
||||
for new, old in rename_fields.items():
|
||||
if frappe.db.has_column('Normal Test Result', old):
|
||||
frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}"""
|
||||
.format(new, old))
|
||||
|
||||
if frappe.db.has_column('Normal Test Template', 'test_event'):
|
||||
frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""")
|
||||
|
||||
if frappe.db.has_column('Normal Test Template', 'test_uom'):
|
||||
frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""")
|
||||
|
||||
if frappe.db.has_column('Descriptive Test Result', 'test_particulars'):
|
||||
frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""")
|
||||
frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_template = test_template""")
|
||||
frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_description = test_description""")
|
||||
frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_rate = test_rate""")
|
||||
|
||||
rename_fields = {
|
||||
'lab_test_template': 'test_template',
|
||||
'lab_test_description': 'test_description',
|
||||
'lab_test_rate': 'test_rate'
|
||||
}
|
||||
|
||||
for new, old in rename_fields.items():
|
||||
if frappe.db.has_column('Lab Test Group Template', old):
|
||||
frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}"""
|
||||
.format(new, old))
|
||||
|
||||
# rename field
|
||||
frappe.reload_doc('healthcare', 'doctype', 'lab_test')
|
||||
|
@ -0,0 +1,8 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
"""Remove has_variants and attribute fields from item variant settings."""
|
||||
frappe.reload_doc("stock", "doctype", "Item Variant Settings")
|
||||
|
||||
frappe.db.sql("""delete from `tabVariant Field`
|
||||
where field_name in ('attributes', 'has_variants')""")
|
@ -3,7 +3,7 @@ import frappe
|
||||
|
||||
def execute():
|
||||
company = frappe.db.get_single_value('Global Defaults', 'default_company')
|
||||
doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection' 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment']
|
||||
doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection', 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment']
|
||||
for entry in doctypes:
|
||||
if frappe.db.exists('DocType', entry):
|
||||
frappe.reload_doc('Healthcare', 'doctype', entry)
|
||||
|
14
erpnext/patches/v13_0/update_shipment_status.py
Normal file
14
erpnext/patches/v13_0/update_shipment_status.py
Normal file
@ -0,0 +1,14 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("stock", "doctype", "shipment")
|
||||
|
||||
# update submitted status
|
||||
frappe.db.sql("""UPDATE `tabShipment`
|
||||
SET status = "Submitted"
|
||||
WHERE status = "Draft" AND docstatus = 1""")
|
||||
|
||||
# update cancelled status
|
||||
frappe.db.sql("""UPDATE `tabShipment`
|
||||
SET status = "Cancelled"
|
||||
WHERE status = "Draft" AND docstatus = 2""")
|
@ -13,12 +13,19 @@ class AdditionalSalary(Document):
|
||||
if self.ref_doctype == "Employee Advance" and self.ref_docname:
|
||||
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
|
||||
|
||||
self.update_employee_referral()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_employee_referral(cancel=True)
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_salary_structure()
|
||||
self.validate_recurring_additional_salary_overlap()
|
||||
self.validate_employee_referral()
|
||||
|
||||
if self.amount < 0:
|
||||
frappe.throw(_("Amount should not be less than zero."))
|
||||
frappe.throw(_("Amount should not be less than zero"))
|
||||
|
||||
def validate_salary_structure(self):
|
||||
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
|
||||
@ -70,6 +77,27 @@ class AdditionalSalary(Document):
|
||||
if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date):
|
||||
frappe.throw(_("Payroll date can not be greater than employee's relieving date."))
|
||||
|
||||
def validate_employee_referral(self):
|
||||
if self.ref_doctype == "Employee Referral":
|
||||
referral_details = frappe.db.get_value("Employee Referral", self.ref_docname,
|
||||
["is_applicable_for_referral_bonus", "status"], as_dict=1)
|
||||
|
||||
if not referral_details.is_applicable_for_referral_bonus:
|
||||
frappe.throw(_("Employee Referral {0} is not applicable for referral bonus.").format(
|
||||
self.ref_docname))
|
||||
|
||||
if self.type == "Deduction":
|
||||
frappe.throw(_("Earning Salary Component is required for Employee Referral Bonus."))
|
||||
|
||||
if referral_details.status != "Accepted":
|
||||
frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format(
|
||||
frappe.bold("Accepted")))
|
||||
|
||||
def update_employee_referral(self, cancel=False):
|
||||
if self.ref_doctype == "Employee Referral":
|
||||
status = "Unpaid" if cancel else "Paid"
|
||||
frappe.db.set_value("Employee Referral", self.ref_docname, "referral_payment_status", status)
|
||||
|
||||
def get_amount(self, sal_start_date, sal_end_date):
|
||||
start_date = getdate(sal_start_date)
|
||||
end_date = getdate(sal_end_date)
|
||||
@ -110,8 +138,7 @@ def get_additional_salaries(employee, start_date, end_date, component_type):
|
||||
for d in additional_salary_list:
|
||||
if d.overwrite:
|
||||
if d.component in components_to_overwrite:
|
||||
frappe.throw(_("Multiple Additional Salaries with overwrite "
|
||||
"property exist for Salary Component {0} between {1} and {2}.").format(
|
||||
frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}.").format(
|
||||
frappe.bold(d.component), start_date, end_date), title=_("Error"))
|
||||
|
||||
components_to_overwrite.append(d.component)
|
||||
|
@ -151,6 +151,10 @@ frappe.ui.form.on('Payroll Entry', {
|
||||
filters['company'] = frm.doc.company;
|
||||
filters['start_date'] = frm.doc.start_date;
|
||||
filters['end_date'] = frm.doc.end_date;
|
||||
filters['salary_slip_based_on_timesheet'] = frm.doc.salary_slip_based_on_timesheet;
|
||||
filters['payroll_frequency'] = frm.doc.payroll_frequency;
|
||||
filters['payroll_payable_account'] = frm.doc.payroll_payable_account;
|
||||
filters['currency'] = frm.doc.currency;
|
||||
|
||||
if (frm.doc.department) {
|
||||
filters['department'] = frm.doc.department;
|
||||
|
@ -52,49 +52,32 @@ class PayrollEntry(Document):
|
||||
Returns list of active employees based on selected criteria
|
||||
and for which salary structure exists
|
||||
"""
|
||||
cond = self.get_filter_condition()
|
||||
cond += self.get_joining_relieving_condition()
|
||||
self.check_mandatory()
|
||||
filters = self.make_filters()
|
||||
cond = get_filter_condition(filters)
|
||||
cond += get_joining_relieving_condition(self.start_date, self.end_date)
|
||||
|
||||
condition = ''
|
||||
if self.payroll_frequency:
|
||||
condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency}
|
||||
|
||||
sal_struct = frappe.db.sql_list("""
|
||||
select
|
||||
name from `tabSalary Structure`
|
||||
where
|
||||
docstatus = 1 and
|
||||
is_active = 'Yes'
|
||||
and company = %(company)s
|
||||
and currency = %(currency)s and
|
||||
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
|
||||
{condition}""".format(condition=condition),
|
||||
{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
|
||||
|
||||
sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition)
|
||||
if sal_struct:
|
||||
cond += "and t2.salary_structure IN %(sal_struct)s "
|
||||
cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
|
||||
cond += "and %(from_date)s >= t2.from_date"
|
||||
emp_list = frappe.db.sql("""
|
||||
select
|
||||
distinct t1.name as employee, t1.employee_name, t1.department, t1.designation
|
||||
from
|
||||
`tabEmployee` t1, `tabSalary Structure Assignment` t2
|
||||
where
|
||||
t1.name = t2.employee
|
||||
and t2.docstatus = 1
|
||||
%s order by t2.from_date desc
|
||||
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
|
||||
|
||||
emp_list = self.remove_payrolled_employees(emp_list)
|
||||
emp_list = get_emp_list(sal_struct, cond, self.end_date, self.payroll_payable_account)
|
||||
emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date)
|
||||
return emp_list
|
||||
|
||||
def remove_payrolled_employees(self, emp_list):
|
||||
for employee_details in emp_list:
|
||||
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
|
||||
emp_list.remove(employee_details)
|
||||
def make_filters(self):
|
||||
filters = frappe._dict()
|
||||
filters['company'] = self.company
|
||||
filters['branch'] = self.branch
|
||||
filters['department'] = self.department
|
||||
filters['designation'] = self.designation
|
||||
|
||||
return emp_list
|
||||
return filters
|
||||
|
||||
@frappe.whitelist()
|
||||
def fill_employee_details(self):
|
||||
@ -122,23 +105,6 @@ class PayrollEntry(Document):
|
||||
if self.validate_attendance:
|
||||
return self.validate_employee_attendance()
|
||||
|
||||
def get_filter_condition(self):
|
||||
self.check_mandatory()
|
||||
|
||||
cond = ''
|
||||
for f in ['company', 'branch', 'department', 'designation']:
|
||||
if self.get(f):
|
||||
cond += " and t1." + f + " = " + frappe.db.escape(self.get(f))
|
||||
|
||||
return cond
|
||||
|
||||
def get_joining_relieving_condition(self):
|
||||
cond = """
|
||||
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
|
||||
and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s'
|
||||
""" % {"start_date": self.start_date, "end_date": self.end_date}
|
||||
return cond
|
||||
|
||||
def check_mandatory(self):
|
||||
for fieldname in ['company', 'start_date', 'end_date']:
|
||||
if not self.get(fieldname):
|
||||
@ -451,6 +417,53 @@ class PayrollEntry(Document):
|
||||
marked_days = attendances[0][0]
|
||||
return marked_days
|
||||
|
||||
def get_sal_struct(company, currency, salary_slip_based_on_timesheet, condition):
|
||||
return frappe.db.sql_list("""
|
||||
select
|
||||
name from `tabSalary Structure`
|
||||
where
|
||||
docstatus = 1 and
|
||||
is_active = 'Yes'
|
||||
and company = %(company)s
|
||||
and currency = %(currency)s and
|
||||
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
|
||||
{condition}""".format(condition=condition),
|
||||
{"company": company, "currency": currency, "salary_slip_based_on_timesheet": salary_slip_based_on_timesheet})
|
||||
|
||||
def get_filter_condition(filters):
|
||||
cond = ''
|
||||
for f in ['company', 'branch', 'department', 'designation']:
|
||||
if filters.get(f):
|
||||
cond += " and t1." + f + " = " + frappe.db.escape(filters.get(f))
|
||||
|
||||
return cond
|
||||
|
||||
def get_joining_relieving_condition(start_date, end_date):
|
||||
cond = """
|
||||
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
|
||||
and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s'
|
||||
""" % {"start_date": start_date, "end_date": end_date}
|
||||
return cond
|
||||
|
||||
def get_emp_list(sal_struct, cond, end_date, payroll_payable_account):
|
||||
return frappe.db.sql("""
|
||||
select
|
||||
distinct t1.name as employee, t1.employee_name, t1.department, t1.designation
|
||||
from
|
||||
`tabEmployee` t1, `tabSalary Structure Assignment` t2
|
||||
where
|
||||
t1.name = t2.employee
|
||||
and t2.docstatus = 1
|
||||
%s order by t2.from_date desc
|
||||
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True)
|
||||
|
||||
def remove_payrolled_employees(emp_list, start_date, end_date):
|
||||
for employee_details in emp_list:
|
||||
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}):
|
||||
emp_list.remove(employee_details)
|
||||
|
||||
return emp_list
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_start_end_dates(payroll_frequency, start_date=None, company=None):
|
||||
'''Returns dict of start and end dates for given payroll frequency based on start_date'''
|
||||
@ -639,39 +652,41 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte
|
||||
'start': start, 'page_len': page_len
|
||||
})
|
||||
|
||||
def get_employee_with_existing_salary_slip(start_date, end_date, company):
|
||||
return frappe.db.sql_list("""
|
||||
select employee from `tabSalary Slip`
|
||||
where
|
||||
(start_date between %(start_date)s and %(end_date)s
|
||||
or
|
||||
end_date between %(start_date)s and %(end_date)s
|
||||
or
|
||||
%(start_date)s between start_date and end_date)
|
||||
and company = %(company)s
|
||||
and docstatus = 1
|
||||
""", {'start_date': start_date, 'end_date': end_date, 'company': company})
|
||||
def get_employee_list(filters):
|
||||
cond = get_filter_condition(filters)
|
||||
cond += get_joining_relieving_condition(filters.start_date, filters.end_date)
|
||||
condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": filters.payroll_frequency}
|
||||
sal_struct = get_sal_struct(filters.company, filters.currency, filters.salary_slip_based_on_timesheet, condition)
|
||||
if sal_struct:
|
||||
cond += "and t2.salary_structure IN %(sal_struct)s "
|
||||
cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
|
||||
cond += "and %(from_date)s >= t2.from_date"
|
||||
emp_list = get_emp_list(sal_struct, cond, filters.end_date, filters.payroll_payable_account)
|
||||
emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date)
|
||||
return emp_list
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
filters = frappe._dict(filters)
|
||||
conditions = []
|
||||
exclude_employees = []
|
||||
include_employees = []
|
||||
emp_cond = ''
|
||||
if filters.start_date and filters.end_date:
|
||||
employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company)
|
||||
employee_list = get_employee_list(filters)
|
||||
emp = filters.get('employees')
|
||||
include_employees = [employee.employee for employee in employee_list if employee.employee not in emp]
|
||||
filters.pop('start_date')
|
||||
filters.pop('end_date')
|
||||
filters.pop('salary_slip_based_on_timesheet')
|
||||
filters.pop('payroll_frequency')
|
||||
filters.pop('payroll_payable_account')
|
||||
filters.pop('currency')
|
||||
if filters.employees is not None:
|
||||
filters.pop('employees')
|
||||
if employee_list:
|
||||
exclude_employees.extend(employee_list)
|
||||
if emp:
|
||||
exclude_employees.extend(emp)
|
||||
if exclude_employees:
|
||||
emp_cond += 'and employee not in %(exclude_employees)s'
|
||||
|
||||
if include_employees:
|
||||
emp_cond += 'and employee in %(include_employees)s'
|
||||
|
||||
return frappe.db.sql("""select name, employee_name from `tabEmployee`
|
||||
where status = 'Active'
|
||||
@ -695,4 +710,4 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len,
|
||||
'exclude_employees': exclude_employees})
|
||||
'include_employees': include_employees})
|
||||
|
@ -133,8 +133,6 @@ frappe.ui.form.on('Salary Structure', {
|
||||
title: __("Assign to Employees"),
|
||||
fields: [
|
||||
{fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")},
|
||||
{fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1},
|
||||
{fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1},
|
||||
{fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")},
|
||||
{fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
|
||||
{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},
|
||||
|
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