Merge branch 'develop' into make-image-field-obsolete-in-web-item
This commit is contained in:
commit
d2d651a0c3
7
.github/helper/install.sh
vendored
7
.github/helper/install.sh
vendored
@ -2,13 +2,6 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Check for merge conflicts before proceeding
|
|
||||||
python -m compileall -f "${GITHUB_WORKSPACE}"
|
|
||||||
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
|
||||||
then echo "Found merge conflicts"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd ~ || exit
|
cd ~ || exit
|
||||||
|
|
||||||
sudo apt update && sudo apt install redis-server libcups2-dev
|
sudo apt update && sudo apt install redis-server libcups2-dev
|
||||||
|
|||||||
8
.github/workflows/patch.yml
vendored
8
.github/workflows/patch.yml
vendored
@ -34,6 +34,14 @@ jobs:
|
|||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Check for valid Python & Merge Conflicts
|
||||||
|
run: |
|
||||||
|
python -m compileall -f "${GITHUB_WORKSPACE}"
|
||||||
|
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
||||||
|
then echo "Found merge conflicts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: "gabrielfalcao/pyenv-action@v9"
|
uses: "gabrielfalcao/pyenv-action@v9"
|
||||||
with:
|
with:
|
||||||
|
|||||||
30
.github/workflows/semantic-commits.yml
vendored
Normal file
30
.github/workflows/semantic-commits.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: Semantic Commits
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request: {}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: commitcheck-erpnext-${{ github.event.number }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
commitlint:
|
||||||
|
name: Check Commit Titles
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 200
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Check commit titles
|
||||||
|
run: |
|
||||||
|
npm install @commitlint/cli @commitlint/config-conventional
|
||||||
|
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
|
||||||
8
.github/workflows/server-tests-mariadb.yml
vendored
8
.github/workflows/server-tests-mariadb.yml
vendored
@ -61,6 +61,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Check for valid Python & Merge Conflicts
|
||||||
|
run: |
|
||||||
|
python -m compileall -f "${GITHUB_WORKSPACE}"
|
||||||
|
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
||||||
|
then echo "Found merge conflicts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
9
.github/workflows/server-tests-postgres.yml
vendored
9
.github/workflows/server-tests-postgres.yml
vendored
@ -48,6 +48,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Check for valid Python & Merge Conflicts
|
||||||
|
run: |
|
||||||
|
python -m compileall -f "${GITHUB_WORKSPACE}"
|
||||||
|
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
||||||
|
then echo "Found merge conflicts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
@ -90,7 +98,6 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-yarn-
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
env:
|
env:
|
||||||
|
|||||||
25
commitlint.config.js
Normal file
25
commitlint.config.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
parserPreset: 'conventional-changelog-conventionalcommits',
|
||||||
|
rules: {
|
||||||
|
'subject-empty': [2, 'never'],
|
||||||
|
'type-case': [2, 'always', 'lower-case'],
|
||||||
|
'type-empty': [2, 'never'],
|
||||||
|
'type-enum': [
|
||||||
|
2,
|
||||||
|
'always',
|
||||||
|
[
|
||||||
|
'build',
|
||||||
|
'chore',
|
||||||
|
'ci',
|
||||||
|
'docs',
|
||||||
|
'feat',
|
||||||
|
'fix',
|
||||||
|
'perf',
|
||||||
|
'refactor',
|
||||||
|
'revert',
|
||||||
|
'style',
|
||||||
|
'test',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -49,15 +49,9 @@ class AccountingPeriod(Document):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_doctypes_for_closing(self):
|
def get_doctypes_for_closing(self):
|
||||||
docs_for_closing = []
|
docs_for_closing = []
|
||||||
doctypes = [
|
# get period closing doctypes from all the apps
|
||||||
"Sales Invoice",
|
doctypes = frappe.get_hooks("period_closing_doctypes")
|
||||||
"Purchase Invoice",
|
|
||||||
"Journal Entry",
|
|
||||||
"Payroll Entry",
|
|
||||||
"Bank Clearance",
|
|
||||||
"Asset",
|
|
||||||
"Stock Entry",
|
|
||||||
]
|
|
||||||
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
|
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
|
||||||
for closed_doctype in closed_doctypes:
|
for closed_doctype in closed_doctypes:
|
||||||
docs_for_closing.append(closed_doctype)
|
docs_for_closing.append(closed_doctype)
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
"automatically_fetch_payment_terms",
|
"automatically_fetch_payment_terms",
|
||||||
"column_break_17",
|
"column_break_17",
|
||||||
"enable_common_party_accounting",
|
"enable_common_party_accounting",
|
||||||
|
"allow_multi_currency_invoices_against_single_party_account",
|
||||||
"report_setting_section",
|
"report_setting_section",
|
||||||
"use_custom_cash_flow",
|
"use_custom_cash_flow",
|
||||||
"deferred_accounting_settings_section",
|
"deferred_accounting_settings_section",
|
||||||
@ -339,6 +340,13 @@
|
|||||||
"fieldname": "report_setting_section",
|
"fieldname": "report_setting_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Report Setting"
|
"label": "Report Setting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency",
|
||||||
|
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow multi-currency invoices against single party account "
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@ -346,7 +354,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-04-08 14:45:06.796418",
|
"modified": "2022-07-11 13:37:50.605141",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@ -104,7 +104,7 @@ class BankClearance(Document):
|
|||||||
|
|
||||||
loan_repayment = frappe.qb.DocType("Loan Repayment")
|
loan_repayment = frappe.qb.DocType("Loan Repayment")
|
||||||
|
|
||||||
loan_repayments = (
|
query = (
|
||||||
frappe.qb.from_(loan_repayment)
|
frappe.qb.from_(loan_repayment)
|
||||||
.select(
|
.select(
|
||||||
ConstantColumn("Loan Repayment").as_("payment_document"),
|
ConstantColumn("Loan Repayment").as_("payment_document"),
|
||||||
@ -118,13 +118,17 @@ class BankClearance(Document):
|
|||||||
)
|
)
|
||||||
.where(loan_repayment.docstatus == 1)
|
.where(loan_repayment.docstatus == 1)
|
||||||
.where(loan_repayment.clearance_date.isnull())
|
.where(loan_repayment.clearance_date.isnull())
|
||||||
.where(loan_repayment.repay_from_salary == 0)
|
|
||||||
.where(loan_repayment.posting_date >= self.from_date)
|
.where(loan_repayment.posting_date >= self.from_date)
|
||||||
.where(loan_repayment.posting_date <= self.to_date)
|
.where(loan_repayment.posting_date <= self.to_date)
|
||||||
.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
|
.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
|
||||||
.orderby(loan_repayment.posting_date)
|
)
|
||||||
.orderby(loan_repayment.name, frappe.qb.desc)
|
|
||||||
).run(as_dict=1)
|
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
|
||||||
|
query = query.where((loan_repayment.repay_from_salary == 0))
|
||||||
|
|
||||||
|
query = query.orderby(loan_repayment.posting_date).orderby(loan_repayment.name, frappe.qb.desc)
|
||||||
|
|
||||||
|
loan_repayments = query.run(as_dict=True)
|
||||||
|
|
||||||
pos_sales_invoices, pos_purchase_invoices = [], []
|
pos_sales_invoices, pos_purchase_invoices = [], []
|
||||||
if self.include_pos_transactions:
|
if self.include_pos_transactions:
|
||||||
|
|||||||
@ -10,7 +10,6 @@ from frappe.model.document import Document
|
|||||||
from frappe.query_builder.custom import ConstantColumn
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
|
|
||||||
from erpnext import get_company_currency
|
|
||||||
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount
|
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount
|
||||||
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
|
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
|
||||||
get_amounts_not_reflected_in_system,
|
get_amounts_not_reflected_in_system,
|
||||||
@ -368,6 +367,27 @@ def get_queries(bank_account, company, transaction, document_types):
|
|||||||
account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from"
|
account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from"
|
||||||
queries = []
|
queries = []
|
||||||
|
|
||||||
|
# get matching queries from all the apps
|
||||||
|
for method_name in frappe.get_hooks("get_matching_queries"):
|
||||||
|
queries.extend(
|
||||||
|
frappe.get_attr(method_name)(
|
||||||
|
bank_account,
|
||||||
|
company,
|
||||||
|
transaction,
|
||||||
|
document_types,
|
||||||
|
amount_condition,
|
||||||
|
account_from_to,
|
||||||
|
)
|
||||||
|
or []
|
||||||
|
)
|
||||||
|
|
||||||
|
return queries
|
||||||
|
|
||||||
|
|
||||||
|
def get_matching_queries(
|
||||||
|
bank_account, company, transaction, document_types, amount_condition, account_from_to
|
||||||
|
):
|
||||||
|
queries = []
|
||||||
if "payment_entry" in document_types:
|
if "payment_entry" in document_types:
|
||||||
pe_amount_matching = get_pe_matching_query(amount_condition, account_from_to, transaction)
|
pe_amount_matching = get_pe_matching_query(amount_condition, account_from_to, transaction)
|
||||||
queries.extend([pe_amount_matching])
|
queries.extend([pe_amount_matching])
|
||||||
@ -385,10 +405,6 @@ def get_queries(bank_account, company, transaction, document_types):
|
|||||||
pi_amount_matching = get_pi_matching_query(amount_condition)
|
pi_amount_matching = get_pi_matching_query(amount_condition)
|
||||||
queries.extend([pi_amount_matching])
|
queries.extend([pi_amount_matching])
|
||||||
|
|
||||||
if "expense_claim" in document_types:
|
|
||||||
ec_amount_matching = get_ec_matching_query(bank_account, company, amount_condition)
|
|
||||||
queries.extend([ec_amount_matching])
|
|
||||||
|
|
||||||
return queries
|
return queries
|
||||||
|
|
||||||
|
|
||||||
@ -467,11 +483,13 @@ def get_lr_matching_query(bank_account, amount_condition, filters):
|
|||||||
loan_repayment.posting_date,
|
loan_repayment.posting_date,
|
||||||
)
|
)
|
||||||
.where(loan_repayment.docstatus == 1)
|
.where(loan_repayment.docstatus == 1)
|
||||||
.where(loan_repayment.repay_from_salary == 0)
|
|
||||||
.where(loan_repayment.clearance_date.isnull())
|
.where(loan_repayment.clearance_date.isnull())
|
||||||
.where(loan_repayment.payment_account == bank_account)
|
.where(loan_repayment.payment_account == bank_account)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
|
||||||
|
query = query.where((loan_repayment.repay_from_salary == 0))
|
||||||
|
|
||||||
if amount_condition:
|
if amount_condition:
|
||||||
query.where(loan_repayment.amount_paid == filters.get("amount"))
|
query.where(loan_repayment.amount_paid == filters.get("amount"))
|
||||||
else:
|
else:
|
||||||
@ -602,37 +620,3 @@ def get_pi_matching_query(amount_condition):
|
|||||||
AND ifnull(clearance_date, '') = ""
|
AND ifnull(clearance_date, '') = ""
|
||||||
AND cash_bank_account = %(bank_account)s
|
AND cash_bank_account = %(bank_account)s
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_ec_matching_query(bank_account, company, amount_condition):
|
|
||||||
# get matching Expense Claim query
|
|
||||||
mode_of_payments = [
|
|
||||||
x["parent"]
|
|
||||||
for x in frappe.db.get_all(
|
|
||||||
"Mode of Payment Account", filters={"default_account": bank_account}, fields=["parent"]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
mode_of_payments = "('" + "', '".join(mode_of_payments) + "' )"
|
|
||||||
company_currency = get_company_currency(company)
|
|
||||||
return f"""
|
|
||||||
SELECT
|
|
||||||
( CASE WHEN employee = %(party)s THEN 1 ELSE 0 END
|
|
||||||
+ 1 ) AS rank,
|
|
||||||
'Expense Claim' as doctype,
|
|
||||||
name,
|
|
||||||
total_sanctioned_amount as paid_amount,
|
|
||||||
'' as reference_no,
|
|
||||||
'' as reference_date,
|
|
||||||
employee as party,
|
|
||||||
'Employee' as party_type,
|
|
||||||
posting_date,
|
|
||||||
'{company_currency}' as currency
|
|
||||||
FROM
|
|
||||||
`tabExpense Claim`
|
|
||||||
WHERE
|
|
||||||
total_sanctioned_amount {amount_condition} %(amount)s
|
|
||||||
AND docstatus = 1
|
|
||||||
AND is_paid = 1
|
|
||||||
AND ifnull(clearance_date, '') = ""
|
|
||||||
AND mode_of_payment in {mode_of_payments}
|
|
||||||
"""
|
|
||||||
|
|||||||
@ -3,28 +3,21 @@
|
|||||||
|
|
||||||
frappe.ui.form.on("Bank Transaction", {
|
frappe.ui.form.on("Bank Transaction", {
|
||||||
onload(frm) {
|
onload(frm) {
|
||||||
frm.set_query("payment_document", "payment_entries", function () {
|
frm.set_query("payment_document", "payment_entries", function() {
|
||||||
|
const payment_doctypes = frm.events.get_payment_doctypes(frm);
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
name: [
|
name: ["in", payment_doctypes],
|
||||||
"in",
|
|
||||||
[
|
|
||||||
"Payment Entry",
|
|
||||||
"Journal Entry",
|
|
||||||
"Sales Invoice",
|
|
||||||
"Purchase Invoice",
|
|
||||||
"Expense Claim",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
bank_account: function (frm) {
|
|
||||||
|
bank_account: function(frm) {
|
||||||
set_bank_statement_filter(frm);
|
set_bank_statement_filter(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
setup: function (frm) {
|
setup: function(frm) {
|
||||||
frm.set_query("party_type", function () {
|
frm.set_query("party_type", function () {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@ -33,6 +26,16 @@ frappe.ui.form.on("Bank Transaction", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get_payment_doctypes: function() {
|
||||||
|
// get payment doctypes from all the apps
|
||||||
|
return [
|
||||||
|
"Payment Entry",
|
||||||
|
"Journal Entry",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Purchase Invoice",
|
||||||
|
];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("Bank Transaction Payments", {
|
frappe.ui.form.on("Bank Transaction Payments", {
|
||||||
|
|||||||
@ -58,18 +58,10 @@ class BankTransaction(StatusUpdater):
|
|||||||
|
|
||||||
def clear_linked_payment_entries(self, for_cancel=False):
|
def clear_linked_payment_entries(self, for_cancel=False):
|
||||||
for payment_entry in self.payment_entries:
|
for payment_entry in self.payment_entries:
|
||||||
if payment_entry.payment_document in [
|
if payment_entry.payment_document == "Sales Invoice":
|
||||||
"Payment Entry",
|
|
||||||
"Journal Entry",
|
|
||||||
"Purchase Invoice",
|
|
||||||
"Expense Claim",
|
|
||||||
"Loan Repayment",
|
|
||||||
"Loan Disbursement",
|
|
||||||
]:
|
|
||||||
self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
|
|
||||||
|
|
||||||
elif payment_entry.payment_document == "Sales Invoice":
|
|
||||||
self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
|
self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
|
||||||
|
elif payment_entry.payment_document in get_doctypes_for_bank_reconciliation():
|
||||||
|
self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
|
||||||
|
|
||||||
def clear_simple_entry(self, payment_entry, for_cancel=False):
|
def clear_simple_entry(self, payment_entry, for_cancel=False):
|
||||||
if payment_entry.payment_document == "Payment Entry":
|
if payment_entry.payment_document == "Payment Entry":
|
||||||
@ -95,6 +87,12 @@ class BankTransaction(StatusUpdater):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_doctypes_for_bank_reconciliation():
|
||||||
|
"""Get Bank Reconciliation doctypes from all the apps"""
|
||||||
|
return frappe.get_hooks("bank_reconciliation_doctypes")
|
||||||
|
|
||||||
|
|
||||||
def get_reconciled_bank_transactions(payment_entry):
|
def get_reconciled_bank_transactions(payment_entry):
|
||||||
reconciled_bank_transactions = frappe.get_all(
|
reconciled_bank_transactions = frappe.get_all(
|
||||||
"Bank Transaction Payments",
|
"Bank Transaction Payments",
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import json
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
||||||
get_linked_payments,
|
get_linked_payments,
|
||||||
@ -18,28 +19,21 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
test_dependencies = ["Item", "Cost Center"]
|
test_dependencies = ["Item", "Cost Center"]
|
||||||
|
|
||||||
|
|
||||||
class TestBankTransaction(unittest.TestCase):
|
class TestBankTransaction(FrappeTestCase):
|
||||||
@classmethod
|
def setUp(self):
|
||||||
def setUpClass(cls):
|
for dt in [
|
||||||
|
"Loan Repayment",
|
||||||
|
"Bank Transaction",
|
||||||
|
"Payment Entry",
|
||||||
|
"Payment Entry Reference",
|
||||||
|
"POS Profile",
|
||||||
|
]:
|
||||||
|
frappe.db.delete(dt)
|
||||||
|
|
||||||
make_pos_profile()
|
make_pos_profile()
|
||||||
add_transactions()
|
add_transactions()
|
||||||
add_vouchers()
|
add_vouchers()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def tearDownClass(cls):
|
|
||||||
for bt in frappe.get_all("Bank Transaction"):
|
|
||||||
doc = frappe.get_doc("Bank Transaction", bt.name)
|
|
||||||
if doc.docstatus == 1:
|
|
||||||
doc.cancel()
|
|
||||||
doc.delete()
|
|
||||||
|
|
||||||
# Delete directly in DB to avoid validation errors for countries not allowing deletion
|
|
||||||
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
|
|
||||||
frappe.db.sql("""delete from `tabPayment Entry`""")
|
|
||||||
|
|
||||||
# Delete POS Profile
|
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
|
||||||
|
|
||||||
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
|
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
|
||||||
def test_linked_payments(self):
|
def test_linked_payments(self):
|
||||||
bank_transaction = frappe.get_doc(
|
bank_transaction = frappe.get_doc(
|
||||||
@ -155,6 +149,35 @@ class TestBankTransaction(unittest.TestCase):
|
|||||||
is not None
|
is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_matching_loan_repayment(self):
|
||||||
|
from erpnext.loan_management.doctype.loan.test_loan import create_loan_accounts
|
||||||
|
|
||||||
|
create_loan_accounts()
|
||||||
|
bank_account = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Bank Account",
|
||||||
|
"account_name": "Payment Account",
|
||||||
|
"bank": "Citi Bank",
|
||||||
|
"account": "Payment Account - _TC",
|
||||||
|
}
|
||||||
|
).insert(ignore_if_duplicate=True)
|
||||||
|
|
||||||
|
bank_transaction = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Bank Transaction",
|
||||||
|
"description": "Loan Repayment - OPSKATTUZWXXX AT776000000098709837 Herr G",
|
||||||
|
"date": "2018-10-27",
|
||||||
|
"deposit": 500,
|
||||||
|
"currency": "INR",
|
||||||
|
"bank_account": bank_account.name,
|
||||||
|
}
|
||||||
|
).submit()
|
||||||
|
|
||||||
|
repayment_entry = create_loan_and_repayment()
|
||||||
|
|
||||||
|
linked_payments = get_linked_payments(bank_transaction.name, ["loan_repayment", "exact_match"])
|
||||||
|
self.assertEqual(linked_payments[0][2], repayment_entry.name)
|
||||||
|
|
||||||
|
|
||||||
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
||||||
try:
|
try:
|
||||||
@ -364,3 +387,59 @@ def add_vouchers():
|
|||||||
)
|
)
|
||||||
si.insert()
|
si.insert()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
|
|
||||||
|
def create_loan_and_repayment():
|
||||||
|
from erpnext.loan_management.doctype.loan.test_loan import (
|
||||||
|
create_loan,
|
||||||
|
create_loan_type,
|
||||||
|
create_repayment_entry,
|
||||||
|
make_loan_disbursement_entry,
|
||||||
|
)
|
||||||
|
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (
|
||||||
|
process_loan_interest_accrual_for_term_loans,
|
||||||
|
)
|
||||||
|
from erpnext.setup.doctype.employee.test_employee import make_employee
|
||||||
|
|
||||||
|
create_loan_type(
|
||||||
|
"Personal Loan",
|
||||||
|
500000,
|
||||||
|
8.4,
|
||||||
|
is_term_loan=1,
|
||||||
|
mode_of_payment="Cash",
|
||||||
|
disbursement_account="Disbursement Account - _TC",
|
||||||
|
payment_account="Payment Account - _TC",
|
||||||
|
loan_account="Loan Account - _TC",
|
||||||
|
interest_income_account="Interest Income Account - _TC",
|
||||||
|
penalty_income_account="Penalty Income Account - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
applicant = make_employee("test_bank_reco@loan.com", company="_Test Company")
|
||||||
|
loan = create_loan(applicant, "Personal Loan", 5000, "Repay Over Number of Periods", 20)
|
||||||
|
loan = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Loan",
|
||||||
|
"applicant_type": "Employee",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"applicant": applicant,
|
||||||
|
"loan_type": "Personal Loan",
|
||||||
|
"loan_amount": 5000,
|
||||||
|
"repayment_method": "Repay Fixed Amount per Period",
|
||||||
|
"monthly_repayment_amount": 500,
|
||||||
|
"repayment_start_date": "2018-09-27",
|
||||||
|
"is_term_loan": 1,
|
||||||
|
"posting_date": "2018-09-27",
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date="2018-09-27")
|
||||||
|
process_loan_interest_accrual_for_term_loans(posting_date="2018-10-27")
|
||||||
|
|
||||||
|
repayment_entry = create_repayment_entry(
|
||||||
|
loan.name,
|
||||||
|
applicant,
|
||||||
|
"2018-10-27",
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
repayment_entry.submit()
|
||||||
|
return repayment_entry
|
||||||
|
|||||||
@ -42,7 +42,7 @@ class GLEntry(Document):
|
|||||||
self.validate_and_set_fiscal_year()
|
self.validate_and_set_fiscal_year()
|
||||||
self.pl_must_have_cost_center()
|
self.pl_must_have_cost_center()
|
||||||
|
|
||||||
if not self.flags.from_repost:
|
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
|
||||||
self.check_mandatory()
|
self.check_mandatory()
|
||||||
self.validate_cost_center()
|
self.validate_cost_center()
|
||||||
self.check_pl_account()
|
self.check_pl_account()
|
||||||
@ -51,7 +51,7 @@ class GLEntry(Document):
|
|||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
adv_adj = self.flags.adv_adj
|
adv_adj = self.flags.adv_adj
|
||||||
if not self.flags.from_repost:
|
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
|
||||||
self.validate_account_details(adv_adj)
|
self.validate_account_details(adv_adj)
|
||||||
self.validate_dimensions_for_pl_and_bs()
|
self.validate_dimensions_for_pl_and_bs()
|
||||||
self.validate_allowed_dimensions()
|
self.validate_allowed_dimensions()
|
||||||
|
|||||||
@ -224,25 +224,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
|||||||
me.frm.set_query("reference_name", "accounts", function(doc, cdt, cdn) {
|
me.frm.set_query("reference_name", "accounts", function(doc, cdt, cdn) {
|
||||||
var jvd = frappe.get_doc(cdt, cdn);
|
var jvd = frappe.get_doc(cdt, cdn);
|
||||||
|
|
||||||
// expense claim
|
|
||||||
if(jvd.reference_type==="Expense Claim") {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'total_sanctioned_amount': ['>', 0],
|
|
||||||
'status': ['!=', 'Paid'],
|
|
||||||
'docstatus': 1
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(jvd.reference_type==="Employee Advance") {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'docstatus': 1
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// journal entry
|
// journal entry
|
||||||
if(jvd.reference_type==="Journal Entry") {
|
if(jvd.reference_type==="Journal Entry") {
|
||||||
frappe.model.validate_missing(jvd, "account");
|
frappe.model.validate_missing(jvd, "account");
|
||||||
@ -255,13 +236,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// payroll entry
|
|
||||||
if(jvd.reference_type==="Payroll Entry") {
|
|
||||||
return {
|
|
||||||
query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.get_payroll_entries_for_jv",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var out = {
|
var out = {
|
||||||
filters: [
|
filters: [
|
||||||
[jvd.reference_type, "docstatus", "=", 1]
|
[jvd.reference_type, "docstatus", "=", 1]
|
||||||
|
|||||||
@ -24,7 +24,6 @@ from erpnext.accounts.utils import (
|
|||||||
get_stock_and_account_balance,
|
get_stock_and_account_balance,
|
||||||
)
|
)
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
|
|
||||||
|
|
||||||
|
|
||||||
class StockAccountInvalidTransaction(frappe.ValidationError):
|
class StockAccountInvalidTransaction(frappe.ValidationError):
|
||||||
@ -66,7 +65,6 @@ class JournalEntry(AccountsController):
|
|||||||
self.set_against_account()
|
self.set_against_account()
|
||||||
self.create_remarks()
|
self.create_remarks()
|
||||||
self.set_print_format_fields()
|
self.set_print_format_fields()
|
||||||
self.validate_expense_claim()
|
|
||||||
self.validate_credit_debit_note()
|
self.validate_credit_debit_note()
|
||||||
self.validate_empty_accounts_table()
|
self.validate_empty_accounts_table()
|
||||||
self.set_account_and_party_balance()
|
self.set_account_and_party_balance()
|
||||||
@ -83,27 +81,21 @@ class JournalEntry(AccountsController):
|
|||||||
self.check_credit_limit()
|
self.check_credit_limit()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_expense_claim()
|
|
||||||
self.update_inter_company_jv()
|
self.update_inter_company_jv()
|
||||||
self.update_invoice_discounting()
|
self.update_invoice_discounting()
|
||||||
self.update_status_for_full_and_final_statement()
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
|
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
|
||||||
from erpnext.payroll.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip
|
|
||||||
|
|
||||||
unlink_ref_doc_from_payment_entries(self)
|
unlink_ref_doc_from_payment_entries(self)
|
||||||
unlink_ref_doc_from_salary_slip(self.name)
|
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
||||||
self.make_gl_entries(1)
|
self.make_gl_entries(1)
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_expense_claim()
|
|
||||||
self.unlink_advance_entry_reference()
|
self.unlink_advance_entry_reference()
|
||||||
self.unlink_asset_reference()
|
self.unlink_asset_reference()
|
||||||
self.unlink_inter_company_jv()
|
self.unlink_inter_company_jv()
|
||||||
self.unlink_asset_adjustment_entry()
|
self.unlink_asset_adjustment_entry()
|
||||||
self.update_invoice_discounting()
|
self.update_invoice_discounting()
|
||||||
self.update_status_for_full_and_final_statement()
|
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.pay_to_recd_from or self.accounts[0].account
|
return self.pay_to_recd_from or self.accounts[0].account
|
||||||
@ -112,21 +104,13 @@ class JournalEntry(AccountsController):
|
|||||||
advance_paid = frappe._dict()
|
advance_paid = frappe._dict()
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
if d.is_advance:
|
if d.is_advance:
|
||||||
if d.reference_type in ("Sales Order", "Purchase Order", "Employee Advance"):
|
if d.reference_type in frappe.get_hooks("advance_payment_doctypes"):
|
||||||
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
|
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
|
||||||
|
|
||||||
for voucher_type, order_list in advance_paid.items():
|
for voucher_type, order_list in advance_paid.items():
|
||||||
for voucher_no in list(set(order_list)):
|
for voucher_no in list(set(order_list)):
|
||||||
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
|
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
|
||||||
|
|
||||||
def update_status_for_full_and_final_statement(self):
|
|
||||||
for entry in self.accounts:
|
|
||||||
if entry.reference_type == "Full and Final Statement":
|
|
||||||
if self.docstatus == 1:
|
|
||||||
frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Paid")
|
|
||||||
elif self.docstatus == 2:
|
|
||||||
frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Unpaid")
|
|
||||||
|
|
||||||
def validate_inter_company_accounts(self):
|
def validate_inter_company_accounts(self):
|
||||||
if (
|
if (
|
||||||
self.voucher_type == "Inter Company Journal Entry"
|
self.voucher_type == "Inter Company Journal Entry"
|
||||||
@ -935,29 +919,6 @@ class JournalEntry(AccountsController):
|
|||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_expense_claim(self):
|
|
||||||
for d in self.accounts:
|
|
||||||
if d.reference_type == "Expense Claim" and d.reference_name:
|
|
||||||
doc = frappe.get_doc("Expense Claim", d.reference_name)
|
|
||||||
if self.docstatus == 2:
|
|
||||||
update_reimbursed_amount(doc, -1 * d.debit)
|
|
||||||
else:
|
|
||||||
update_reimbursed_amount(doc, d.debit)
|
|
||||||
|
|
||||||
def validate_expense_claim(self):
|
|
||||||
for d in self.accounts:
|
|
||||||
if d.reference_type == "Expense Claim":
|
|
||||||
sanctioned_amount, reimbursed_amount = frappe.db.get_value(
|
|
||||||
"Expense Claim", d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed")
|
|
||||||
)
|
|
||||||
pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount)
|
|
||||||
if d.debit > pending_amount:
|
|
||||||
frappe.throw(
|
|
||||||
_(
|
|
||||||
"Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}"
|
|
||||||
).format(d.idx, d.reference_name, pending_amount)
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_credit_debit_note(self):
|
def validate_credit_debit_note(self):
|
||||||
if self.stock_entry:
|
if self.stock_entry:
|
||||||
if frappe.db.get_value("Stock Entry", self.stock_entry, "docstatus") != 1:
|
if frappe.db.get_value("Stock Entry", self.stock_entry, "docstatus") != 1:
|
||||||
|
|||||||
@ -110,8 +110,6 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||||
} else if (frm.doc.party_type == "Supplier") {
|
} else if (frm.doc.party_type == "Supplier") {
|
||||||
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
||||||
} else if (frm.doc.party_type == "Employee") {
|
|
||||||
var doctypes = ["Expense Claim", "Journal Entry"];
|
|
||||||
} else {
|
} else {
|
||||||
var doctypes = ["Journal Entry"];
|
var doctypes = ["Journal Entry"];
|
||||||
}
|
}
|
||||||
@ -140,17 +138,12 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
const child = locals[cdt][cdn];
|
const child = locals[cdt][cdn];
|
||||||
const filters = {"docstatus": 1, "company": doc.company};
|
const filters = {"docstatus": 1, "company": doc.company};
|
||||||
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
||||||
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
|
'Purchase Order', 'Dunning'];
|
||||||
|
|
||||||
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
||||||
filters[doc.party_type.toLowerCase()] = doc.party;
|
filters[doc.party_type.toLowerCase()] = doc.party;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(child.reference_doctype == "Expense Claim") {
|
|
||||||
filters["docstatus"] = 1;
|
|
||||||
filters["is_paid"] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: filters
|
filters: filters
|
||||||
};
|
};
|
||||||
@ -730,7 +723,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
c.payment_term = d.payment_term;
|
c.payment_term = d.payment_term;
|
||||||
c.allocated_amount = d.allocated_amount;
|
c.allocated_amount = d.allocated_amount;
|
||||||
|
|
||||||
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) {
|
if(!in_list(frm.events.get_order_doctypes(frm), d.voucher_type)) {
|
||||||
if(flt(d.outstanding_amount) > 0)
|
if(flt(d.outstanding_amount) > 0)
|
||||||
total_positive_outstanding += flt(d.outstanding_amount);
|
total_positive_outstanding += flt(d.outstanding_amount);
|
||||||
else
|
else
|
||||||
@ -745,7 +738,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
} else {
|
} else {
|
||||||
c.exchange_rate = 1;
|
c.exchange_rate = 1;
|
||||||
}
|
}
|
||||||
if (in_list(['Sales Invoice', 'Purchase Invoice', "Expense Claim", "Fees"], d.reference_doctype)){
|
if (in_list(frm.events.get_invoice_doctypes(frm), d.reference_doctype)){
|
||||||
c.due_date = d.due_date;
|
c.due_date = d.due_date;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -776,6 +769,14 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get_order_doctypes: function(frm) {
|
||||||
|
return ["Sales Order", "Purchase Order"];
|
||||||
|
},
|
||||||
|
|
||||||
|
get_invoice_doctypes: function(frm) {
|
||||||
|
return ["Sales Invoice", "Purchase Invoice"];
|
||||||
|
},
|
||||||
|
|
||||||
allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) {
|
allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) {
|
||||||
var total_positive_outstanding_including_order = 0;
|
var total_positive_outstanding_including_order = 0;
|
||||||
var total_negative_outstanding = 0;
|
var total_negative_outstanding = 0;
|
||||||
@ -946,14 +947,6 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry", [row.idx]));
|
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry", [row.idx]));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(frm.doc.party_type=="Employee" &&
|
|
||||||
!in_list(["Expense Claim", "Journal Entry"], row.reference_doctype)
|
|
||||||
) {
|
|
||||||
frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null);
|
|
||||||
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row) {
|
if (row) {
|
||||||
|
|||||||
@ -29,7 +29,6 @@ from erpnext.controllers.accounts_controller import (
|
|||||||
get_supplier_block_status,
|
get_supplier_block_status,
|
||||||
validate_taxes_and_charges,
|
validate_taxes_and_charges,
|
||||||
)
|
)
|
||||||
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
|
|
||||||
from erpnext.setup.utils import get_exchange_rate
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
|
|
||||||
|
|
||||||
@ -88,7 +87,6 @@ class PaymentEntry(AccountsController):
|
|||||||
if self.difference_amount:
|
if self.difference_amount:
|
||||||
frappe.throw(_("Difference Amount must be zero"))
|
frappe.throw(_("Difference Amount must be zero"))
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
self.update_expense_claim()
|
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_payment_schedule()
|
self.update_payment_schedule()
|
||||||
@ -97,7 +95,6 @@ class PaymentEntry(AccountsController):
|
|||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
||||||
self.make_gl_entries(cancel=1)
|
self.make_gl_entries(cancel=1)
|
||||||
self.update_expense_claim()
|
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.delink_advance_entry_references()
|
self.delink_advance_entry_references()
|
||||||
@ -296,14 +293,7 @@ class PaymentEntry(AccountsController):
|
|||||||
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
|
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
|
||||||
|
|
||||||
def validate_reference_documents(self):
|
def validate_reference_documents(self):
|
||||||
if self.party_type == "Customer":
|
valid_reference_doctypes = self.get_valid_reference_doctypes()
|
||||||
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
|
|
||||||
elif self.party_type == "Supplier":
|
|
||||||
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
|
||||||
elif self.party_type == "Employee":
|
|
||||||
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
|
|
||||||
elif self.party_type == "Shareholder":
|
|
||||||
valid_reference_doctypes = "Journal Entry"
|
|
||||||
|
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if not d.allocated_amount:
|
if not d.allocated_amount:
|
||||||
@ -329,7 +319,7 @@ class PaymentEntry(AccountsController):
|
|||||||
else:
|
else:
|
||||||
self.validate_journal_entry()
|
self.validate_journal_entry()
|
||||||
|
|
||||||
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Expense Claim", "Fees"):
|
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
|
||||||
if self.party_type == "Customer":
|
if self.party_type == "Customer":
|
||||||
ref_party_account = (
|
ref_party_account = (
|
||||||
get_party_account_based_on_invoice_discounting(d.reference_name) or ref_doc.debit_to
|
get_party_account_based_on_invoice_discounting(d.reference_name) or ref_doc.debit_to
|
||||||
@ -355,6 +345,16 @@ class PaymentEntry(AccountsController):
|
|||||||
if ref_doc.docstatus != 1:
|
if ref_doc.docstatus != 1:
|
||||||
frappe.throw(_("{0} {1} must be submitted").format(d.reference_doctype, d.reference_name))
|
frappe.throw(_("{0} {1} must be submitted").format(d.reference_doctype, d.reference_name))
|
||||||
|
|
||||||
|
def get_valid_reference_doctypes(self):
|
||||||
|
if self.party_type == "Customer":
|
||||||
|
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
|
||||||
|
elif self.party_type == "Supplier":
|
||||||
|
return ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
||||||
|
elif self.party_type == "Shareholder":
|
||||||
|
return ("Journal Entry",)
|
||||||
|
elif self.party_type == "Employee":
|
||||||
|
return ("Journal Entry",)
|
||||||
|
|
||||||
def validate_paid_invoices(self):
|
def validate_paid_invoices(self):
|
||||||
no_oustanding_refs = {}
|
no_oustanding_refs = {}
|
||||||
|
|
||||||
@ -980,24 +980,9 @@ class PaymentEntry(AccountsController):
|
|||||||
def update_advance_paid(self):
|
def update_advance_paid(self):
|
||||||
if self.payment_type in ("Receive", "Pay") and self.party:
|
if self.payment_type in ("Receive", "Pay") and self.party:
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if d.allocated_amount and d.reference_doctype in (
|
if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
|
||||||
"Sales Order",
|
|
||||||
"Purchase Order",
|
|
||||||
"Employee Advance",
|
|
||||||
"Gratuity",
|
|
||||||
):
|
|
||||||
frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
|
frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
|
||||||
|
|
||||||
def update_expense_claim(self):
|
|
||||||
if self.payment_type in ("Pay") and self.party:
|
|
||||||
for d in self.get("references"):
|
|
||||||
if d.reference_doctype == "Expense Claim" and d.reference_name:
|
|
||||||
doc = frappe.get_doc("Expense Claim", d.reference_name)
|
|
||||||
if self.docstatus == 2:
|
|
||||||
update_reimbursed_amount(doc, -1 * d.allocated_amount)
|
|
||||||
else:
|
|
||||||
update_reimbursed_amount(doc, d.allocated_amount)
|
|
||||||
|
|
||||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||||
self.reference_no = reference_doc.name
|
self.reference_no = reference_doc.name
|
||||||
self.reference_date = nowdate()
|
self.reference_date = nowdate()
|
||||||
@ -1191,7 +1176,6 @@ def validate_inclusive_tax(tax, doc):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_outstanding_reference_documents(args):
|
def get_outstanding_reference_documents(args):
|
||||||
|
|
||||||
if isinstance(args, str):
|
if isinstance(args, str):
|
||||||
args = json.loads(args)
|
args = json.loads(args)
|
||||||
|
|
||||||
@ -1260,7 +1244,7 @@ def get_outstanding_reference_documents(args):
|
|||||||
for d in outstanding_invoices:
|
for d in outstanding_invoices:
|
||||||
d["exchange_rate"] = 1
|
d["exchange_rate"] = 1
|
||||||
if party_account_currency != company_currency:
|
if party_account_currency != company_currency:
|
||||||
if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
|
if d.voucher_type in frappe.get_hooks("invoice_doctypes"):
|
||||||
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
|
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
|
||||||
elif d.voucher_type == "Journal Entry":
|
elif d.voucher_type == "Journal Entry":
|
||||||
d["exchange_rate"] = get_exchange_rate(
|
d["exchange_rate"] = get_exchange_rate(
|
||||||
@ -1587,20 +1571,17 @@ def get_outstanding_on_journal_entry(name):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_reference_details(reference_doctype, reference_name, party_account_currency):
|
def get_reference_details(reference_doctype, reference_name, party_account_currency):
|
||||||
total_amount = outstanding_amount = exchange_rate = bill_no = None
|
total_amount = outstanding_amount = exchange_rate = None
|
||||||
|
|
||||||
ref_doc = frappe.get_doc(reference_doctype, reference_name)
|
ref_doc = frappe.get_doc(reference_doctype, reference_name)
|
||||||
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
|
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
|
||||||
ref_doc.company
|
ref_doc.company
|
||||||
)
|
)
|
||||||
|
|
||||||
if reference_doctype == "Fees":
|
if reference_doctype == "Dunning":
|
||||||
total_amount = ref_doc.get("grand_total")
|
total_amount = outstanding_amount = ref_doc.get("dunning_amount")
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
|
||||||
elif reference_doctype == "Dunning":
|
|
||||||
total_amount = ref_doc.get("dunning_amount")
|
|
||||||
exchange_rate = 1
|
|
||||||
outstanding_amount = ref_doc.get("dunning_amount")
|
|
||||||
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
|
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
|
||||||
total_amount = ref_doc.get("total_amount")
|
total_amount = ref_doc.get("total_amount")
|
||||||
if ref_doc.multi_currency:
|
if ref_doc.multi_currency:
|
||||||
@ -1610,16 +1591,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
else:
|
else:
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
|
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
|
||||||
|
|
||||||
elif reference_doctype != "Journal Entry":
|
elif reference_doctype != "Journal Entry":
|
||||||
if ref_doc.doctype == "Expense Claim":
|
|
||||||
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
|
|
||||||
elif ref_doc.doctype == "Employee Advance":
|
|
||||||
total_amount = ref_doc.advance_amount
|
|
||||||
exchange_rate = ref_doc.get("exchange_rate")
|
|
||||||
if party_account_currency != ref_doc.currency:
|
|
||||||
total_amount = flt(total_amount) * flt(exchange_rate)
|
|
||||||
elif ref_doc.doctype == "Gratuity":
|
|
||||||
total_amount = ref_doc.amount
|
|
||||||
if not total_amount:
|
if not total_amount:
|
||||||
if party_account_currency == company_currency:
|
if party_account_currency == company_currency:
|
||||||
total_amount = ref_doc.base_grand_total
|
total_amount = ref_doc.base_grand_total
|
||||||
@ -1632,26 +1605,12 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
exchange_rate = ref_doc.get("conversion_rate") or get_exchange_rate(
|
exchange_rate = ref_doc.get("conversion_rate") or get_exchange_rate(
|
||||||
party_account_currency, company_currency, ref_doc.posting_date
|
party_account_currency, company_currency, ref_doc.posting_date
|
||||||
)
|
)
|
||||||
|
|
||||||
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||||
bill_no = ref_doc.get("bill_no")
|
|
||||||
elif reference_doctype == "Expense Claim":
|
|
||||||
outstanding_amount = (
|
|
||||||
flt(ref_doc.get("total_sanctioned_amount"))
|
|
||||||
+ flt(ref_doc.get("total_taxes_and_charges"))
|
|
||||||
- flt(ref_doc.get("total_amount_reimbursed"))
|
|
||||||
- flt(ref_doc.get("total_advance_amount"))
|
|
||||||
)
|
|
||||||
elif reference_doctype == "Employee Advance":
|
|
||||||
outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount)
|
|
||||||
if party_account_currency != ref_doc.currency:
|
|
||||||
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
|
|
||||||
if party_account_currency == company_currency:
|
|
||||||
exchange_rate = 1
|
|
||||||
elif reference_doctype == "Gratuity":
|
|
||||||
outstanding_amount = ref_doc.amount - flt(ref_doc.paid_amount)
|
|
||||||
else:
|
else:
|
||||||
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Get the exchange rate based on the posting date of the ref doc.
|
# Get the exchange rate based on the posting date of the ref doc.
|
||||||
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
|
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
|
||||||
@ -1662,114 +1621,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
"total_amount": flt(total_amount),
|
"total_amount": flt(total_amount),
|
||||||
"outstanding_amount": flt(outstanding_amount),
|
"outstanding_amount": flt(outstanding_amount),
|
||||||
"exchange_rate": flt(exchange_rate),
|
"exchange_rate": flt(exchange_rate),
|
||||||
"bill_no": bill_no,
|
"bill_no": ref_doc.get("bill_no"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_amounts_based_on_reference_doctype(
|
|
||||||
reference_doctype, ref_doc, party_account_currency, company_currency, reference_name
|
|
||||||
):
|
|
||||||
total_amount = outstanding_amount = exchange_rate = None
|
|
||||||
if reference_doctype == "Fees":
|
|
||||||
total_amount = ref_doc.get("grand_total")
|
|
||||||
exchange_rate = 1
|
|
||||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
|
||||||
elif reference_doctype == "Dunning":
|
|
||||||
total_amount = ref_doc.get("dunning_amount")
|
|
||||||
exchange_rate = 1
|
|
||||||
outstanding_amount = ref_doc.get("dunning_amount")
|
|
||||||
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
|
|
||||||
total_amount = ref_doc.get("total_amount")
|
|
||||||
if ref_doc.multi_currency:
|
|
||||||
exchange_rate = get_exchange_rate(
|
|
||||||
party_account_currency, company_currency, ref_doc.posting_date
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
exchange_rate = 1
|
|
||||||
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
|
|
||||||
|
|
||||||
return total_amount, outstanding_amount, exchange_rate
|
|
||||||
|
|
||||||
|
|
||||||
def get_amounts_based_on_ref_doc(
|
|
||||||
reference_doctype, ref_doc, party_account_currency, company_currency
|
|
||||||
):
|
|
||||||
total_amount = outstanding_amount = exchange_rate = None
|
|
||||||
if ref_doc.doctype == "Expense Claim":
|
|
||||||
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
|
|
||||||
elif ref_doc.doctype == "Employee Advance":
|
|
||||||
total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(
|
|
||||||
party_account_currency, ref_doc
|
|
||||||
)
|
|
||||||
|
|
||||||
if not total_amount:
|
|
||||||
total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency(
|
|
||||||
party_account_currency, company_currency, ref_doc
|
|
||||||
)
|
|
||||||
|
|
||||||
if not exchange_rate:
|
|
||||||
# Get the exchange rate from the original ref doc
|
|
||||||
# or get it based on the posting date of the ref doc
|
|
||||||
exchange_rate = ref_doc.get("conversion_rate") or get_exchange_rate(
|
|
||||||
party_account_currency, company_currency, ref_doc.posting_date
|
|
||||||
)
|
|
||||||
|
|
||||||
outstanding_amount, exchange_rate, bill_no = get_bill_no_and_update_amounts(
|
|
||||||
reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency
|
|
||||||
)
|
|
||||||
|
|
||||||
return total_amount, outstanding_amount, exchange_rate, bill_no
|
|
||||||
|
|
||||||
|
|
||||||
def get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc):
|
|
||||||
total_amount = ref_doc.advance_amount
|
|
||||||
exchange_rate = ref_doc.get("exchange_rate")
|
|
||||||
if party_account_currency != ref_doc.currency:
|
|
||||||
total_amount = flt(total_amount) * flt(exchange_rate)
|
|
||||||
|
|
||||||
return total_amount, exchange_rate
|
|
||||||
|
|
||||||
|
|
||||||
def get_total_amount_exchange_rate_base_on_currency(
|
|
||||||
party_account_currency, company_currency, ref_doc
|
|
||||||
):
|
|
||||||
exchange_rate = None
|
|
||||||
if party_account_currency == company_currency:
|
|
||||||
total_amount = ref_doc.base_grand_total
|
|
||||||
exchange_rate = 1
|
|
||||||
else:
|
|
||||||
total_amount = ref_doc.grand_total
|
|
||||||
|
|
||||||
return total_amount, exchange_rate
|
|
||||||
|
|
||||||
|
|
||||||
def get_bill_no_and_update_amounts(
|
|
||||||
reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency
|
|
||||||
):
|
|
||||||
outstanding_amount = bill_no = None
|
|
||||||
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
|
||||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
|
||||||
bill_no = ref_doc.get("bill_no")
|
|
||||||
elif reference_doctype == "Expense Claim":
|
|
||||||
outstanding_amount = (
|
|
||||||
flt(ref_doc.get("total_sanctioned_amount"))
|
|
||||||
+ flt(ref_doc.get("total_taxes_and_charges"))
|
|
||||||
- flt(ref_doc.get("total_amount_reimbursed"))
|
|
||||||
- flt(ref_doc.get("total_advance_amount"))
|
|
||||||
)
|
|
||||||
elif reference_doctype == "Employee Advance":
|
|
||||||
outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount)
|
|
||||||
if party_account_currency != ref_doc.currency:
|
|
||||||
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
|
|
||||||
if party_account_currency == company_currency:
|
|
||||||
exchange_rate = 1
|
|
||||||
else:
|
|
||||||
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
|
||||||
|
|
||||||
return outstanding_amount, exchange_rate, bill_no
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
|
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
|
||||||
reference_doc = None
|
reference_doc = None
|
||||||
@ -1888,8 +1744,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
|||||||
pe.set_missing_values()
|
pe.set_missing_values()
|
||||||
|
|
||||||
if party_account and bank:
|
if party_account and bank:
|
||||||
if dt == "Employee Advance":
|
|
||||||
reference_doc = doc
|
|
||||||
pe.set_exchange_rate(ref_doc=reference_doc)
|
pe.set_exchange_rate(ref_doc=reference_doc)
|
||||||
pe.set_amounts()
|
pe.set_amounts()
|
||||||
if discount_amount:
|
if discount_amount:
|
||||||
@ -1924,8 +1778,6 @@ def set_party_type(dt):
|
|||||||
party_type = "Customer"
|
party_type = "Customer"
|
||||||
elif dt in ("Purchase Invoice", "Purchase Order"):
|
elif dt in ("Purchase Invoice", "Purchase Order"):
|
||||||
party_type = "Supplier"
|
party_type = "Supplier"
|
||||||
elif dt in ("Expense Claim", "Employee Advance", "Gratuity"):
|
|
||||||
party_type = "Employee"
|
|
||||||
return party_type
|
return party_type
|
||||||
|
|
||||||
|
|
||||||
@ -1936,12 +1788,6 @@ def set_party_account(dt, dn, doc, party_type):
|
|||||||
party_account = doc.credit_to
|
party_account = doc.credit_to
|
||||||
elif dt == "Fees":
|
elif dt == "Fees":
|
||||||
party_account = doc.receivable_account
|
party_account = doc.receivable_account
|
||||||
elif dt == "Employee Advance":
|
|
||||||
party_account = doc.advance_account
|
|
||||||
elif dt == "Expense Claim":
|
|
||||||
party_account = doc.payable_account
|
|
||||||
elif dt == "Gratuity":
|
|
||||||
party_account = doc.payable_account
|
|
||||||
else:
|
else:
|
||||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||||
return party_account
|
return party_account
|
||||||
@ -1976,24 +1822,12 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
|
|||||||
else:
|
else:
|
||||||
grand_total = doc.rounded_total or doc.grand_total
|
grand_total = doc.rounded_total or doc.grand_total
|
||||||
outstanding_amount = doc.outstanding_amount
|
outstanding_amount = doc.outstanding_amount
|
||||||
elif dt in ("Expense Claim"):
|
|
||||||
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
|
|
||||||
outstanding_amount = doc.grand_total - doc.total_amount_reimbursed
|
|
||||||
elif dt == "Employee Advance":
|
|
||||||
grand_total = flt(doc.advance_amount)
|
|
||||||
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
|
|
||||||
if party_account_currency != doc.currency:
|
|
||||||
grand_total = flt(doc.advance_amount) * flt(doc.exchange_rate)
|
|
||||||
outstanding_amount = (flt(doc.advance_amount) - flt(doc.paid_amount)) * flt(doc.exchange_rate)
|
|
||||||
elif dt == "Fees":
|
elif dt == "Fees":
|
||||||
grand_total = doc.grand_total
|
grand_total = doc.grand_total
|
||||||
outstanding_amount = doc.outstanding_amount
|
outstanding_amount = doc.outstanding_amount
|
||||||
elif dt == "Dunning":
|
elif dt == "Dunning":
|
||||||
grand_total = doc.grand_total
|
grand_total = doc.grand_total
|
||||||
outstanding_amount = doc.grand_total
|
outstanding_amount = doc.grand_total
|
||||||
elif dt == "Gratuity":
|
|
||||||
grand_total = doc.amount
|
|
||||||
outstanding_amount = flt(doc.amount) - flt(doc.paid_amount)
|
|
||||||
else:
|
else:
|
||||||
if party_account_currency == doc.company_currency:
|
if party_account_currency == doc.company_currency:
|
||||||
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
||||||
@ -2015,8 +1849,6 @@ def set_paid_amount_and_received_amount(
|
|||||||
received_amount = bank_amount
|
received_amount = bank_amount
|
||||||
else:
|
else:
|
||||||
received_amount = paid_amount * doc.get("conversion_rate", 1)
|
received_amount = paid_amount * doc.get("conversion_rate", 1)
|
||||||
if dt == "Employee Advance":
|
|
||||||
received_amount = paid_amount * doc.get("exchange_rate", 1)
|
|
||||||
else:
|
else:
|
||||||
received_amount = abs(outstanding_amount)
|
received_amount = abs(outstanding_amount)
|
||||||
if bank_amount:
|
if bank_amount:
|
||||||
@ -2024,8 +1856,6 @@ def set_paid_amount_and_received_amount(
|
|||||||
else:
|
else:
|
||||||
# if party account currency and bank currency is different then populate paid amount as well
|
# if party account currency and bank currency is different then populate paid amount as well
|
||||||
paid_amount = received_amount * doc.get("conversion_rate", 1)
|
paid_amount = received_amount * doc.get("conversion_rate", 1)
|
||||||
if dt == "Employee Advance":
|
|
||||||
paid_amount = received_amount * doc.get("exchange_rate", 1)
|
|
||||||
|
|
||||||
return paid_amount, received_amount
|
return paid_amount, received_amount
|
||||||
|
|
||||||
|
|||||||
@ -19,8 +19,8 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
|||||||
create_sales_invoice,
|
create_sales_invoice,
|
||||||
create_sales_invoice_against_cost_center,
|
create_sales_invoice_against_cost_center,
|
||||||
)
|
)
|
||||||
from erpnext.hr.doctype.expense_claim.test_expense_claim import make_expense_claim
|
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
from erpnext.setup.doctype.employee.test_employee import make_employee
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
test_dependencies = ["Item"]
|
||||||
|
|
||||||
@ -297,31 +297,6 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
self.assertEqual(flt(outstanding_amount), 250)
|
self.assertEqual(flt(outstanding_amount), 250)
|
||||||
self.assertEqual(status, "Unpaid")
|
self.assertEqual(status, "Unpaid")
|
||||||
|
|
||||||
def test_payment_entry_against_ec(self):
|
|
||||||
|
|
||||||
payable = frappe.get_cached_value("Company", "_Test Company", "default_payable_account")
|
|
||||||
ec = make_expense_claim(payable, 300, 300, "_Test Company", "Travel Expenses - _TC")
|
|
||||||
pe = get_payment_entry(
|
|
||||||
"Expense Claim", ec.name, bank_account="_Test Bank USD - _TC", bank_amount=300
|
|
||||||
)
|
|
||||||
pe.reference_no = "1"
|
|
||||||
pe.reference_date = "2016-01-01"
|
|
||||||
pe.source_exchange_rate = 1
|
|
||||||
pe.paid_to = payable
|
|
||||||
pe.insert()
|
|
||||||
pe.submit()
|
|
||||||
|
|
||||||
expected_gle = dict(
|
|
||||||
(d[0], d) for d in [[payable, 300, 0, ec.name], ["_Test Bank USD - _TC", 0, 300, None]]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.validate_gl_entries(pe.name, expected_gle)
|
|
||||||
|
|
||||||
outstanding_amount = flt(
|
|
||||||
frappe.db.get_value("Expense Claim", ec.name, "total_sanctioned_amount")
|
|
||||||
) - flt(frappe.db.get_value("Expense Claim", ec.name, "total_amount_reimbursed"))
|
|
||||||
self.assertEqual(outstanding_amount, 0)
|
|
||||||
|
|
||||||
def test_payment_entry_against_si_usd_to_inr(self):
|
def test_payment_entry_against_si_usd_to_inr(self):
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
customer="_Test Customer USD",
|
customer="_Test Customer USD",
|
||||||
@ -762,6 +737,10 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertTrue("is on hold" in str(err.exception).lower())
|
self.assertTrue("is on hold" in str(err.exception).lower())
|
||||||
|
|
||||||
|
def test_payment_entry_for_employee(self):
|
||||||
|
employee = make_employee("test_payment_entry@salary.com", company="_Test Company")
|
||||||
|
create_payment_entry(party_type="Employee", party=employee, save=True)
|
||||||
|
|
||||||
|
|
||||||
def create_payment_entry(**args):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
payment_entry = frappe.new_doc("Payment Entry")
|
||||||
|
|||||||
@ -40,19 +40,22 @@
|
|||||||
"fieldname": "account",
|
"fieldname": "account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Account",
|
"label": "Account",
|
||||||
"options": "Account"
|
"options": "Account",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_type",
|
"fieldname": "party_type",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Party Type",
|
"label": "Party Type",
|
||||||
"options": "DocType"
|
"options": "DocType",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party",
|
"fieldname": "party",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"label": "Party",
|
"label": "Party",
|
||||||
"options": "party_type"
|
"options": "party_type",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "voucher_type",
|
"fieldname": "voucher_type",
|
||||||
@ -114,7 +117,8 @@
|
|||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company"
|
"options": "Company",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "cost_center",
|
"fieldname": "cost_center",
|
||||||
@ -137,7 +141,7 @@
|
|||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-05-30 19:04:55.532171",
|
"modified": "2022-07-11 09:13:54.379168",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Ledger Entry",
|
"name": "Payment Ledger Entry",
|
||||||
|
|||||||
@ -147,3 +147,8 @@ class PaymentLedgerEntry(Document):
|
|||||||
update_voucher_outstanding(
|
update_voucher_outstanding(
|
||||||
self.against_voucher_type, self.against_voucher_no, self.account, self.party_type, self.party
|
self.against_voucher_type, self.against_voucher_no, self.account, self.party_type, self.party
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def on_doctype_update():
|
||||||
|
frappe.db.add_index("Payment Ledger Entry", ["against_voucher_no", "against_voucher_type"])
|
||||||
|
frappe.db.add_index("Payment Ledger Entry", ["voucher_no", "voucher_type"])
|
||||||
|
|||||||
@ -10,10 +10,11 @@
|
|||||||
"fiscal_year",
|
"fiscal_year",
|
||||||
"amended_from",
|
"amended_from",
|
||||||
"company",
|
"company",
|
||||||
"cost_center_wise_pnl",
|
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"closing_account_head",
|
"closing_account_head",
|
||||||
"remarks"
|
"remarks",
|
||||||
|
"gle_processing_status",
|
||||||
|
"error_message"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -86,17 +87,26 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"depends_on": "eval:doc.docstatus!=0",
|
||||||
"fieldname": "cost_center_wise_pnl",
|
"fieldname": "gle_processing_status",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Select",
|
||||||
"label": "Book Cost Center Wise Profit/Loss"
|
"label": "GL Entry Processing Status",
|
||||||
|
"options": "In Progress\nCompleted\nFailed",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.gle_processing_status=='Failed'",
|
||||||
|
"fieldname": "error_message",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Error Message",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-05-20 15:27:37.210458",
|
"modified": "2022-07-20 14:51:04.714154",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Period Closing Voucher",
|
"name": "Period Closing Voucher",
|
||||||
|
|||||||
@ -8,7 +8,6 @@ from frappe.utils import flt
|
|||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
get_dimensions,
|
|
||||||
)
|
)
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
@ -20,13 +19,28 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
self.validate_posting_date()
|
self.validate_posting_date()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
self.db_set("gle_processing_status", "In Progress")
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
self.db_set("gle_processing_status", "In Progress")
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
||||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
gle_count = frappe.db.count(
|
||||||
|
"GL Entry",
|
||||||
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
|
{"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0},
|
||||||
|
)
|
||||||
|
if gle_count > 5000:
|
||||||
|
frappe.enqueue(
|
||||||
|
make_reverse_gl_entries,
|
||||||
|
voucher_type="Period Closing Voucher",
|
||||||
|
voucher_no=self.name,
|
||||||
|
queue="long",
|
||||||
|
)
|
||||||
|
frappe.msgprint(
|
||||||
|
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
|
||||||
|
|
||||||
def validate_account_head(self):
|
def validate_account_head(self):
|
||||||
closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")
|
closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")
|
||||||
@ -67,90 +81,80 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
def make_gl_entries(self):
|
def make_gl_entries(self):
|
||||||
gl_entries = self.get_gl_entries()
|
gl_entries = self.get_gl_entries()
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
if len(gl_entries) > 5000:
|
||||||
|
frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long")
|
||||||
make_gl_entries(gl_entries)
|
frappe.msgprint(
|
||||||
|
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
process_gl_entries(gl_entries)
|
||||||
|
|
||||||
def get_gl_entries(self):
|
def get_gl_entries(self):
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
pl_accounts = self.get_pl_balances()
|
|
||||||
|
|
||||||
for acc in pl_accounts:
|
# pl account
|
||||||
|
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True):
|
||||||
if flt(acc.bal_in_company_currency):
|
if flt(acc.bal_in_company_currency):
|
||||||
gl_entries.append(
|
gl_entries.append(self.get_gle_for_pl_account(acc))
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": acc.account,
|
|
||||||
"cost_center": acc.cost_center,
|
|
||||||
"finance_book": acc.finance_book,
|
|
||||||
"account_currency": acc.account_currency,
|
|
||||||
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
|
|
||||||
if flt(acc.bal_in_account_currency) < 0
|
|
||||||
else 0,
|
|
||||||
"debit": abs(flt(acc.bal_in_company_currency))
|
|
||||||
if flt(acc.bal_in_company_currency) < 0
|
|
||||||
else 0,
|
|
||||||
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
|
|
||||||
if flt(acc.bal_in_account_currency) > 0
|
|
||||||
else 0,
|
|
||||||
"credit": abs(flt(acc.bal_in_company_currency))
|
|
||||||
if flt(acc.bal_in_company_currency) > 0
|
|
||||||
else 0,
|
|
||||||
},
|
|
||||||
item=acc,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if gl_entries:
|
# closing liability account
|
||||||
gle_for_net_pl_bal = self.get_pnl_gl_entry(pl_accounts)
|
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False):
|
||||||
gl_entries += gle_for_net_pl_bal
|
if flt(acc.bal_in_company_currency):
|
||||||
|
gl_entries.append(self.get_gle_for_closing_account(acc))
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
def get_pnl_gl_entry(self, pl_accounts):
|
def get_gle_for_pl_account(self, acc):
|
||||||
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
gl_entry = self.get_gl_dict(
|
||||||
gl_entries = []
|
{
|
||||||
|
"account": acc.account,
|
||||||
|
"cost_center": acc.cost_center,
|
||||||
|
"finance_book": acc.finance_book,
|
||||||
|
"account_currency": acc.account_currency,
|
||||||
|
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
|
||||||
|
if flt(acc.bal_in_account_currency) < 0
|
||||||
|
else 0,
|
||||||
|
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
|
||||||
|
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
|
||||||
|
if flt(acc.bal_in_account_currency) > 0
|
||||||
|
else 0,
|
||||||
|
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
|
||||||
|
},
|
||||||
|
item=acc,
|
||||||
|
)
|
||||||
|
self.update_default_dimensions(gl_entry, acc)
|
||||||
|
return gl_entry
|
||||||
|
|
||||||
for acc in pl_accounts:
|
def get_gle_for_closing_account(self, acc):
|
||||||
if flt(acc.bal_in_company_currency):
|
gl_entry = self.get_gl_dict(
|
||||||
cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center
|
{
|
||||||
gl_entry = self.get_gl_dict(
|
"account": self.closing_account_head,
|
||||||
{
|
"cost_center": acc.cost_center,
|
||||||
"account": self.closing_account_head,
|
"finance_book": acc.finance_book,
|
||||||
"cost_center": cost_center,
|
"account_currency": acc.account_currency,
|
||||||
"finance_book": acc.finance_book,
|
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
|
||||||
"account_currency": acc.account_currency,
|
if flt(acc.bal_in_account_currency) > 0
|
||||||
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
|
else 0,
|
||||||
if flt(acc.bal_in_account_currency) > 0
|
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
|
||||||
else 0,
|
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
|
||||||
"debit": abs(flt(acc.bal_in_company_currency))
|
if flt(acc.bal_in_account_currency) < 0
|
||||||
if flt(acc.bal_in_company_currency) > 0
|
else 0,
|
||||||
else 0,
|
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
|
||||||
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
|
},
|
||||||
if flt(acc.bal_in_account_currency) < 0
|
item=acc,
|
||||||
else 0,
|
)
|
||||||
"credit": abs(flt(acc.bal_in_company_currency))
|
self.update_default_dimensions(gl_entry, acc)
|
||||||
if flt(acc.bal_in_company_currency) < 0
|
return gl_entry
|
||||||
else 0,
|
|
||||||
},
|
|
||||||
item=acc,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.update_default_dimensions(gl_entry)
|
def update_default_dimensions(self, gl_entry, acc):
|
||||||
|
|
||||||
gl_entries.append(gl_entry)
|
|
||||||
|
|
||||||
return gl_entries
|
|
||||||
|
|
||||||
def update_default_dimensions(self, gl_entry):
|
|
||||||
if not self.accounting_dimensions:
|
if not self.accounting_dimensions:
|
||||||
self.accounting_dimensions = get_accounting_dimensions()
|
self.accounting_dimensions = get_accounting_dimensions()
|
||||||
|
|
||||||
_, default_dimensions = get_dimensions()
|
|
||||||
for dimension in self.accounting_dimensions:
|
for dimension in self.accounting_dimensions:
|
||||||
gl_entry.update({dimension: default_dimensions.get(self.company, {}).get(dimension)})
|
gl_entry.update({dimension: acc.get(dimension)})
|
||||||
|
|
||||||
def get_pl_balances(self):
|
def get_pl_balances_based_on_dimensions(self, group_by_account=False):
|
||||||
"""Get balance for dimension-wise pl accounts"""
|
"""Get balance for dimension-wise pl accounts"""
|
||||||
|
|
||||||
dimension_fields = ["t1.cost_center", "t1.finance_book"]
|
dimension_fields = ["t1.cost_center", "t1.finance_book"]
|
||||||
@ -159,20 +163,56 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
for dimension in self.accounting_dimensions:
|
for dimension in self.accounting_dimensions:
|
||||||
dimension_fields.append("t1.{0}".format(dimension))
|
dimension_fields.append("t1.{0}".format(dimension))
|
||||||
|
|
||||||
|
if group_by_account:
|
||||||
|
dimension_fields.append("t1.account")
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
t1.account, t2.account_currency, {dimension_fields},
|
t2.account_currency,
|
||||||
|
{dimension_fields},
|
||||||
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
|
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
|
||||||
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
|
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
|
||||||
from `tabGL Entry` t1, `tabAccount` t2
|
from `tabGL Entry` t1, `tabAccount` t2
|
||||||
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
|
where
|
||||||
and t2.docstatus < 2 and t2.company = %s
|
t1.is_cancelled = 0
|
||||||
and t1.posting_date between %s and %s
|
and t1.account = t2.name
|
||||||
group by t1.account, {dimension_fields}
|
and t2.report_type = 'Profit and Loss'
|
||||||
|
and t2.docstatus < 2
|
||||||
|
and t2.company = %s
|
||||||
|
and t1.posting_date between %s and %s
|
||||||
|
group by {dimension_fields}
|
||||||
""".format(
|
""".format(
|
||||||
dimension_fields=", ".join(dimension_fields)
|
dimension_fields=", ".join(dimension_fields)
|
||||||
),
|
),
|
||||||
(self.company, self.get("year_start_date"), self.posting_date),
|
(self.company, self.get("year_start_date"), self.posting_date),
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def process_gl_entries(gl_entries):
|
||||||
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
|
try:
|
||||||
|
make_gl_entries(gl_entries, merge_entries=False)
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(e)
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_reverse_gl_entries(voucher_type, voucher_no):
|
||||||
|
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||||
|
|
||||||
|
try:
|
||||||
|
make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no)
|
||||||
|
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed")
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(e)
|
||||||
|
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed")
|
||||||
|
|||||||
@ -49,7 +49,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
|
|
||||||
expected_gle = (
|
expected_gle = (
|
||||||
("Cost of Goods Sold - TPC", 0.0, 600.0),
|
("Cost of Goods Sold - TPC", 0.0, 600.0),
|
||||||
(surplus_account, 600.0, 400.0),
|
(surplus_account, 200.0, 0.0),
|
||||||
("Sales - TPC", 400.0, 0.0),
|
("Sales - TPC", 400.0, 0.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,7 +59,8 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
""",
|
""",
|
||||||
(pcv.name),
|
(pcv.name),
|
||||||
)
|
)
|
||||||
|
pcv.reload()
|
||||||
|
self.assertEqual(pcv.gle_processing_status, "Completed")
|
||||||
self.assertEqual(pcv_gle, expected_gle)
|
self.assertEqual(pcv_gle, expected_gle)
|
||||||
|
|
||||||
def test_cost_center_wise_posting(self):
|
def test_cost_center_wise_posting(self):
|
||||||
@ -93,7 +94,6 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
pcv = self.make_period_closing_voucher(submit=False)
|
pcv = self.make_period_closing_voucher(submit=False)
|
||||||
pcv.cost_center_wise_pnl = 1
|
|
||||||
pcv.save()
|
pcv.save()
|
||||||
pcv.submit()
|
pcv.submit()
|
||||||
surplus_account = pcv.closing_account_head
|
surplus_account = pcv.closing_account_head
|
||||||
@ -114,7 +114,17 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
(pcv.name),
|
(pcv.name),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(pcv_gle, expected_gle)
|
self.assertSequenceEqual(pcv_gle, expected_gle)
|
||||||
|
|
||||||
|
pcv.reload()
|
||||||
|
pcv.cancel()
|
||||||
|
|
||||||
|
self.assertFalse(
|
||||||
|
frappe.db.get_value(
|
||||||
|
"GL Entry",
|
||||||
|
{"voucher_type": "Period Closing Voucher", "voucher_no": pcv.name, "is_cancelled": 0},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def test_period_closing_with_finance_book_entries(self):
|
def test_period_closing_with_finance_book_entries(self):
|
||||||
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||||
@ -165,7 +175,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
(pcv.name),
|
(pcv.name),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(pcv_gle, expected_gle)
|
self.assertSequenceEqual(pcv_gle, expected_gle)
|
||||||
|
|
||||||
def make_period_closing_voucher(self, submit=True):
|
def make_period_closing_voucher(self, submit=True):
|
||||||
surplus_account = create_account()
|
surplus_account = create_account()
|
||||||
|
|||||||
@ -222,9 +222,6 @@ class POSInvoice(SalesInvoice):
|
|||||||
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
is_service_item = not (frappe.db.get_value("Item", d.get("item_code"), "is_stock_item"))
|
|
||||||
if is_service_item:
|
|
||||||
return
|
|
||||||
if d.serial_no:
|
if d.serial_no:
|
||||||
self.validate_pos_reserved_serial_nos(d)
|
self.validate_pos_reserved_serial_nos(d)
|
||||||
self.validate_delivered_serial_nos(d)
|
self.validate_delivered_serial_nos(d)
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from frappe import _
|
|||||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.mapper import map_child_doc, map_doc
|
from frappe.model.mapper import map_child_doc, map_doc
|
||||||
from frappe.utils import flt, getdate, nowdate
|
from frappe.utils import cint, flt, getdate, nowdate
|
||||||
from frappe.utils.background_jobs import enqueue
|
from frappe.utils.background_jobs import enqueue
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
@ -219,6 +219,9 @@ class POSInvoiceMergeLog(Document):
|
|||||||
invoice.taxes_and_charges = None
|
invoice.taxes_and_charges = None
|
||||||
invoice.ignore_pricing_rule = 1
|
invoice.ignore_pricing_rule = 1
|
||||||
invoice.customer = self.customer
|
invoice.customer = self.customer
|
||||||
|
invoice.disable_rounded_total = cint(
|
||||||
|
frappe.db.get_value("POS Profile", invoice.pos_profile, "disable_rounded_total")
|
||||||
|
)
|
||||||
|
|
||||||
if self.merge_invoices_based_on == "Customer Group":
|
if self.merge_invoices_based_on == "Customer Group":
|
||||||
invoice.flags.ignore_pos_profile = True
|
invoice.flags.ignore_pos_profile = True
|
||||||
|
|||||||
@ -44,6 +44,7 @@
|
|||||||
"write_off_account",
|
"write_off_account",
|
||||||
"write_off_cost_center",
|
"write_off_cost_center",
|
||||||
"account_for_change_amount",
|
"account_for_change_amount",
|
||||||
|
"disable_rounded_total",
|
||||||
"column_break_23",
|
"column_break_23",
|
||||||
"income_account",
|
"income_account",
|
||||||
"expense_account",
|
"expense_account",
|
||||||
@ -358,6 +359,13 @@
|
|||||||
"fieldname": "validate_stock_on_save",
|
"fieldname": "validate_stock_on_save",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Validate Stock on Save"
|
"label": "Validate Stock on Save"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "If enabled, the consolidated invoices will have rounded total disabled",
|
||||||
|
"fieldname": "disable_rounded_total",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Disable Rounded Total"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@ -385,7 +393,7 @@
|
|||||||
"link_fieldname": "pos_profile"
|
"link_fieldname": "pos_profile"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-03-21 13:29:28.480533",
|
"modified": "2022-07-21 11:16:46.911173",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Profile",
|
"name": "POS Profile",
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"creation": "2016-07-27 17:24:24.956896",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"company",
|
|
||||||
"account"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "company",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Company",
|
|
||||||
"options": "Company"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
|
|
||||||
"fieldname": "account",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Account",
|
|
||||||
"options": "Account"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"istable": 1,
|
|
||||||
"links": [],
|
|
||||||
"modified": "2020-10-18 17:57:57.110257",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Salary Component Account",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC"
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class SalaryComponentAccount(Document):
|
|
||||||
pass
|
|
||||||
@ -412,7 +412,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval: doc.is_return && doc.return_against",
|
"depends_on": "eval: doc.is_return",
|
||||||
"fieldname": "update_billed_amount_in_sales_order",
|
"fieldname": "update_billed_amount_in_sales_order",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@ -2022,7 +2022,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-06-16 16:22:44.870575",
|
"modified": "2022-07-11 17:43:56.435382",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@ -168,6 +168,7 @@ def get_cost_center_allocation_data(company, posting_date):
|
|||||||
def merge_similar_entries(gl_map, precision=None):
|
def merge_similar_entries(gl_map, precision=None):
|
||||||
merged_gl_map = []
|
merged_gl_map = []
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
|
|
||||||
for entry in gl_map:
|
for entry in gl_map:
|
||||||
# if there is already an entry in this account then just add it
|
# if there is already an entry in this account then just add it
|
||||||
# to that entry
|
# to that entry
|
||||||
@ -298,9 +299,10 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
|
|||||||
gle.flags.from_repost = from_repost
|
gle.flags.from_repost = from_repost
|
||||||
gle.flags.adv_adj = adv_adj
|
gle.flags.adv_adj = adv_adj
|
||||||
gle.flags.update_outstanding = update_outstanding or "Yes"
|
gle.flags.update_outstanding = update_outstanding or "Yes"
|
||||||
|
gle.flags.notify_update = False
|
||||||
gle.submit()
|
gle.submit()
|
||||||
|
|
||||||
if not from_repost:
|
if not from_repost and gle.voucher_type != "Period Closing Voucher":
|
||||||
validate_expense_against_budget(args)
|
validate_expense_against_budget(args)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -198,12 +198,10 @@ def get_loan_entries(filters):
|
|||||||
amount_field = (loan_doc.disbursed_amount).as_("credit")
|
amount_field = (loan_doc.disbursed_amount).as_("credit")
|
||||||
posting_date = (loan_doc.disbursement_date).as_("posting_date")
|
posting_date = (loan_doc.disbursement_date).as_("posting_date")
|
||||||
account = loan_doc.disbursement_account
|
account = loan_doc.disbursement_account
|
||||||
salary_condition = loan_doc.docstatus == 1
|
|
||||||
else:
|
else:
|
||||||
amount_field = (loan_doc.amount_paid).as_("debit")
|
amount_field = (loan_doc.amount_paid).as_("debit")
|
||||||
posting_date = (loan_doc.posting_date).as_("posting_date")
|
posting_date = (loan_doc.posting_date).as_("posting_date")
|
||||||
account = loan_doc.payment_account
|
account = loan_doc.payment_account
|
||||||
salary_condition = loan_doc.repay_from_salary == 0
|
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(loan_doc)
|
frappe.qb.from_(loan_doc)
|
||||||
@ -216,12 +214,14 @@ def get_loan_entries(filters):
|
|||||||
posting_date,
|
posting_date,
|
||||||
)
|
)
|
||||||
.where(loan_doc.docstatus == 1)
|
.where(loan_doc.docstatus == 1)
|
||||||
.where(salary_condition)
|
|
||||||
.where(account == filters.get("account"))
|
.where(account == filters.get("account"))
|
||||||
.where(posting_date <= getdate(filters.get("report_date")))
|
.where(posting_date <= getdate(filters.get("report_date")))
|
||||||
.where(ifnull(loan_doc.clearance_date, "4000-01-01") > getdate(filters.get("report_date")))
|
.where(ifnull(loan_doc.clearance_date, "4000-01-01") > getdate(filters.get("report_date")))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if doctype == "Loan Repayment" and frappe.db.has_column("Loan Repayment", "repay_from_salary"):
|
||||||
|
query = query.where((loan_doc.repay_from_salary == 0))
|
||||||
|
|
||||||
entries = query.run(as_dict=1)
|
entries = query.run(as_dict=1)
|
||||||
loan_docs.extend(entries)
|
loan_docs.extend(entries)
|
||||||
|
|
||||||
@ -267,23 +267,24 @@ def get_loan_amount(filters):
|
|||||||
amount_field = Sum(loan_doc.disbursed_amount)
|
amount_field = Sum(loan_doc.disbursed_amount)
|
||||||
posting_date = (loan_doc.disbursement_date).as_("posting_date")
|
posting_date = (loan_doc.disbursement_date).as_("posting_date")
|
||||||
account = loan_doc.disbursement_account
|
account = loan_doc.disbursement_account
|
||||||
salary_condition = loan_doc.docstatus == 1
|
|
||||||
else:
|
else:
|
||||||
amount_field = Sum(loan_doc.amount_paid)
|
amount_field = Sum(loan_doc.amount_paid)
|
||||||
posting_date = (loan_doc.posting_date).as_("posting_date")
|
posting_date = (loan_doc.posting_date).as_("posting_date")
|
||||||
account = loan_doc.payment_account
|
account = loan_doc.payment_account
|
||||||
salary_condition = loan_doc.repay_from_salary == 0
|
|
||||||
amount = (
|
query = (
|
||||||
frappe.qb.from_(loan_doc)
|
frappe.qb.from_(loan_doc)
|
||||||
.select(amount_field)
|
.select(amount_field)
|
||||||
.where(loan_doc.docstatus == 1)
|
.where(loan_doc.docstatus == 1)
|
||||||
.where(salary_condition)
|
|
||||||
.where(account == filters.get("account"))
|
.where(account == filters.get("account"))
|
||||||
.where(posting_date > getdate(filters.get("report_date")))
|
.where(posting_date > getdate(filters.get("report_date")))
|
||||||
.where(ifnull(loan_doc.clearance_date, "4000-01-01") <= getdate(filters.get("report_date")))
|
.where(ifnull(loan_doc.clearance_date, "4000-01-01") <= getdate(filters.get("report_date")))
|
||||||
.run()[0][0]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if doctype == "Loan Repayment" and frappe.db.has_column("Loan Repayment", "repay_from_salary"):
|
||||||
|
query = query.where((loan_doc.repay_from_salary == 0))
|
||||||
|
|
||||||
|
amount = query.run()[0][0]
|
||||||
total_amount += flt(amount)
|
total_amount += flt(amount)
|
||||||
|
|
||||||
return total_amount
|
return total_amount
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
|
||||||
|
create_loan_and_repayment,
|
||||||
|
)
|
||||||
|
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
|
||||||
|
execute,
|
||||||
|
)
|
||||||
|
from erpnext.loan_management.doctype.loan.test_loan import create_loan_accounts
|
||||||
|
|
||||||
|
|
||||||
|
class TestBankReconciliationStatement(FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
for dt in [
|
||||||
|
"Loan Repayment",
|
||||||
|
"Loan Disbursement",
|
||||||
|
"Journal Entry",
|
||||||
|
"Journal Entry Account",
|
||||||
|
"Payment Entry",
|
||||||
|
]:
|
||||||
|
frappe.db.delete(dt)
|
||||||
|
|
||||||
|
def test_loan_entries_in_bank_reco_statement(self):
|
||||||
|
create_loan_accounts()
|
||||||
|
repayment_entry = create_loan_and_repayment()
|
||||||
|
|
||||||
|
filters = frappe._dict(
|
||||||
|
{
|
||||||
|
"company": "Test Company",
|
||||||
|
"account": "Payment Account - _TC",
|
||||||
|
"report_date": "2018-10-30",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = execute(filters)
|
||||||
|
|
||||||
|
self.assertEqual(result[1][0].payment_entry, repayment_entry.name)
|
||||||
@ -8,11 +8,11 @@ from frappe import _
|
|||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
validate_filters(filters)
|
validate_filters(filters)
|
||||||
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
|
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
|
||||||
|
|
||||||
columns = get_columns(filters)
|
columns = get_columns(filters)
|
||||||
|
|
||||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
|
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
|
||||||
return columns, res
|
return columns, res
|
||||||
|
|
||||||
|
|
||||||
@ -22,10 +22,11 @@ def validate_filters(filters):
|
|||||||
frappe.throw(_("From Date must be before To Date"))
|
frappe.throw(_("From Date must be before To Date"))
|
||||||
|
|
||||||
|
|
||||||
def get_result(filters, tds_docs, tds_accounts, tax_category_map):
|
def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map):
|
||||||
supplier_map = get_supplier_pan_map()
|
supplier_map = get_supplier_pan_map()
|
||||||
tax_rate_map = get_tax_rate_map(filters)
|
tax_rate_map = get_tax_rate_map(filters)
|
||||||
gle_map = get_gle_map(tds_docs)
|
gle_map = get_gle_map(tds_docs)
|
||||||
|
print(journal_entry_party_map)
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
for name, details in gle_map.items():
|
for name, details in gle_map.items():
|
||||||
@ -38,6 +39,11 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map):
|
|||||||
posting_date = entry.posting_date
|
posting_date = entry.posting_date
|
||||||
voucher_type = entry.voucher_type
|
voucher_type = entry.voucher_type
|
||||||
|
|
||||||
|
if voucher_type == "Journal Entry":
|
||||||
|
suppliers = journal_entry_party_map.get(name)
|
||||||
|
if suppliers:
|
||||||
|
supplier = suppliers[0]
|
||||||
|
|
||||||
if not tax_withholding_category:
|
if not tax_withholding_category:
|
||||||
tax_withholding_category = supplier_map.get(supplier, {}).get("tax_withholding_category")
|
tax_withholding_category = supplier_map.get(supplier, {}).get("tax_withholding_category")
|
||||||
rate = tax_rate_map.get(tax_withholding_category)
|
rate = tax_rate_map.get(tax_withholding_category)
|
||||||
@ -176,6 +182,7 @@ def get_tds_docs(filters):
|
|||||||
journal_entries = []
|
journal_entries = []
|
||||||
tax_category_map = {}
|
tax_category_map = {}
|
||||||
or_filters = {}
|
or_filters = {}
|
||||||
|
journal_entry_party_map = {}
|
||||||
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
|
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
|
||||||
|
|
||||||
tds_accounts = frappe.get_all(
|
tds_accounts = frappe.get_all(
|
||||||
@ -218,9 +225,24 @@ def get_tds_docs(filters):
|
|||||||
get_tax_category_map(payment_entries, "Payment Entry", tax_category_map)
|
get_tax_category_map(payment_entries, "Payment Entry", tax_category_map)
|
||||||
|
|
||||||
if journal_entries:
|
if journal_entries:
|
||||||
|
journal_entry_party_map = get_journal_entry_party_map(journal_entries)
|
||||||
get_tax_category_map(journal_entries, "Journal Entry", tax_category_map)
|
get_tax_category_map(journal_entries, "Journal Entry", tax_category_map)
|
||||||
|
|
||||||
return tds_documents, tds_accounts, tax_category_map
|
return tds_documents, tds_accounts, tax_category_map, journal_entry_party_map
|
||||||
|
|
||||||
|
|
||||||
|
def get_journal_entry_party_map(journal_entries):
|
||||||
|
journal_entry_party_map = {}
|
||||||
|
for d in frappe.db.get_all(
|
||||||
|
"Journal Entry Account",
|
||||||
|
{"parent": ("in", journal_entries), "party_type": "Supplier", "party": ("is", "set")},
|
||||||
|
["parent", "party"],
|
||||||
|
):
|
||||||
|
if d.parent not in journal_entry_party_map:
|
||||||
|
journal_entry_party_map[d.parent] = []
|
||||||
|
journal_entry_party_map[d.parent].append(d.party)
|
||||||
|
|
||||||
|
return journal_entry_party_map
|
||||||
|
|
||||||
|
|
||||||
def get_tax_category_map(vouchers, doctype, tax_category_map):
|
def get_tax_category_map(vouchers, doctype, tax_category_map):
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.query_reports["Unpaid Expense Claim"] = {
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"fieldname": "employee",
|
|
||||||
"label": __("Employee"),
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Employee"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"add_total_row": 0,
|
|
||||||
"apply_user_permissions": 1,
|
|
||||||
"creation": "2017-01-04 16:26:18.309717",
|
|
||||||
"disabled": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Report",
|
|
||||||
"idx": 2,
|
|
||||||
"is_standard": "Yes",
|
|
||||||
"modified": "2017-02-24 19:59:29.747039",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Unpaid Expense Claim",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"ref_doctype": "Expense Claim",
|
|
||||||
"report_name": "Unpaid Expense Claim",
|
|
||||||
"report_type": "Script Report",
|
|
||||||
"roles": [
|
|
||||||
{
|
|
||||||
"role": "HR Manager"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "Expense Approver"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "HR User"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
|
||||||
columns, data = [], []
|
|
||||||
columns = get_columns()
|
|
||||||
data = get_unclaimed_expese_claims(filters)
|
|
||||||
return columns, data
|
|
||||||
|
|
||||||
|
|
||||||
def get_columns():
|
|
||||||
return [
|
|
||||||
_("Employee") + ":Link/Employee:120",
|
|
||||||
_("Employee Name") + "::120",
|
|
||||||
_("Expense Claim") + ":Link/Expense Claim:120",
|
|
||||||
_("Sanctioned Amount") + ":Currency:120",
|
|
||||||
_("Paid Amount") + ":Currency:120",
|
|
||||||
_("Outstanding Amount") + ":Currency:150",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_unclaimed_expese_claims(filters):
|
|
||||||
cond = "1=1"
|
|
||||||
if filters.get("employee"):
|
|
||||||
cond = "ec.employee = %(employee)s"
|
|
||||||
|
|
||||||
return frappe.db.sql(
|
|
||||||
"""
|
|
||||||
select
|
|
||||||
ec.employee, ec.employee_name, ec.name, ec.total_sanctioned_amount, ec.total_amount_reimbursed,
|
|
||||||
sum(gle.credit_in_account_currency - gle.debit_in_account_currency) as outstanding_amt
|
|
||||||
from
|
|
||||||
`tabExpense Claim` ec, `tabGL Entry` gle
|
|
||||||
where
|
|
||||||
gle.against_voucher_type = "Expense Claim" and gle.against_voucher = ec.name
|
|
||||||
and gle.party is not null and ec.docstatus = 1 and ec.is_paid = 0 and {cond} group by ec.name
|
|
||||||
having
|
|
||||||
outstanding_amt > 0
|
|
||||||
""".format(
|
|
||||||
cond=cond
|
|
||||||
),
|
|
||||||
filters,
|
|
||||||
as_list=1,
|
|
||||||
)
|
|
||||||
@ -420,7 +420,7 @@ def add_cc(args=None):
|
|||||||
return cc.name
|
return cc.name
|
||||||
|
|
||||||
|
|
||||||
def reconcile_against_document(args):
|
def reconcile_against_document(args): # nosemgrep
|
||||||
"""
|
"""
|
||||||
Cancel PE or JV, Update against document, split if required and resubmit
|
Cancel PE or JV, Update against document, split if required and resubmit
|
||||||
"""
|
"""
|
||||||
@ -461,7 +461,8 @@ def reconcile_against_document(args):
|
|||||||
frappe.flags.ignore_party_validation = False
|
frappe.flags.ignore_party_validation = False
|
||||||
|
|
||||||
if entry.voucher_type in ("Payment Entry", "Journal Entry"):
|
if entry.voucher_type in ("Payment Entry", "Journal Entry"):
|
||||||
doc.update_expense_claim()
|
if hasattr(doc, "update_expense_claim"):
|
||||||
|
doc.update_expense_claim()
|
||||||
|
|
||||||
|
|
||||||
def check_if_advance_entry_modified(args):
|
def check_if_advance_entry_modified(args):
|
||||||
|
|||||||
@ -40,7 +40,6 @@
|
|||||||
"purchase_date",
|
"purchase_date",
|
||||||
"section_break_23",
|
"section_break_23",
|
||||||
"calculate_depreciation",
|
"calculate_depreciation",
|
||||||
"allow_monthly_depreciation",
|
|
||||||
"column_break_33",
|
"column_break_33",
|
||||||
"opening_accumulated_depreciation",
|
"opening_accumulated_depreciation",
|
||||||
"number_of_depreciations_booked",
|
"number_of_depreciations_booked",
|
||||||
@ -456,13 +455,6 @@
|
|||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "calculate_depreciation",
|
|
||||||
"fieldname": "allow_monthly_depreciation",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Allow Monthly Depreciation"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"collapsible_depends_on": "is_existing_asset",
|
"collapsible_depends_on": "is_existing_asset",
|
||||||
@ -518,7 +510,7 @@
|
|||||||
"link_fieldname": "asset"
|
"link_fieldname": "asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-01-30 20:19:24.680027",
|
"modified": "2022-07-20 10:15:12.887372",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
|||||||
@ -343,51 +343,13 @@ class Asset(AccountsController):
|
|||||||
skip_row = True
|
skip_row = True
|
||||||
|
|
||||||
if depreciation_amount > 0:
|
if depreciation_amount > 0:
|
||||||
# With monthly depreciation, each depreciation is divided by months remaining until next date
|
self._add_depreciation_row(
|
||||||
if self.allow_monthly_depreciation:
|
schedule_date,
|
||||||
# month range is 1 to 12
|
depreciation_amount,
|
||||||
# In pro rata case, for first and last depreciation, month range would be different
|
finance_book.depreciation_method,
|
||||||
month_range = (
|
finance_book.finance_book,
|
||||||
months
|
finance_book.idx,
|
||||||
if (has_pro_rata and n == 0)
|
)
|
||||||
or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1)
|
|
||||||
else finance_book.frequency_of_depreciation
|
|
||||||
)
|
|
||||||
|
|
||||||
for r in range(month_range):
|
|
||||||
if has_pro_rata and n == 0:
|
|
||||||
# For first entry of monthly depr
|
|
||||||
if r == 0:
|
|
||||||
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
|
|
||||||
per_day_amt = depreciation_amount / days
|
|
||||||
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
|
|
||||||
depreciation_amount -= depreciation_amount_for_current_month
|
|
||||||
date = monthly_schedule_date
|
|
||||||
amount = depreciation_amount_for_current_month
|
|
||||||
else:
|
|
||||||
date = add_months(monthly_schedule_date, r)
|
|
||||||
amount = depreciation_amount / (month_range - 1)
|
|
||||||
elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(
|
|
||||||
month_range
|
|
||||||
) - 1:
|
|
||||||
# For last entry of monthly depr
|
|
||||||
date = last_schedule_date
|
|
||||||
amount = depreciation_amount / month_range
|
|
||||||
else:
|
|
||||||
date = add_months(monthly_schedule_date, r)
|
|
||||||
amount = depreciation_amount / month_range
|
|
||||||
|
|
||||||
self._add_depreciation_row(
|
|
||||||
date, amount, finance_book.depreciation_method, finance_book.finance_book, finance_book.idx
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._add_depreciation_row(
|
|
||||||
schedule_date,
|
|
||||||
depreciation_amount,
|
|
||||||
finance_book.depreciation_method,
|
|
||||||
finance_book.finance_book,
|
|
||||||
finance_book.idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _add_depreciation_row(
|
def _add_depreciation_row(
|
||||||
self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id
|
self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id
|
||||||
@ -854,10 +816,8 @@ class Asset(AccountsController):
|
|||||||
return args.get("rate_of_depreciation")
|
return args.get("rate_of_depreciation")
|
||||||
|
|
||||||
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
|
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
|
||||||
|
|
||||||
depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
|
depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
|
||||||
|
return flt((100 * (1 - depreciation_rate)), float_precision)
|
||||||
return 100 * (1 - flt(depreciation_rate, float_precision))
|
|
||||||
|
|
||||||
def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
|
def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
|
||||||
days = date_diff(to_date, from_date)
|
days = date_diff(to_date, from_date)
|
||||||
|
|||||||
@ -133,7 +133,7 @@ class TestAsset(AssetSetup):
|
|||||||
order by account""",
|
order by account""",
|
||||||
pi.name,
|
pi.name,
|
||||||
)
|
)
|
||||||
self.assertEqual(gle, expected_gle)
|
self.assertSequenceEqual(gle, expected_gle)
|
||||||
|
|
||||||
pi.cancel()
|
pi.cancel()
|
||||||
asset.cancel()
|
asset.cancel()
|
||||||
@ -208,7 +208,7 @@ class TestAsset(AssetSetup):
|
|||||||
order by account""",
|
order by account""",
|
||||||
asset.journal_entry_for_scrap,
|
asset.journal_entry_for_scrap,
|
||||||
)
|
)
|
||||||
self.assertEqual(gle, expected_gle)
|
self.assertSequenceEqual(gle, expected_gle)
|
||||||
|
|
||||||
restore_asset(asset.name)
|
restore_asset(asset.name)
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ class TestAsset(AssetSetup):
|
|||||||
si.name,
|
si.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(gle, expected_gle)
|
self.assertSequenceEqual(gle, expected_gle)
|
||||||
|
|
||||||
si.cancel()
|
si.cancel()
|
||||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
||||||
@ -361,7 +361,7 @@ class TestAsset(AssetSetup):
|
|||||||
pr.name,
|
pr.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(pr_gle, expected_gle)
|
self.assertSequenceEqual(pr_gle, expected_gle)
|
||||||
|
|
||||||
pi = make_invoice(pr.name)
|
pi = make_invoice(pr.name)
|
||||||
pi.submit()
|
pi.submit()
|
||||||
@ -381,7 +381,7 @@ class TestAsset(AssetSetup):
|
|||||||
pi.name,
|
pi.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(pi_gle, expected_gle)
|
self.assertSequenceEqual(pi_gle, expected_gle)
|
||||||
|
|
||||||
asset = frappe.db.get_value("Asset", {"purchase_receipt": pr.name, "docstatus": 0}, "name")
|
asset = frappe.db.get_value("Asset", {"purchase_receipt": pr.name, "docstatus": 0}, "name")
|
||||||
|
|
||||||
@ -414,7 +414,7 @@ class TestAsset(AssetSetup):
|
|||||||
asset_doc.name,
|
asset_doc.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(gle, expected_gle)
|
self.assertSequenceEqual(gle, expected_gle)
|
||||||
|
|
||||||
def test_asset_cwip_toggling_cases(self):
|
def test_asset_cwip_toggling_cases(self):
|
||||||
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
|
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
|
||||||
@ -721,12 +721,12 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
)
|
)
|
||||||
|
|
||||||
expected_schedules = [
|
expected_schedules = [
|
||||||
["2022-02-28", 645.0, 645.0],
|
["2022-02-28", 647.25, 647.25],
|
||||||
["2022-03-31", 1206.8, 1851.8],
|
["2022-03-31", 1210.71, 1857.96],
|
||||||
["2022-04-30", 1051.12, 2902.92],
|
["2022-04-30", 1053.99, 2911.95],
|
||||||
["2022-05-31", 915.52, 3818.44],
|
["2022-05-31", 917.55, 3829.5],
|
||||||
["2022-06-30", 797.42, 4615.86],
|
["2022-06-30", 798.77, 4628.27],
|
||||||
["2022-07-15", 384.14, 5000.0],
|
["2022-07-15", 371.73, 5000.0],
|
||||||
]
|
]
|
||||||
|
|
||||||
schedules = [
|
schedules = [
|
||||||
@ -737,7 +737,6 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
]
|
]
|
||||||
for d in asset.get("schedules")
|
for d in asset.get("schedules")
|
||||||
]
|
]
|
||||||
|
|
||||||
self.assertEqual(schedules, expected_schedules)
|
self.assertEqual(schedules, expected_schedules)
|
||||||
|
|
||||||
|
|
||||||
@ -1288,7 +1287,7 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
asset.name,
|
asset.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(gle, expected_gle)
|
self.assertSequenceEqual(gle, expected_gle)
|
||||||
self.assertEqual(asset.get("value_after_depreciation"), 0)
|
self.assertEqual(asset.get("value_after_depreciation"), 0)
|
||||||
|
|
||||||
def test_expected_value_change(self):
|
def test_expected_value_change(self):
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import frappe
|
|||||||
from frappe.utils import now
|
from frappe.utils import now
|
||||||
|
|
||||||
from erpnext.assets.doctype.asset.test_asset import create_asset_data
|
from erpnext.assets.doctype.asset.test_asset import create_asset_data
|
||||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
from erpnext.setup.doctype.employee.test_employee import make_employee
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -91,7 +91,7 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
|||||||
adj_doc.journal_entry,
|
adj_doc.journal_entry,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(gle, expected_gle)
|
self.assertSequenceEqual(gle, expected_gle)
|
||||||
|
|
||||||
|
|
||||||
def make_asset_value_adjustment(**args):
|
def make_asset_value_adjustment(**args):
|
||||||
|
|||||||
@ -68,9 +68,6 @@ frappe.query_reports["Purchase Analytics"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
after_datatable_render: function(datatable_obj) {
|
|
||||||
$(datatable_obj.wrapper).find(".dt-row-0").find('input[type=checkbox]').click();
|
|
||||||
},
|
|
||||||
get_datatable_options(options) {
|
get_datatable_options(options) {
|
||||||
return Object.assign(options, {
|
return Object.assign(options, {
|
||||||
checkboxColumn: true,
|
checkboxColumn: true,
|
||||||
@ -130,11 +127,8 @@ frappe.query_reports["Purchase Analytics"] = {
|
|||||||
labels: raw_data.labels,
|
labels: raw_data.labels,
|
||||||
datasets: new_datasets,
|
datasets: new_datasets,
|
||||||
};
|
};
|
||||||
chart_options = {
|
const new_options = Object.assign({}, frappe.query_report.chart_options, {data: new_data});
|
||||||
data: new_data,
|
frappe.query_report.render_chart(new_options);
|
||||||
type: "line",
|
|
||||||
};
|
|
||||||
frappe.query_report.render_chart(chart_options);
|
|
||||||
|
|
||||||
frappe.query_report.raw_chart_data = new_data;
|
frappe.query_report.raw_chart_data = new_data;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1472,8 +1472,15 @@ class AccountsController(TransactionBase):
|
|||||||
self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to")
|
self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to")
|
||||||
)
|
)
|
||||||
party_account_currency = get_account_currency(party_account)
|
party_account_currency = get_account_currency(party_account)
|
||||||
|
allow_multi_currency_invoices_against_single_party_account = frappe.db.get_singles_value(
|
||||||
|
"Accounts Settings", "allow_multi_currency_invoices_against_single_party_account"
|
||||||
|
)
|
||||||
|
|
||||||
if not party_gle_currency and (party_account_currency != self.currency):
|
if (
|
||||||
|
not party_gle_currency
|
||||||
|
and (party_account_currency != self.currency)
|
||||||
|
and not allow_multi_currency_invoices_against_single_party_account
|
||||||
|
):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Party Account {0} currency ({1}) and document currency ({2}) should be same").format(
|
_("Party Account {0} currency ({1}) and document currency ({2}) should be same").format(
|
||||||
frappe.bold(party_account), party_account_currency, self.currency
|
frappe.bold(party_account), party_account_currency, self.currency
|
||||||
|
|||||||
@ -7,8 +7,8 @@ from frappe.desk.form import assign_to
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import add_days, flt, unique
|
from frappe.utils import add_days, flt, unique
|
||||||
|
|
||||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
from erpnext.setup.doctype.employee.employee import get_holiday_list_for_employee
|
||||||
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
|
from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday
|
||||||
|
|
||||||
|
|
||||||
class EmployeeBoardingController(Document):
|
class EmployeeBoardingController(Document):
|
||||||
|
|||||||
@ -615,13 +615,13 @@ class SellingController(StockController):
|
|||||||
stock_items = [d.item_code, d.description, d.warehouse, ""]
|
stock_items = [d.item_code, d.description, d.warehouse, ""]
|
||||||
non_stock_items = [d.item_code, d.description]
|
non_stock_items = [d.item_code, d.description]
|
||||||
|
|
||||||
|
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
|
||||||
|
duplicate_items_msg += "<br><br>"
|
||||||
|
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
|
||||||
|
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
|
||||||
|
get_link_to_form("Selling Settings", "Selling Settings"),
|
||||||
|
)
|
||||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
||||||
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
|
|
||||||
duplicate_items_msg += "<br><br>"
|
|
||||||
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
|
|
||||||
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
|
|
||||||
get_link_to_form("Selling Settings", "Selling Settings"),
|
|
||||||
)
|
|
||||||
if stock_items in check_list:
|
if stock_items in check_list:
|
||||||
frappe.throw(duplicate_items_msg)
|
frappe.throw(duplicate_items_msg)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -18,6 +18,9 @@ from erpnext.accounts.general_ledger import (
|
|||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
from erpnext.stock import get_warehouse_account_map
|
from erpnext.stock import get_warehouse_account_map
|
||||||
|
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
||||||
|
get_evaluated_inventory_dimension,
|
||||||
|
)
|
||||||
from erpnext.stock.stock_ledger import get_items_to_be_repost
|
from erpnext.stock.stock_ledger import get_items_to_be_repost
|
||||||
|
|
||||||
|
|
||||||
@ -364,8 +367,16 @@ class StockController(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
sl_dict.update(args)
|
sl_dict.update(args)
|
||||||
|
self.update_inventory_dimensions(d, sl_dict)
|
||||||
|
|
||||||
return sl_dict
|
return sl_dict
|
||||||
|
|
||||||
|
def update_inventory_dimensions(self, row, sl_dict) -> None:
|
||||||
|
dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self)
|
||||||
|
for dimension in dimensions:
|
||||||
|
if dimension and row.get(dimension.source_fieldname):
|
||||||
|
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||||
|
|
||||||
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
|
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||||
from erpnext.stock.stock_ledger import make_sl_entries
|
from erpnext.stock.stock_ledger import make_sl_entries
|
||||||
|
|
||||||
|
|||||||
@ -80,13 +80,14 @@ calendars = [
|
|||||||
"Holiday List",
|
"Holiday List",
|
||||||
]
|
]
|
||||||
|
|
||||||
website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner", "Job Opening"]
|
website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner"]
|
||||||
|
|
||||||
website_context = {
|
website_context = {
|
||||||
"favicon": "/assets/erpnext/images/erpnext-favicon.svg",
|
"favicon": "/assets/erpnext/images/erpnext-favicon.svg",
|
||||||
"splash_image": "/assets/erpnext/images/erpnext-logo.svg",
|
"splash_image": "/assets/erpnext/images/erpnext-logo.svg",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# nosemgrep
|
||||||
website_route_rules = [
|
website_route_rules = [
|
||||||
{"from_route": "/orders", "to_route": "Sales Order"},
|
{"from_route": "/orders", "to_route": "Sales Order"},
|
||||||
{
|
{
|
||||||
@ -163,7 +164,6 @@ website_route_rules = [
|
|||||||
"to_route": "addresses",
|
"to_route": "addresses",
|
||||||
"defaults": {"doctype": "Address", "parents": [{"label": _("Addresses"), "route": "addresses"}]},
|
"defaults": {"doctype": "Address", "parents": [{"label": _("Addresses"), "route": "addresses"}]},
|
||||||
},
|
},
|
||||||
{"from_route": "/jobs", "to_route": "Job Opening"},
|
|
||||||
{"from_route": "/boms", "to_route": "BOM"},
|
{"from_route": "/boms", "to_route": "BOM"},
|
||||||
{"from_route": "/timesheets", "to_route": "Timesheet"},
|
{"from_route": "/timesheets", "to_route": "Timesheet"},
|
||||||
{"from_route": "/material-requests", "to_route": "Material Request"},
|
{"from_route": "/material-requests", "to_route": "Material Request"},
|
||||||
@ -256,7 +256,9 @@ sounds = [
|
|||||||
{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2},
|
{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2},
|
||||||
]
|
]
|
||||||
|
|
||||||
has_upload_permission = {"Employee": "erpnext.hr.doctype.employee.employee.has_upload_permission"}
|
has_upload_permission = {
|
||||||
|
"Employee": "erpnext.setup.doctype.employee.employee.has_upload_permission"
|
||||||
|
}
|
||||||
|
|
||||||
has_website_permission = {
|
has_website_permission = {
|
||||||
"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
||||||
@ -289,9 +291,9 @@ doc_events = {
|
|||||||
},
|
},
|
||||||
"User": {
|
"User": {
|
||||||
"after_insert": "frappe.contacts.doctype.contact.contact.update_contact",
|
"after_insert": "frappe.contacts.doctype.contact.contact.update_contact",
|
||||||
"validate": "erpnext.hr.doctype.employee.employee.validate_employee_role",
|
"validate": "erpnext.setup.doctype.employee.employee.validate_employee_role",
|
||||||
"on_update": [
|
"on_update": [
|
||||||
"erpnext.hr.doctype.employee.employee.update_user_permissions",
|
"erpnext.setup.doctype.employee.employee.update_user_permissions",
|
||||||
"erpnext.portal.utils.set_default_role",
|
"erpnext.portal.utils.set_default_role",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -366,8 +368,6 @@ auto_cancel_exempted_doctypes = [
|
|||||||
"Payment Entry",
|
"Payment Entry",
|
||||||
]
|
]
|
||||||
|
|
||||||
after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
|
|
||||||
|
|
||||||
scheduler_events = {
|
scheduler_events = {
|
||||||
"cron": {
|
"cron": {
|
||||||
"0/5 * * * *": [
|
"0/5 * * * *": [
|
||||||
@ -387,16 +387,13 @@ scheduler_events = {
|
|||||||
},
|
},
|
||||||
"all": [
|
"all": [
|
||||||
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
||||||
"erpnext.hr.doctype.interview.interview.send_interview_reminder",
|
|
||||||
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts",
|
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts",
|
||||||
],
|
],
|
||||||
"hourly": [
|
"hourly": [
|
||||||
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails",
|
|
||||||
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
||||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
||||||
"erpnext.projects.doctype.project.project.hourly_reminder",
|
"erpnext.projects.doctype.project.project.hourly_reminder",
|
||||||
"erpnext.projects.doctype.project.project.collect_project_status",
|
"erpnext.projects.doctype.project.project.collect_project_status",
|
||||||
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
|
|
||||||
],
|
],
|
||||||
"hourly_long": [
|
"hourly_long": [
|
||||||
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
||||||
@ -407,11 +404,8 @@ scheduler_events = {
|
|||||||
"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
|
"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
|
||||||
"erpnext.controllers.accounts_controller.update_invoice_status",
|
"erpnext.controllers.accounts_controller.update_invoice_status",
|
||||||
"erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year",
|
"erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year",
|
||||||
"erpnext.hr.doctype.employee.employee_reminders.send_work_anniversary_reminders",
|
|
||||||
"erpnext.hr.doctype.employee.employee_reminders.send_birthday_reminders",
|
|
||||||
"erpnext.projects.doctype.task.task.set_tasks_as_overdue",
|
"erpnext.projects.doctype.task.task.set_tasks_as_overdue",
|
||||||
"erpnext.assets.doctype.asset.depreciation.post_depreciation_entries",
|
"erpnext.assets.doctype.asset.depreciation.post_depreciation_entries",
|
||||||
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.send_summary",
|
|
||||||
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status",
|
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status",
|
||||||
"erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards",
|
"erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards",
|
||||||
"erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history",
|
"erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history",
|
||||||
@ -427,20 +421,14 @@ scheduler_events = {
|
|||||||
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
|
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
|
||||||
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
|
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
|
||||||
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
|
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
|
||||||
"erpnext.hr.doctype.interview.interview.send_daily_feedback_reminder",
|
|
||||||
],
|
],
|
||||||
"daily_long": [
|
"daily_long": [
|
||||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||||
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.auto_update_latest_price_in_all_boms",
|
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.auto_update_latest_price_in_all_boms",
|
||||||
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
|
|
||||||
"erpnext.hr.utils.generate_leave_encashment",
|
|
||||||
"erpnext.hr.utils.allocate_earned_leaves",
|
|
||||||
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
||||||
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
||||||
"erpnext.crm.utils.open_leads_opportunities_based_on_todays_event",
|
"erpnext.crm.utils.open_leads_opportunities_based_on_todays_event",
|
||||||
],
|
],
|
||||||
"weekly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"],
|
|
||||||
"monthly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"],
|
|
||||||
"monthly_long": [
|
"monthly_long": [
|
||||||
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
|
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
|
||||||
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_demand_loans",
|
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_demand_loans",
|
||||||
@ -472,6 +460,28 @@ payment_gateway_enabled = "erpnext.accounts.utils.create_payment_gateway_account
|
|||||||
|
|
||||||
communication_doctypes = ["Customer", "Supplier"]
|
communication_doctypes = ["Customer", "Supplier"]
|
||||||
|
|
||||||
|
advance_payment_doctypes = ["Sales Order", "Purchase Order"]
|
||||||
|
|
||||||
|
invoice_doctypes = ["Sales Invoice", "Purchase Invoice"]
|
||||||
|
|
||||||
|
period_closing_doctypes = [
|
||||||
|
"Sales Invoice",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Journal Entry",
|
||||||
|
"Bank Clearance",
|
||||||
|
"Asset",
|
||||||
|
"Stock Entry",
|
||||||
|
]
|
||||||
|
|
||||||
|
bank_reconciliation_doctypes = [
|
||||||
|
"Payment Entry",
|
||||||
|
"Journal Entry",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Loan Repayment",
|
||||||
|
"Loan Disbursement",
|
||||||
|
]
|
||||||
|
|
||||||
accounting_dimension_doctypes = [
|
accounting_dimension_doctypes = [
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
"Payment Ledger Entry",
|
"Payment Ledger Entry",
|
||||||
@ -479,12 +489,8 @@ accounting_dimension_doctypes = [
|
|||||||
"Purchase Invoice",
|
"Purchase Invoice",
|
||||||
"Payment Entry",
|
"Payment Entry",
|
||||||
"Asset",
|
"Asset",
|
||||||
"Expense Claim",
|
|
||||||
"Expense Claim Detail",
|
|
||||||
"Expense Taxes and Charges",
|
|
||||||
"Stock Entry",
|
"Stock Entry",
|
||||||
"Budget",
|
"Budget",
|
||||||
"Payroll Entry",
|
|
||||||
"Delivery Note",
|
"Delivery Note",
|
||||||
"Sales Invoice Item",
|
"Sales Invoice Item",
|
||||||
"Purchase Invoice Item",
|
"Purchase Invoice Item",
|
||||||
@ -502,7 +508,6 @@ accounting_dimension_doctypes = [
|
|||||||
"Asset Value Adjustment",
|
"Asset Value Adjustment",
|
||||||
"Loyalty Program",
|
"Loyalty Program",
|
||||||
"Stock Reconciliation",
|
"Stock Reconciliation",
|
||||||
"Travel Request",
|
|
||||||
"POS Profile",
|
"POS Profile",
|
||||||
"Opening Invoice Creation Tool",
|
"Opening Invoice Creation Tool",
|
||||||
"Opening Invoice Creation Tool Item",
|
"Opening Invoice Creation Tool Item",
|
||||||
@ -515,14 +520,15 @@ accounting_dimension_doctypes = [
|
|||||||
"Sales Order",
|
"Sales Order",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# get matching queries for Bank Reconciliation
|
||||||
|
get_matching_queries = (
|
||||||
|
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_matching_queries"
|
||||||
|
)
|
||||||
|
|
||||||
regional_overrides = {
|
regional_overrides = {
|
||||||
"France": {
|
"France": {
|
||||||
"erpnext.tests.test_regional.test_method": "erpnext.regional.france.utils.test_method"
|
"erpnext.tests.test_regional.test_method": "erpnext.regional.france.utils.test_method"
|
||||||
},
|
},
|
||||||
"India": {
|
|
||||||
"erpnext.hr.utils.calculate_annual_eligible_hra_exemption": "erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption",
|
|
||||||
"erpnext.hr.utils.calculate_hra_exemption_for_period": "erpnext.regional.india.utils.calculate_hra_exemption_for_period",
|
|
||||||
},
|
|
||||||
"United Arab Emirates": {
|
"United Arab Emirates": {
|
||||||
"erpnext.controllers.taxes_and_totals.update_itemised_tax_data": "erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data",
|
"erpnext.controllers.taxes_and_totals.update_itemised_tax_data": "erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data",
|
||||||
"erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries": "erpnext.regional.united_arab_emirates.utils.make_regional_gl_entries",
|
"erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries": "erpnext.regional.united_arab_emirates.utils.make_regional_gl_entries",
|
||||||
@ -570,9 +576,6 @@ global_search_doctypes = {
|
|||||||
{"doctype": "Material Request", "index": 16},
|
{"doctype": "Material Request", "index": 16},
|
||||||
{"doctype": "Delivery Trip", "index": 17},
|
{"doctype": "Delivery Trip", "index": 17},
|
||||||
{"doctype": "Pick List", "index": 18},
|
{"doctype": "Pick List", "index": 18},
|
||||||
{"doctype": "Salary Slip", "index": 19},
|
|
||||||
{"doctype": "Leave Application", "index": 20},
|
|
||||||
{"doctype": "Expense Claim", "index": 21},
|
|
||||||
{"doctype": "Payment Entry", "index": 22},
|
{"doctype": "Payment Entry", "index": 22},
|
||||||
{"doctype": "Lead", "index": 23},
|
{"doctype": "Lead", "index": 23},
|
||||||
{"doctype": "Opportunity", "index": 24},
|
{"doctype": "Opportunity", "index": 24},
|
||||||
@ -588,13 +591,7 @@ global_search_doctypes = {
|
|||||||
{"doctype": "Batch", "index": 34},
|
{"doctype": "Batch", "index": 34},
|
||||||
{"doctype": "Branch", "index": 35},
|
{"doctype": "Branch", "index": 35},
|
||||||
{"doctype": "Department", "index": 36},
|
{"doctype": "Department", "index": 36},
|
||||||
{"doctype": "Employee Grade", "index": 37},
|
|
||||||
{"doctype": "Designation", "index": 38},
|
{"doctype": "Designation", "index": 38},
|
||||||
{"doctype": "Job Opening", "index": 39},
|
|
||||||
{"doctype": "Job Applicant", "index": 40},
|
|
||||||
{"doctype": "Job Offer", "index": 41},
|
|
||||||
{"doctype": "Salary Structure Assignment", "index": 42},
|
|
||||||
{"doctype": "Appraisal", "index": 43},
|
|
||||||
{"doctype": "Loan", "index": 44},
|
{"doctype": "Loan", "index": 44},
|
||||||
{"doctype": "Maintenance Schedule", "index": 45},
|
{"doctype": "Maintenance Schedule", "index": 45},
|
||||||
{"doctype": "Maintenance Visit", "index": 46},
|
{"doctype": "Maintenance Visit", "index": 46},
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
Key features:
|
|
||||||
|
|
||||||
- Leave and Attendance
|
|
||||||
- Payroll
|
|
||||||
- Appraisal
|
|
||||||
- Expense Claim
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"chart_name": "Attendance Count",
|
|
||||||
"chart_type": "Report",
|
|
||||||
"creation": "2020-07-22 11:56:32.730068",
|
|
||||||
"custom_options": "{\n\t\t\"type\": \"line\",\n\t\t\"axisOptions\": {\n\t\t\t\"shortenYAxisNumbers\": 1\n\t\t},\n\t\t\"tooltipOptions\": {}\n\t}",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Dashboard Chart",
|
|
||||||
"dynamic_filters_json": "{\"month\":\"frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1\",\"year\":\"frappe.datetime.str_to_obj(frappe.datetime.get_today()).getFullYear();\",\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}",
|
|
||||||
"filters_json": "{}",
|
|
||||||
"group_by_type": "Count",
|
|
||||||
"idx": 0,
|
|
||||||
"is_public": 1,
|
|
||||||
"is_standard": 1,
|
|
||||||
"modified": "2020-07-22 14:32:40.334424",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Attendance Count",
|
|
||||||
"number_of_groups": 0,
|
|
||||||
"owner": "Administrator",
|
|
||||||
"report_name": "Monthly Attendance Sheet",
|
|
||||||
"time_interval": "Yearly",
|
|
||||||
"timeseries": 0,
|
|
||||||
"timespan": "Last Year",
|
|
||||||
"type": "Line",
|
|
||||||
"use_report_chart": 1,
|
|
||||||
"y_axis": []
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"chart_name": "Department Wise Employee Count",
|
|
||||||
"chart_type": "Group By",
|
|
||||||
"creation": "2020-07-22 11:56:32.760730",
|
|
||||||
"custom_options": "",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Dashboard Chart",
|
|
||||||
"document_type": "Employee",
|
|
||||||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
|
||||||
"filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]",
|
|
||||||
"group_by_based_on": "department",
|
|
||||||
"group_by_type": "Count",
|
|
||||||
"idx": 0,
|
|
||||||
"is_public": 1,
|
|
||||||
"is_standard": 1,
|
|
||||||
"last_synced_on": "2020-07-22 14:27:40.574194",
|
|
||||||
"modified": "2020-07-22 14:33:38.036794",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Department Wise Employee Count",
|
|
||||||
"number_of_groups": 0,
|
|
||||||
"owner": "Administrator",
|
|
||||||
"time_interval": "Yearly",
|
|
||||||
"timeseries": 0,
|
|
||||||
"timespan": "Last Year",
|
|
||||||
"type": "Donut",
|
|
||||||
"use_report_chart": 0,
|
|
||||||
"y_axis": []
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"aggregate_function_based_on": "planned_vacancies",
|
|
||||||
"chart_name": "Department Wise Openings",
|
|
||||||
"chart_type": "Group By",
|
|
||||||
"creation": "2020-07-22 11:56:32.849775",
|
|
||||||
"custom_options": "",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Dashboard Chart",
|
|
||||||
"document_type": "Job Opening",
|
|
||||||
"filters_json": "[]",
|
|
||||||
"group_by_based_on": "department",
|
|
||||||
"group_by_type": "Sum",
|
|
||||||
"idx": 0,
|
|
||||||
"is_public": 1,
|
|
||||||
"is_standard": 1,
|
|
||||||
"last_synced_on": "2020-07-22 14:33:44.834801",
|
|
||||||
"modified": "2020-07-22 14:34:45.273591",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Department Wise Openings",
|
|
||||||
"number_of_groups": 0,
|
|
||||||
"owner": "Administrator",
|
|
||||||
"time_interval": "Monthly",
|
|
||||||
"timeseries": 0,
|
|
||||||
"timespan": "Last Year",
|
|
||||||
"type": "Bar",
|
|
||||||
"use_report_chart": 0,
|
|
||||||
"y_axis": []
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"chart_name": "Designation Wise Employee Count",
|
|
||||||
"chart_type": "Group By",
|
|
||||||
"creation": "2020-07-22 11:56:32.790337",
|
|
||||||
"custom_options": "",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Dashboard Chart",
|
|
||||||
"document_type": "Employee",
|
|
||||||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
|
||||||
"filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]",
|
|
||||||
"group_by_based_on": "designation",
|
|
||||||
"group_by_type": "Count",
|
|
||||||
"idx": 0,
|
|
||||||
"is_public": 1,
|
|
||||||
"is_standard": 1,
|
|
||||||
"last_synced_on": "2020-07-22 14:27:40.602783",
|
|
||||||
"modified": "2020-07-22 14:31:49.665555",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Designation Wise Employee Count",
|
|
||||||
"number_of_groups": 0,
|
|
||||||
"owner": "Administrator",
|
|
||||||
"time_interval": "Yearly",
|
|
||||||
"timeseries": 0,
|
|
||||||
"timespan": "Last Year",
|
|
||||||
"type": "Donut",
|
|
||||||
"use_report_chart": 0,
|
|
||||||
"y_axis": []
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"aggregate_function_based_on": "planned_vacancies",
|
|
||||||
"chart_name": "Designation Wise Openings",
|
|
||||||
"chart_type": "Group By",
|
|
||||||
"creation": "2020-07-22 11:56:32.820217",
|
|
||||||
"custom_options": "",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Dashboard Chart",
|
|
||||||
"document_type": "Job Opening",
|
|
||||||
"dynamic_filters_json": "",
|
|
||||||
"filters_json": "[]",
|
|
||||||
"group_by_based_on": "designation",
|
|
||||||
"group_by_type": "Sum",
|
|
||||||
"idx": 0,
|
|
||||||
"is_public": 1,
|
|
||||||
"is_standard": 1,
|
|
||||||
"last_synced_on": "2020-07-22 14:33:44.806626",
|
|
||||||
"modified": "2020-07-22 14:34:32.711881",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Designation Wise Openings",
|
|
||||||
"number_of_groups": 0,
|
|
||||||
"owner": "Administrator",
|
|
||||||
"time_interval": "Monthly",
|
|
||||||
"timeseries": 0,
|
|
||||||
"timespan": "Last Year",
|
|
||||||
"type": "Bar",
|
|
||||||
"use_report_chart": 0,
|
|
||||||
"y_axis": []
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"chart_name": "Gender Diversity Ratio",
|
|
||||||
"chart_type": "Group By",
|
|
||||||
"creation": "2020-07-22 11:56:32.667291",
|
|
||||||
"custom_options": "",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Dashboard Chart",
|
|
||||||
"document_type": "Employee",
|
|
||||||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
|
|
||||||
"filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]",
|
|
||||||
"group_by_based_on": "gender",
|
|
||||||
"group_by_type": "Count",
|
|
||||||
"idx": 0,
|
|
||||||
"is_public": 1,
|
|
||||||
"is_standard": 1,
|
|
||||||
"last_synced_on": "2020-07-22 14:27:40.143783",
|
|
||||||
"modified": "2020-07-22 14:32:50.962459",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Gender Diversity Ratio",
|
|
||||||
"number_of_groups": 0,
|
|
||||||
"owner": "Administrator",
|
|
||||||
"time_interval": "Yearly",
|
|
||||||
"timeseries": 0,
|
|
||||||
"timespan": "Last Year",
|
|
||||||
"type": "Pie",
|
|
||||||
"use_report_chart": 0,
|
|
||||||
"y_axis": []
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"chart_name": "Job Application Status",
|
|
||||||
"chart_type": "Group By",
|
|
||||||
"creation": "2020-07-22 11:56:32.699696",
|
|
||||||
"custom_options": "",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Dashboard Chart",
|
|
||||||
"document_type": "Job Applicant",
|
|
||||||
"dynamic_filters_json": "",
|
|
||||||
"filters_json": "[[\"Job Applicant\",\"creation\",\"Timespan\",\"last month\",false]]",
|
|
||||||
"group_by_based_on": "status",
|
|
||||||
"group_by_type": "Count",
|
|
||||||
"idx": 0,
|
|
||||||
"is_public": 1,
|
|
||||||
"is_standard": 1,
|
|
||||||
"last_synced_on": "2020-07-28 16:19:12.109979",
|
|
||||||
"modified": "2020-07-28 16:19:45.279490",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Job Application Status",
|
|
||||||
"number_of_groups": 0,
|
|
||||||
"owner": "Administrator",
|
|
||||||
"time_interval": "Yearly",
|
|
||||||
"timeseries": 0,
|
|
||||||
"timespan": "Last Year",
|
|
||||||
"type": "Pie",
|
|
||||||
"use_report_chart": 0,
|
|
||||||
"y_axis": []
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on('Appointment Letter', {
|
|
||||||
appointment_letter_template: function(frm){
|
|
||||||
if (frm.doc.appointment_letter_template){
|
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.hr.doctype.appointment_letter.appointment_letter.get_appointment_letter_details',
|
|
||||||
args : {
|
|
||||||
template : frm.doc.appointment_letter_template
|
|
||||||
},
|
|
||||||
callback: function(r){
|
|
||||||
if(r.message){
|
|
||||||
let message_body = r.message;
|
|
||||||
frm.set_value("introduction", message_body[0].introduction);
|
|
||||||
frm.set_value("closing_notes", message_body[0].closing_notes);
|
|
||||||
frm.doc.terms = []
|
|
||||||
for (var i in message_body[1].description){
|
|
||||||
frm.add_child("terms");
|
|
||||||
frm.fields_dict.terms.get_value()[i].title = message_body[1].description[i].title;
|
|
||||||
frm.fields_dict.terms.get_value()[i].description = message_body[1].description[i].description;
|
|
||||||
}
|
|
||||||
frm.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"autoname": "HR-APP-LETTER-.#####",
|
|
||||||
"creation": "2019-12-26 12:35:49.574828",
|
|
||||||
"default_print_format": "Standard Appointment Letter",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"job_applicant",
|
|
||||||
"applicant_name",
|
|
||||||
"column_break_3",
|
|
||||||
"company",
|
|
||||||
"appointment_date",
|
|
||||||
"appointment_letter_template",
|
|
||||||
"body_section",
|
|
||||||
"introduction",
|
|
||||||
"terms",
|
|
||||||
"closing_notes"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fetch_from": "job_applicant.applicant_name",
|
|
||||||
"fieldname": "applicant_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"in_global_search": 1,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Applicant Name",
|
|
||||||
"read_only": 1,
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "appointment_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"label": "Appointment Date",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "appointment_letter_template",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Appointment Letter Template",
|
|
||||||
"options": "Appointment Letter Template",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "appointment_letter_template.introduction",
|
|
||||||
"fieldname": "introduction",
|
|
||||||
"fieldtype": "Long Text",
|
|
||||||
"label": "Introduction",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "body_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Body"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_3",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "job_applicant",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Job Applicant",
|
|
||||||
"options": "Job Applicant",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "company",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Company",
|
|
||||||
"options": "Company",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "closing_notes",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"label": "Closing Notes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "terms",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"label": "Terms",
|
|
||||||
"options": "Appointment Letter content",
|
|
||||||
"reqd": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [],
|
|
||||||
"modified": "2022-01-18 19:27:35.649424",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Appointment Letter",
|
|
||||||
"name_case": "Title Case",
|
|
||||||
"naming_rule": "Expression (old style)",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"search_fields": "applicant_name, company",
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"title_field": "applicant_name",
|
|
||||||
"track_changes": 1
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class AppointmentLetter(Document):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_appointment_letter_details(template):
|
|
||||||
body = []
|
|
||||||
intro = frappe.get_list(
|
|
||||||
"Appointment Letter Template",
|
|
||||||
fields=["introduction", "closing_notes"],
|
|
||||||
filters={"name": template},
|
|
||||||
)[0]
|
|
||||||
content = frappe.get_all(
|
|
||||||
"Appointment Letter content",
|
|
||||||
fields=["title", "description"],
|
|
||||||
filters={"parent": template},
|
|
||||||
order_by="idx",
|
|
||||||
)
|
|
||||||
body.append(intro)
|
|
||||||
body.append({"description": content})
|
|
||||||
return body
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# See license.txt
|
|
||||||
|
|
||||||
# import frappe
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
|
|
||||||
class TestAppointmentLetter(unittest.TestCase):
|
|
||||||
pass
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"creation": "2019-12-26 12:22:16.575767",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"title",
|
|
||||||
"description"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "title",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Title",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "description",
|
|
||||||
"fieldtype": "Long Text",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Description",
|
|
||||||
"reqd": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"istable": 1,
|
|
||||||
"links": [],
|
|
||||||
"modified": "2019-12-26 12:24:09.824084",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Appointment Letter content",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
|
|
||||||
# import frappe
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class AppointmentLettercontent(Document):
|
|
||||||
pass
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on('Appointment Letter Template', {
|
|
||||||
// refresh: function(frm) {
|
|
||||||
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"autoname": "field:template_name",
|
|
||||||
"creation": "2019-12-26 12:20:14.219578",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"template_name",
|
|
||||||
"introduction",
|
|
||||||
"terms",
|
|
||||||
"closing_notes"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "introduction",
|
|
||||||
"fieldtype": "Long Text",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Introduction",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "closing_notes",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"label": "Closing Notes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "terms",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"label": "Terms",
|
|
||||||
"options": "Appointment Letter content",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "template_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Template Name",
|
|
||||||
"reqd": 1,
|
|
||||||
"unique": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [],
|
|
||||||
"modified": "2022-01-18 19:25:14.614616",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Appointment Letter Template",
|
|
||||||
"naming_rule": "By fieldname",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"search_fields": "template_name",
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"title_field": "template_name",
|
|
||||||
"track_changes": 1
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
|
|
||||||
# import frappe
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class AppointmentLetterTemplate(Document):
|
|
||||||
pass
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# See license.txt
|
|
||||||
|
|
||||||
# import frappe
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
|
|
||||||
class TestAppointmentLetterTemplate(unittest.TestCase):
|
|
||||||
pass
|
|
||||||
@ -1 +0,0 @@
|
|||||||
Performance of an Employee in a Time Period against given goals.
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
// License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on('Appraisal', {
|
|
||||||
setup: function(frm) {
|
|
||||||
frm.add_fetch('employee', 'company', 'company');
|
|
||||||
frm.add_fetch('employee', 'employee_name', 'employee_name');
|
|
||||||
frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
|
|
||||||
return{ query: "erpnext.controllers.queries.employee_query" }
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onload: function(frm) {
|
|
||||||
if(!frm.doc.status) {
|
|
||||||
frm.set_value('status', 'Draft');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
kra_template: function(frm) {
|
|
||||||
frm.doc.goals = [];
|
|
||||||
erpnext.utils.map_current_doc({
|
|
||||||
method: "erpnext.hr.doctype.appraisal.appraisal.fetch_appraisal_template",
|
|
||||||
source_name: frm.doc.kra_template,
|
|
||||||
frm: frm
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
calculate_total: function(frm) {
|
|
||||||
let goals = frm.doc.goals || [];
|
|
||||||
let total = 0;
|
|
||||||
|
|
||||||
if (goals == []) {
|
|
||||||
frm.set_value('total_score', 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (let i = 0; i<goals.length; i++) {
|
|
||||||
total = flt(total)+flt(goals[i].score_earned)
|
|
||||||
}
|
|
||||||
if (!isNaN(total)) {
|
|
||||||
frm.set_value('total_score', total);
|
|
||||||
frm.refresh_field('calculate_total');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
set_score_earned: function(frm) {
|
|
||||||
let goals = frm.doc.goals || [];
|
|
||||||
for (let i = 0; i<goals.length; i++) {
|
|
||||||
var d = locals[goals[i].doctype][goals[i].name];
|
|
||||||
if (d.score && d.per_weightage) {
|
|
||||||
d.score_earned = flt(d.per_weightage*d.score, precision("score_earned", d))/100;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
d.score_earned = 0;
|
|
||||||
}
|
|
||||||
refresh_field('score_earned', d.name, 'goals');
|
|
||||||
}
|
|
||||||
frm.trigger('calculate_total');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
frappe.ui.form.on('Appraisal Goal', {
|
|
||||||
score: function(frm, cdt, cdn) {
|
|
||||||
var d = locals[cdt][cdn];
|
|
||||||
if (flt(d.score) > 5) {
|
|
||||||
frappe.msgprint(__("Score must be less than or equal to 5"));
|
|
||||||
d.score = 0;
|
|
||||||
refresh_field('score', d.name, 'goals');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
frm.trigger('set_score_earned');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
per_weightage: function(frm) {
|
|
||||||
frm.trigger('set_score_earned');
|
|
||||||
},
|
|
||||||
goals_remove: function(frm) {
|
|
||||||
frm.trigger('set_score_earned');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,254 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"autoname": "naming_series:",
|
|
||||||
"creation": "2013-01-10 16:34:12",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "Setup",
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"employee_details",
|
|
||||||
"naming_series",
|
|
||||||
"kra_template",
|
|
||||||
"employee",
|
|
||||||
"employee_name",
|
|
||||||
"column_break0",
|
|
||||||
"status",
|
|
||||||
"start_date",
|
|
||||||
"end_date",
|
|
||||||
"department",
|
|
||||||
"section_break0",
|
|
||||||
"goals",
|
|
||||||
"total_score",
|
|
||||||
"section_break1",
|
|
||||||
"remarks",
|
|
||||||
"other_details",
|
|
||||||
"company",
|
|
||||||
"column_break_17",
|
|
||||||
"amended_from"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "employee_details",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"oldfieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "naming_series",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"label": "Series",
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "HR-APR-.YY.-.MM.",
|
|
||||||
"print_hide": 1,
|
|
||||||
"reqd": 1,
|
|
||||||
"set_only_once": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "kra_template",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Appraisal Template",
|
|
||||||
"oldfieldname": "kra_template",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Appraisal Template",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "kra_template",
|
|
||||||
"fieldname": "employee",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_global_search": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "For Employee",
|
|
||||||
"oldfieldname": "employee",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Employee",
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "kra_template",
|
|
||||||
"fieldname": "employee_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"in_global_search": 1,
|
|
||||||
"label": "For Employee Name",
|
|
||||||
"oldfieldname": "employee_name",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "kra_template",
|
|
||||||
"fieldname": "column_break0",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"oldfieldtype": "Column Break",
|
|
||||||
"width": "50%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "Draft",
|
|
||||||
"depends_on": "kra_template",
|
|
||||||
"fieldname": "status",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Status",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "status",
|
|
||||||
"oldfieldtype": "Select",
|
|
||||||
"options": "\nDraft\nSubmitted\nCompleted\nCancelled",
|
|
||||||
"read_only": 1,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "kra_template",
|
|
||||||
"fieldname": "start_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Start Date",
|
|
||||||
"oldfieldname": "start_date",
|
|
||||||
"oldfieldtype": "Date",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "kra_template",
|
|
||||||
"fieldname": "end_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"label": "End Date",
|
|
||||||
"oldfieldname": "end_date",
|
|
||||||
"oldfieldtype": "Date",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "employee.department",
|
|
||||||
"fieldname": "department",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Department",
|
|
||||||
"options": "Department",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "kra_template",
|
|
||||||
"fieldname": "section_break0",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Goals",
|
|
||||||
"oldfieldtype": "Section Break",
|
|
||||||
"options": "Simple"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "goals",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"label": "Goals",
|
|
||||||
"oldfieldname": "appraisal_details",
|
|
||||||
"oldfieldtype": "Table",
|
|
||||||
"options": "Appraisal Goal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "total_score",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Total Score (Out of 5)",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "total_score",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "kra_template",
|
|
||||||
"fieldname": "section_break1",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Any other remarks, noteworthy effort that should go in the records.",
|
|
||||||
"fieldname": "remarks",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"label": "Remarks"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "kra_template",
|
|
||||||
"fieldname": "other_details",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "company",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Company",
|
|
||||||
"oldfieldname": "company",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Company",
|
|
||||||
"remember_last_selected_value": 1,
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_17",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "amended_from",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 1,
|
|
||||||
"ignore_user_permissions": 1,
|
|
||||||
"label": "Amended From",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "amended_from",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"options": "Appraisal",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 1,
|
|
||||||
"width": "150px"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"icon": "fa fa-thumbs-up",
|
|
||||||
"idx": 1,
|
|
||||||
"index_web_pages_for_search": 1,
|
|
||||||
"is_submittable": 1,
|
|
||||||
"links": [],
|
|
||||||
"modified": "2020-10-03 21:48:33.297065",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Appraisal",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Employee",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 1,
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 1,
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR User",
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"search_fields": "status, employee, employee_name",
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"timeline_field": "employee",
|
|
||||||
"title_field": "employee_name"
|
|
||||||
}
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe.model.mapper import get_mapped_doc
|
|
||||||
from frappe.utils import flt, getdate
|
|
||||||
|
|
||||||
from erpnext.hr.utils import set_employee_name, validate_active_employee
|
|
||||||
|
|
||||||
|
|
||||||
class Appraisal(Document):
|
|
||||||
def validate(self):
|
|
||||||
if not self.status:
|
|
||||||
self.status = "Draft"
|
|
||||||
|
|
||||||
if not self.goals:
|
|
||||||
frappe.throw(_("Goals cannot be empty"))
|
|
||||||
|
|
||||||
validate_active_employee(self.employee)
|
|
||||||
set_employee_name(self)
|
|
||||||
self.validate_dates()
|
|
||||||
self.validate_existing_appraisal()
|
|
||||||
self.calculate_total()
|
|
||||||
|
|
||||||
def get_employee_name(self):
|
|
||||||
self.employee_name = frappe.db.get_value("Employee", self.employee, "employee_name")
|
|
||||||
return self.employee_name
|
|
||||||
|
|
||||||
def validate_dates(self):
|
|
||||||
if getdate(self.start_date) > getdate(self.end_date):
|
|
||||||
frappe.throw(_("End Date can not be less than Start Date"))
|
|
||||||
|
|
||||||
def validate_existing_appraisal(self):
|
|
||||||
chk = frappe.db.sql(
|
|
||||||
"""select name from `tabAppraisal` where employee=%s
|
|
||||||
and (status='Submitted' or status='Completed')
|
|
||||||
and ((start_date>=%s and start_date<=%s)
|
|
||||||
or (end_date>=%s and end_date<=%s))""",
|
|
||||||
(self.employee, self.start_date, self.end_date, self.start_date, self.end_date),
|
|
||||||
)
|
|
||||||
if chk:
|
|
||||||
frappe.throw(
|
|
||||||
_("Appraisal {0} created for Employee {1} in the given date range").format(
|
|
||||||
chk[0][0], self.employee_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def calculate_total(self):
|
|
||||||
total, total_w = 0, 0
|
|
||||||
for d in self.get("goals"):
|
|
||||||
if d.score:
|
|
||||||
d.score_earned = flt(d.score) * flt(d.per_weightage) / 100
|
|
||||||
total = total + d.score_earned
|
|
||||||
total_w += flt(d.per_weightage)
|
|
||||||
|
|
||||||
if int(total_w) != 100:
|
|
||||||
frappe.throw(
|
|
||||||
_("Total weightage assigned should be 100%.<br>It is {0}").format(str(total_w) + "%")
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
frappe.db.get_value("Employee", self.employee, "user_id") != frappe.session.user and total == 0
|
|
||||||
):
|
|
||||||
frappe.throw(_("Total cannot be zero"))
|
|
||||||
|
|
||||||
self.total_score = total
|
|
||||||
|
|
||||||
def on_submit(self):
|
|
||||||
frappe.db.set(self, "status", "Submitted")
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
frappe.db.set(self, "status", "Cancelled")
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def fetch_appraisal_template(source_name, target_doc=None):
|
|
||||||
target_doc = get_mapped_doc(
|
|
||||||
"Appraisal Template",
|
|
||||||
source_name,
|
|
||||||
{
|
|
||||||
"Appraisal Template": {
|
|
||||||
"doctype": "Appraisal",
|
|
||||||
},
|
|
||||||
"Appraisal Template Goal": {
|
|
||||||
"doctype": "Appraisal Goal",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
target_doc,
|
|
||||||
)
|
|
||||||
|
|
||||||
return target_doc
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
|
||||||
# See license.txt
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
# test_records = frappe.get_test_records('Appraisal')
|
|
||||||
|
|
||||||
|
|
||||||
class TestAppraisal(unittest.TestCase):
|
|
||||||
pass
|
|
||||||
@ -1 +0,0 @@
|
|||||||
Goal for the parent Appraisal.
|
|
||||||
@ -1,220 +0,0 @@
|
|||||||
{
|
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "hash",
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-02-22 01:27:44",
|
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"description": "Key Responsibility Area",
|
|
||||||
"fieldname": "kra",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Goal",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "kra",
|
|
||||||
"oldfieldtype": "Small Text",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "240px",
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "240px"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"fieldname": "section_break_2",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"fieldname": "per_weightage",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Weightage (%)",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "per_weightage",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "70px",
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "70px"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"fieldname": "column_break_4",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"fieldname": "score",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Score (0-5)",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "score",
|
|
||||||
"oldfieldtype": "Select",
|
|
||||||
"options": "",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "70px",
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "70px"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"fieldname": "section_break_6",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"fieldname": "score_earned",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Score Earned",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "score_earned",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "70px",
|
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "70px"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Appraisal Goal",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class AppraisalGoal(Document):
|
|
||||||
pass
|
|
||||||
@ -1 +0,0 @@
|
|||||||
Standard set of goals for an Employee / Designation / Job Profile. New Appraisal transactions can be created from the Template.
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on('Appraisal Template', {
|
|
||||||
refresh: function(frm) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,170 +0,0 @@
|
|||||||
{
|
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_import": 1,
|
|
||||||
"allow_rename": 1,
|
|
||||||
"autoname": "field:kra_title",
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2012-07-03 13:30:39",
|
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "Setup",
|
|
||||||
"editable_grid": 0,
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "kra_title",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Appraisal Template Title",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "kra_title",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "description",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Description",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "description",
|
|
||||||
"oldfieldtype": "Small Text",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "300px",
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "300px"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "goals",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Goals",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "kra_sheet",
|
|
||||||
"oldfieldtype": "Table",
|
|
||||||
"options": "Appraisal Template Goal",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "icon-file-text",
|
|
||||||
"idx": 1,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Appraisal Template",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"is_custom": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"is_custom": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Employee",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe.utils import cint, flt
|
|
||||||
|
|
||||||
|
|
||||||
class AppraisalTemplate(Document):
|
|
||||||
def validate(self):
|
|
||||||
self.check_total_points()
|
|
||||||
|
|
||||||
def check_total_points(self):
|
|
||||||
total_points = 0
|
|
||||||
for d in self.get("goals"):
|
|
||||||
total_points += flt(d.per_weightage)
|
|
||||||
|
|
||||||
if cint(total_points) != 100:
|
|
||||||
frappe.throw(_("Sum of points for all goals should be 100. It is {0}").format(total_points))
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
def get_data():
|
|
||||||
return {
|
|
||||||
"fieldname": "kra_template",
|
|
||||||
"transactions": [
|
|
||||||
{"items": ["Appraisal"]},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
|
||||||
# See license.txt
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
# test_records = frappe.get_test_records('Appraisal Template')
|
|
||||||
|
|
||||||
|
|
||||||
class TestAppraisalTemplate(unittest.TestCase):
|
|
||||||
pass
|
|
||||||
@ -1 +0,0 @@
|
|||||||
Goal details for the parent Appraisal Template.
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
{
|
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "hash",
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-02-22 01:27:44",
|
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"description": "Key Performance Area",
|
|
||||||
"fieldname": "kra",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "KRA",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "kra",
|
|
||||||
"oldfieldtype": "Small Text",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "200px",
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "200px"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"fieldname": "per_weightage",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Weightage (%)",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "per_weightage",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Appraisal Template Goal",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class AppraisalTemplateGoal(Document):
|
|
||||||
pass
|
|
||||||
@ -1 +0,0 @@
|
|||||||
Attendance record of an Employee on a particular date.
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
// License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
cur_frm.add_fetch('employee', 'company', 'company');
|
|
||||||
cur_frm.add_fetch('employee', 'employee_name', 'employee_name');
|
|
||||||
|
|
||||||
cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
|
||||||
if(doc.__islocal) cur_frm.set_value("attendance_date", frappe.datetime.get_today());
|
|
||||||
}
|
|
||||||
|
|
||||||
cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
|
|
||||||
return{
|
|
||||||
query: "erpnext.controllers.queries.employee_query"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,260 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_import": 1,
|
|
||||||
"autoname": "naming_series:",
|
|
||||||
"creation": "2013-01-10 16:34:13",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "Setup",
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"attendance_details",
|
|
||||||
"naming_series",
|
|
||||||
"employee",
|
|
||||||
"employee_name",
|
|
||||||
"working_hours",
|
|
||||||
"status",
|
|
||||||
"leave_type",
|
|
||||||
"leave_application",
|
|
||||||
"column_break0",
|
|
||||||
"attendance_date",
|
|
||||||
"company",
|
|
||||||
"department",
|
|
||||||
"attendance_request",
|
|
||||||
"details_section",
|
|
||||||
"shift",
|
|
||||||
"in_time",
|
|
||||||
"out_time",
|
|
||||||
"column_break_18",
|
|
||||||
"late_entry",
|
|
||||||
"early_exit",
|
|
||||||
"amended_from"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "attendance_details",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"oldfieldtype": "Section Break",
|
|
||||||
"options": "Simple"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "naming_series",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"label": "Series",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "naming_series",
|
|
||||||
"oldfieldtype": "Select",
|
|
||||||
"options": "HR-ATT-.YYYY.-",
|
|
||||||
"reqd": 1,
|
|
||||||
"set_only_once": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "employee",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_global_search": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Employee",
|
|
||||||
"oldfieldname": "employee",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Employee",
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "employee.employee_name",
|
|
||||||
"fieldname": "employee_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"in_global_search": 1,
|
|
||||||
"label": "Employee Name",
|
|
||||||
"oldfieldname": "employee_name",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "working_hours",
|
|
||||||
"fieldname": "working_hours",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"label": "Working Hours",
|
|
||||||
"precision": "1",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "Present",
|
|
||||||
"fieldname": "status",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Status",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "status",
|
|
||||||
"oldfieldtype": "Select",
|
|
||||||
"options": "\nPresent\nAbsent\nOn Leave\nHalf Day\nWork From Home",
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
|
|
||||||
"fieldname": "leave_type",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Leave Type",
|
|
||||||
"mandatory_depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
|
|
||||||
"oldfieldname": "leave_type",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Leave Type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "leave_application",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Leave Application",
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Leave Application",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break0",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"oldfieldtype": "Column Break",
|
|
||||||
"width": "50%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "attendance_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Attendance Date",
|
|
||||||
"oldfieldname": "attendance_date",
|
|
||||||
"oldfieldtype": "Date",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "company",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Company",
|
|
||||||
"oldfieldname": "company",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Company",
|
|
||||||
"remember_last_selected_value": 1,
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "employee.department",
|
|
||||||
"fieldname": "department",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Department",
|
|
||||||
"options": "Department",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "shift",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Shift",
|
|
||||||
"options": "Shift Type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "attendance_request",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Attendance Request",
|
|
||||||
"options": "Attendance Request",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "amended_from",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"ignore_user_permissions": 1,
|
|
||||||
"label": "Amended From",
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Attendance",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "late_entry",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Late Entry"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "early_exit",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Early Exit"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "details_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Details"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "shift",
|
|
||||||
"fieldname": "in_time",
|
|
||||||
"fieldtype": "Datetime",
|
|
||||||
"label": "In Time",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "shift",
|
|
||||||
"fieldname": "out_time",
|
|
||||||
"fieldtype": "Datetime",
|
|
||||||
"label": "Out Time",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_18",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"icon": "fa fa-ok",
|
|
||||||
"idx": 1,
|
|
||||||
"is_submittable": 1,
|
|
||||||
"links": [],
|
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Attendance",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"import": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR User",
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR Manager",
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"search_fields": "employee,employee_name,attendance_date,status",
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"title_field": "employee_name"
|
|
||||||
}
|
|
||||||
@ -1,390 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe.query_builder import Criterion
|
|
||||||
from frappe.utils import cint, cstr, formatdate, get_datetime, get_link_to_form, getdate, nowdate
|
|
||||||
|
|
||||||
from erpnext.hr.doctype.shift_assignment.shift_assignment import has_overlapping_timings
|
|
||||||
from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee
|
|
||||||
|
|
||||||
|
|
||||||
class DuplicateAttendanceError(frappe.ValidationError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OverlappingShiftAttendanceError(frappe.ValidationError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Attendance(Document):
|
|
||||||
def validate(self):
|
|
||||||
from erpnext.controllers.status_updater import validate_status
|
|
||||||
|
|
||||||
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
|
|
||||||
validate_active_employee(self.employee)
|
|
||||||
self.validate_attendance_date()
|
|
||||||
self.validate_duplicate_record()
|
|
||||||
self.validate_overlapping_shift_attendance()
|
|
||||||
self.validate_employee_status()
|
|
||||||
self.check_leave_record()
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
self.unlink_attendance_from_checkins()
|
|
||||||
|
|
||||||
def validate_attendance_date(self):
|
|
||||||
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
|
|
||||||
|
|
||||||
# leaves can be marked for future dates
|
|
||||||
if (
|
|
||||||
self.status != "On Leave"
|
|
||||||
and not self.leave_application
|
|
||||||
and getdate(self.attendance_date) > getdate(nowdate())
|
|
||||||
):
|
|
||||||
frappe.throw(_("Attendance can not be marked for future dates"))
|
|
||||||
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
|
|
||||||
frappe.throw(_("Attendance date can not be less than employee's joining date"))
|
|
||||||
|
|
||||||
def validate_duplicate_record(self):
|
|
||||||
duplicate = get_duplicate_attendance_record(
|
|
||||||
self.employee, self.attendance_date, self.shift, self.name
|
|
||||||
)
|
|
||||||
|
|
||||||
if duplicate:
|
|
||||||
frappe.throw(
|
|
||||||
_("Attendance for employee {0} is already marked for the date {1}: {2}").format(
|
|
||||||
frappe.bold(self.employee),
|
|
||||||
frappe.bold(self.attendance_date),
|
|
||||||
get_link_to_form("Attendance", duplicate[0].name),
|
|
||||||
),
|
|
||||||
title=_("Duplicate Attendance"),
|
|
||||||
exc=DuplicateAttendanceError,
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_overlapping_shift_attendance(self):
|
|
||||||
attendance = get_overlapping_shift_attendance(
|
|
||||||
self.employee, self.attendance_date, self.shift, self.name
|
|
||||||
)
|
|
||||||
|
|
||||||
if attendance:
|
|
||||||
frappe.throw(
|
|
||||||
_("Attendance for employee {0} is already marked for an overlapping shift {1}: {2}").format(
|
|
||||||
frappe.bold(self.employee),
|
|
||||||
frappe.bold(attendance.shift),
|
|
||||||
get_link_to_form("Attendance", attendance.name),
|
|
||||||
),
|
|
||||||
title=_("Overlapping Shift Attendance"),
|
|
||||||
exc=OverlappingShiftAttendanceError,
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_employee_status(self):
|
|
||||||
if frappe.db.get_value("Employee", self.employee, "status") == "Inactive":
|
|
||||||
frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee))
|
|
||||||
|
|
||||||
def check_leave_record(self):
|
|
||||||
leave_record = frappe.db.sql(
|
|
||||||
"""
|
|
||||||
select leave_type, half_day, half_day_date
|
|
||||||
from `tabLeave Application`
|
|
||||||
where employee = %s
|
|
||||||
and %s between from_date and to_date
|
|
||||||
and status = 'Approved'
|
|
||||||
and docstatus = 1
|
|
||||||
""",
|
|
||||||
(self.employee, self.attendance_date),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
if leave_record:
|
|
||||||
for d in leave_record:
|
|
||||||
self.leave_type = d.leave_type
|
|
||||||
if d.half_day_date == getdate(self.attendance_date):
|
|
||||||
self.status = "Half Day"
|
|
||||||
frappe.msgprint(
|
|
||||||
_("Employee {0} on Half day on {1}").format(self.employee, formatdate(self.attendance_date))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.status = "On Leave"
|
|
||||||
frappe.msgprint(
|
|
||||||
_("Employee {0} is on Leave on {1}").format(self.employee, formatdate(self.attendance_date))
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.status in ("On Leave", "Half Day"):
|
|
||||||
if not leave_record:
|
|
||||||
frappe.msgprint(
|
|
||||||
_("No leave record found for employee {0} on {1}").format(
|
|
||||||
self.employee, formatdate(self.attendance_date)
|
|
||||||
),
|
|
||||||
alert=1,
|
|
||||||
)
|
|
||||||
elif self.leave_type:
|
|
||||||
self.leave_type = None
|
|
||||||
self.leave_application = None
|
|
||||||
|
|
||||||
def validate_employee(self):
|
|
||||||
emp = frappe.db.sql(
|
|
||||||
"select name from `tabEmployee` where name = %s and status = 'Active'", self.employee
|
|
||||||
)
|
|
||||||
if not emp:
|
|
||||||
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
|
|
||||||
|
|
||||||
def unlink_attendance_from_checkins(self):
|
|
||||||
EmployeeCheckin = frappe.qb.DocType("Employee Checkin")
|
|
||||||
linked_logs = (
|
|
||||||
frappe.qb.from_(EmployeeCheckin)
|
|
||||||
.select(EmployeeCheckin.name)
|
|
||||||
.where(EmployeeCheckin.attendance == self.name)
|
|
||||||
.for_update()
|
|
||||||
.run(as_dict=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
if linked_logs:
|
|
||||||
(
|
|
||||||
frappe.qb.update(EmployeeCheckin)
|
|
||||||
.set("attendance", "")
|
|
||||||
.where(EmployeeCheckin.attendance == self.name)
|
|
||||||
).run()
|
|
||||||
|
|
||||||
frappe.msgprint(
|
|
||||||
msg=_("Unlinked Attendance record from Employee Checkins: {}").format(
|
|
||||||
", ".join(get_link_to_form("Employee Checkin", log.name) for log in linked_logs)
|
|
||||||
),
|
|
||||||
title=_("Unlinked logs"),
|
|
||||||
indicator="blue",
|
|
||||||
is_minimizable=True,
|
|
||||||
wide=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_duplicate_attendance_record(employee, attendance_date, shift, name=None):
|
|
||||||
attendance = frappe.qb.DocType("Attendance")
|
|
||||||
query = (
|
|
||||||
frappe.qb.from_(attendance)
|
|
||||||
.select(attendance.name)
|
|
||||||
.where((attendance.employee == employee) & (attendance.docstatus < 2))
|
|
||||||
)
|
|
||||||
|
|
||||||
if shift:
|
|
||||||
query = query.where(
|
|
||||||
Criterion.any(
|
|
||||||
[
|
|
||||||
Criterion.all(
|
|
||||||
[
|
|
||||||
((attendance.shift.isnull()) | (attendance.shift == "")),
|
|
||||||
(attendance.attendance_date == attendance_date),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Criterion.all(
|
|
||||||
[
|
|
||||||
((attendance.shift.isnotnull()) | (attendance.shift != "")),
|
|
||||||
(attendance.attendance_date == attendance_date),
|
|
||||||
(attendance.shift == shift),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
query = query.where((attendance.attendance_date == attendance_date))
|
|
||||||
|
|
||||||
if name:
|
|
||||||
query = query.where(attendance.name != name)
|
|
||||||
|
|
||||||
return query.run(as_dict=True)
|
|
||||||
|
|
||||||
|
|
||||||
def get_overlapping_shift_attendance(employee, attendance_date, shift, name=None):
|
|
||||||
if not shift:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
attendance = frappe.qb.DocType("Attendance")
|
|
||||||
query = (
|
|
||||||
frappe.qb.from_(attendance)
|
|
||||||
.select(attendance.name, attendance.shift)
|
|
||||||
.where(
|
|
||||||
(attendance.employee == employee)
|
|
||||||
& (attendance.docstatus < 2)
|
|
||||||
& (attendance.attendance_date == attendance_date)
|
|
||||||
& (attendance.shift != shift)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if name:
|
|
||||||
query = query.where(attendance.name != name)
|
|
||||||
|
|
||||||
overlapping_attendance = query.run(as_dict=True)
|
|
||||||
|
|
||||||
if overlapping_attendance and has_overlapping_timings(shift, overlapping_attendance[0].shift):
|
|
||||||
return overlapping_attendance[0]
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_events(start, end, filters=None):
|
|
||||||
events = []
|
|
||||||
|
|
||||||
employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user})
|
|
||||||
|
|
||||||
if not employee:
|
|
||||||
return events
|
|
||||||
|
|
||||||
from frappe.desk.reportview import get_filters_cond
|
|
||||||
|
|
||||||
conditions = get_filters_cond("Attendance", filters, [])
|
|
||||||
add_attendance(events, start, end, conditions=conditions)
|
|
||||||
return events
|
|
||||||
|
|
||||||
|
|
||||||
def add_attendance(events, start, end, conditions=None):
|
|
||||||
query = """select name, attendance_date, status
|
|
||||||
from `tabAttendance` where
|
|
||||||
attendance_date between %(from_date)s and %(to_date)s
|
|
||||||
and docstatus < 2"""
|
|
||||||
if conditions:
|
|
||||||
query += conditions
|
|
||||||
|
|
||||||
for d in frappe.db.sql(query, {"from_date": start, "to_date": end}, as_dict=True):
|
|
||||||
e = {
|
|
||||||
"name": d.name,
|
|
||||||
"doctype": "Attendance",
|
|
||||||
"start": d.attendance_date,
|
|
||||||
"end": d.attendance_date,
|
|
||||||
"title": cstr(d.status),
|
|
||||||
"docstatus": d.docstatus,
|
|
||||||
}
|
|
||||||
if e not in events:
|
|
||||||
events.append(e)
|
|
||||||
|
|
||||||
|
|
||||||
def mark_attendance(
|
|
||||||
employee,
|
|
||||||
attendance_date,
|
|
||||||
status,
|
|
||||||
shift=None,
|
|
||||||
leave_type=None,
|
|
||||||
ignore_validate=False,
|
|
||||||
late_entry=False,
|
|
||||||
early_exit=False,
|
|
||||||
):
|
|
||||||
if get_duplicate_attendance_record(employee, attendance_date, shift):
|
|
||||||
return
|
|
||||||
|
|
||||||
if get_overlapping_shift_attendance(employee, attendance_date, shift):
|
|
||||||
return
|
|
||||||
|
|
||||||
company = frappe.db.get_value("Employee", employee, "company")
|
|
||||||
attendance = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Attendance",
|
|
||||||
"employee": employee,
|
|
||||||
"attendance_date": attendance_date,
|
|
||||||
"status": status,
|
|
||||||
"company": company,
|
|
||||||
"shift": shift,
|
|
||||||
"leave_type": leave_type,
|
|
||||||
"late_entry": late_entry,
|
|
||||||
"early_exit": early_exit,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
attendance.flags.ignore_validate = ignore_validate
|
|
||||||
attendance.insert()
|
|
||||||
attendance.submit()
|
|
||||||
return attendance.name
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def mark_bulk_attendance(data):
|
|
||||||
import json
|
|
||||||
|
|
||||||
if isinstance(data, str):
|
|
||||||
data = json.loads(data)
|
|
||||||
data = frappe._dict(data)
|
|
||||||
company = frappe.get_value("Employee", data.employee, "company")
|
|
||||||
if not data.unmarked_days:
|
|
||||||
frappe.throw(_("Please select a date."))
|
|
||||||
return
|
|
||||||
|
|
||||||
for date in data.unmarked_days:
|
|
||||||
doc_dict = {
|
|
||||||
"doctype": "Attendance",
|
|
||||||
"employee": data.employee,
|
|
||||||
"attendance_date": get_datetime(date),
|
|
||||||
"status": data.status,
|
|
||||||
"company": company,
|
|
||||||
}
|
|
||||||
attendance = frappe.get_doc(doc_dict).insert()
|
|
||||||
attendance.submit()
|
|
||||||
|
|
||||||
|
|
||||||
def get_month_map():
|
|
||||||
return frappe._dict(
|
|
||||||
{
|
|
||||||
"January": 1,
|
|
||||||
"February": 2,
|
|
||||||
"March": 3,
|
|
||||||
"April": 4,
|
|
||||||
"May": 5,
|
|
||||||
"June": 6,
|
|
||||||
"July": 7,
|
|
||||||
"August": 8,
|
|
||||||
"September": 9,
|
|
||||||
"October": 10,
|
|
||||||
"November": 11,
|
|
||||||
"December": 12,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_unmarked_days(employee, month, exclude_holidays=0):
|
|
||||||
import calendar
|
|
||||||
|
|
||||||
month_map = get_month_map()
|
|
||||||
today = get_datetime()
|
|
||||||
|
|
||||||
joining_date, relieving_date = frappe.get_cached_value(
|
|
||||||
"Employee", employee, ["date_of_joining", "relieving_date"]
|
|
||||||
)
|
|
||||||
start_day = 1
|
|
||||||
end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
|
|
||||||
|
|
||||||
if joining_date and joining_date.month == month_map[month]:
|
|
||||||
start_day = joining_date.day
|
|
||||||
|
|
||||||
if relieving_date and relieving_date.month == month_map[month]:
|
|
||||||
end_day = relieving_date.day + 1
|
|
||||||
|
|
||||||
dates_of_month = [
|
|
||||||
"{}-{}-{}".format(today.year, month_map[month], r) for r in range(start_day, end_day)
|
|
||||||
]
|
|
||||||
month_start, month_end = dates_of_month[0], dates_of_month[-1]
|
|
||||||
|
|
||||||
records = frappe.get_all(
|
|
||||||
"Attendance",
|
|
||||||
fields=["attendance_date", "employee"],
|
|
||||||
filters=[
|
|
||||||
["attendance_date", ">=", month_start],
|
|
||||||
["attendance_date", "<=", month_end],
|
|
||||||
["employee", "=", employee],
|
|
||||||
["docstatus", "!=", 2],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
marked_days = [get_datetime(record.attendance_date) for record in records]
|
|
||||||
if cint(exclude_holidays):
|
|
||||||
holiday_dates = get_holiday_dates_for_employee(employee, month_start, month_end)
|
|
||||||
holidays = [get_datetime(record) for record in holiday_dates]
|
|
||||||
marked_days.extend(holidays)
|
|
||||||
|
|
||||||
unmarked_days = []
|
|
||||||
|
|
||||||
for date in dates_of_month:
|
|
||||||
date_time = get_datetime(date)
|
|
||||||
if today.day <= date_time.day and today.month <= date_time.month:
|
|
||||||
break
|
|
||||||
if date_time not in marked_days:
|
|
||||||
unmarked_days.append(date)
|
|
||||||
|
|
||||||
return unmarked_days
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
frappe.views.calendar["Attendance"] = {
|
|
||||||
options: {
|
|
||||||
header: {
|
|
||||||
left: 'prev,next today',
|
|
||||||
center: 'title',
|
|
||||||
right: 'month'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get_events_method: "erpnext.hr.doctype.attendance.attendance.get_events"
|
|
||||||
};
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
def get_data():
|
|
||||||
return {"fieldname": "attendance", "transactions": [{"label": "", "items": ["Employee Checkin"]}]}
|
|
||||||
@ -1,164 +0,0 @@
|
|||||||
frappe.listview_settings['Attendance'] = {
|
|
||||||
add_fields: ["status", "attendance_date"],
|
|
||||||
get_indicator: function (doc) {
|
|
||||||
if (["Present", "Work From Home"].includes(doc.status)) {
|
|
||||||
return [__(doc.status), "green", "status,=," + doc.status];
|
|
||||||
} else if (["Absent", "On Leave"].includes(doc.status)) {
|
|
||||||
return [__(doc.status), "red", "status,=," + doc.status];
|
|
||||||
} else if (doc.status == "Half Day") {
|
|
||||||
return [__(doc.status), "orange", "status,=," + doc.status];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onload: function(list_view) {
|
|
||||||
let me = this;
|
|
||||||
const months = moment.months();
|
|
||||||
list_view.page.add_inner_button(__("Mark Attendance"), function() {
|
|
||||||
let dialog = new frappe.ui.Dialog({
|
|
||||||
title: __("Mark Attendance"),
|
|
||||||
fields: [{
|
|
||||||
fieldname: 'employee',
|
|
||||||
label: __('For Employee'),
|
|
||||||
fieldtype: 'Link',
|
|
||||||
options: 'Employee',
|
|
||||||
get_query: () => {
|
|
||||||
return {query: "erpnext.controllers.queries.employee_query"};
|
|
||||||
},
|
|
||||||
reqd: 1,
|
|
||||||
onchange: function() {
|
|
||||||
dialog.set_df_property("unmarked_days", "hidden", 1);
|
|
||||||
dialog.set_df_property("status", "hidden", 1);
|
|
||||||
dialog.set_df_property("exclude_holidays", "hidden", 1);
|
|
||||||
dialog.set_df_property("month", "value", '');
|
|
||||||
dialog.set_df_property("unmarked_days", "options", []);
|
|
||||||
dialog.no_unmarked_days_left = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("For Month"),
|
|
||||||
fieldtype: "Select",
|
|
||||||
fieldname: "month",
|
|
||||||
options: months,
|
|
||||||
reqd: 1,
|
|
||||||
onchange: function() {
|
|
||||||
if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) {
|
|
||||||
dialog.set_df_property("status", "hidden", 0);
|
|
||||||
dialog.set_df_property("exclude_holidays", "hidden", 0);
|
|
||||||
dialog.set_df_property("unmarked_days", "options", []);
|
|
||||||
dialog.no_unmarked_days_left = false;
|
|
||||||
me.get_multi_select_options(
|
|
||||||
dialog.fields_dict.employee.value,
|
|
||||||
dialog.fields_dict.month.value,
|
|
||||||
dialog.fields_dict.exclude_holidays.get_value()
|
|
||||||
).then(options => {
|
|
||||||
if (options.length > 0) {
|
|
||||||
dialog.set_df_property("unmarked_days", "hidden", 0);
|
|
||||||
dialog.set_df_property("unmarked_days", "options", options);
|
|
||||||
} else {
|
|
||||||
dialog.no_unmarked_days_left = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Status"),
|
|
||||||
fieldtype: "Select",
|
|
||||||
fieldname: "status",
|
|
||||||
options: ["Present", "Absent", "Half Day", "Work From Home"],
|
|
||||||
hidden: 1,
|
|
||||||
reqd: 1,
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Exclude Holidays"),
|
|
||||||
fieldtype: "Check",
|
|
||||||
fieldname: "exclude_holidays",
|
|
||||||
hidden: 1,
|
|
||||||
onchange: function() {
|
|
||||||
if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) {
|
|
||||||
dialog.set_df_property("status", "hidden", 0);
|
|
||||||
dialog.set_df_property("unmarked_days", "options", []);
|
|
||||||
dialog.no_unmarked_days_left = false;
|
|
||||||
me.get_multi_select_options(
|
|
||||||
dialog.fields_dict.employee.value,
|
|
||||||
dialog.fields_dict.month.value,
|
|
||||||
dialog.fields_dict.exclude_holidays.get_value()
|
|
||||||
).then(options => {
|
|
||||||
if (options.length > 0) {
|
|
||||||
dialog.set_df_property("unmarked_days", "hidden", 0);
|
|
||||||
dialog.set_df_property("unmarked_days", "options", options);
|
|
||||||
} else {
|
|
||||||
dialog.no_unmarked_days_left = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Unmarked Attendance for days"),
|
|
||||||
fieldname: "unmarked_days",
|
|
||||||
fieldtype: "MultiCheck",
|
|
||||||
options: [],
|
|
||||||
columns: 2,
|
|
||||||
hidden: 1
|
|
||||||
}],
|
|
||||||
primary_action(data) {
|
|
||||||
if (cur_dialog.no_unmarked_days_left) {
|
|
||||||
frappe.msgprint(__("Attendance for the month of {0} , has already been marked for the Employee {1}",
|
|
||||||
[dialog.fields_dict.month.value, dialog.fields_dict.employee.value]));
|
|
||||||
} else {
|
|
||||||
frappe.confirm(__('Mark attendance as {0} for {1} on selected dates?', [data.status, data.month]), () => {
|
|
||||||
frappe.call({
|
|
||||||
method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance",
|
|
||||||
args: {
|
|
||||||
data: data
|
|
||||||
},
|
|
||||||
callback: function (r) {
|
|
||||||
if (r.message === 1) {
|
|
||||||
frappe.show_alert({
|
|
||||||
message: __("Attendance Marked"),
|
|
||||||
indicator: 'blue'
|
|
||||||
});
|
|
||||||
cur_dialog.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dialog.hide();
|
|
||||||
list_view.refresh();
|
|
||||||
},
|
|
||||||
primary_action_label: __('Mark Attendance')
|
|
||||||
|
|
||||||
});
|
|
||||||
dialog.show();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
get_multi_select_options: function(employee, month, exclude_holidays) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.hr.doctype.attendance.attendance.get_unmarked_days',
|
|
||||||
async: false,
|
|
||||||
args: {
|
|
||||||
employee: employee,
|
|
||||||
month: month,
|
|
||||||
exclude_holidays: exclude_holidays
|
|
||||||
}
|
|
||||||
}).then(r => {
|
|
||||||
var options = [];
|
|
||||||
for (var d in r.message) {
|
|
||||||
var momentObj = moment(r.message[d], 'YYYY-MM-DD');
|
|
||||||
var date = momentObj.format('DD-MM-YYYY');
|
|
||||||
options.push({
|
|
||||||
"label": date,
|
|
||||||
"value": r.message[d],
|
|
||||||
"checked": 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
resolve(options);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,232 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
|
||||||
# See license.txt
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.tests.utils import FrappeTestCase
|
|
||||||
from frappe.utils import (
|
|
||||||
add_days,
|
|
||||||
add_months,
|
|
||||||
get_last_day,
|
|
||||||
get_year_ending,
|
|
||||||
get_year_start,
|
|
||||||
getdate,
|
|
||||||
nowdate,
|
|
||||||
)
|
|
||||||
|
|
||||||
from erpnext.hr.doctype.attendance.attendance import (
|
|
||||||
DuplicateAttendanceError,
|
|
||||||
OverlappingShiftAttendanceError,
|
|
||||||
get_month_map,
|
|
||||||
get_unmarked_days,
|
|
||||||
mark_attendance,
|
|
||||||
)
|
|
||||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
|
||||||
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
|
|
||||||
|
|
||||||
test_records = frappe.get_test_records("Attendance")
|
|
||||||
|
|
||||||
|
|
||||||
class TestAttendance(FrappeTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
|
|
||||||
|
|
||||||
from_date = get_year_start(getdate())
|
|
||||||
to_date = get_year_ending(getdate())
|
|
||||||
self.holiday_list = make_holiday_list(from_date=from_date, to_date=to_date)
|
|
||||||
frappe.db.delete("Attendance")
|
|
||||||
|
|
||||||
def test_duplicate_attendance(self):
|
|
||||||
employee = make_employee("test_duplicate_attendance@example.com", company="_Test Company")
|
|
||||||
date = nowdate()
|
|
||||||
|
|
||||||
mark_attendance(employee, date, "Present")
|
|
||||||
attendance = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Attendance",
|
|
||||||
"employee": employee,
|
|
||||||
"attendance_date": date,
|
|
||||||
"status": "Absent",
|
|
||||||
"company": "_Test Company",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertRaises(DuplicateAttendanceError, attendance.insert)
|
|
||||||
|
|
||||||
def test_duplicate_attendance_with_shift(self):
|
|
||||||
from erpnext.hr.doctype.shift_type.test_shift_type import setup_shift_type
|
|
||||||
|
|
||||||
employee = make_employee("test_duplicate_attendance@example.com", company="_Test Company")
|
|
||||||
date = nowdate()
|
|
||||||
|
|
||||||
shift_1 = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="10:00:00")
|
|
||||||
mark_attendance(employee, date, "Present", shift=shift_1.name)
|
|
||||||
|
|
||||||
# attendance record with shift
|
|
||||||
attendance = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Attendance",
|
|
||||||
"employee": employee,
|
|
||||||
"attendance_date": date,
|
|
||||||
"status": "Absent",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"shift": shift_1.name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertRaises(DuplicateAttendanceError, attendance.insert)
|
|
||||||
|
|
||||||
# attendance record without any shift
|
|
||||||
attendance = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Attendance",
|
|
||||||
"employee": employee,
|
|
||||||
"attendance_date": date,
|
|
||||||
"status": "Absent",
|
|
||||||
"company": "_Test Company",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertRaises(DuplicateAttendanceError, attendance.insert)
|
|
||||||
|
|
||||||
def test_overlapping_shift_attendance_validation(self):
|
|
||||||
from erpnext.hr.doctype.shift_type.test_shift_type import setup_shift_type
|
|
||||||
|
|
||||||
employee = make_employee("test_overlap_attendance@example.com", company="_Test Company")
|
|
||||||
date = nowdate()
|
|
||||||
|
|
||||||
shift_1 = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="10:00:00")
|
|
||||||
shift_2 = setup_shift_type(shift_type="Shift 2", start_time="09:30:00", end_time="11:00:00")
|
|
||||||
|
|
||||||
mark_attendance(employee, date, "Present", shift=shift_1.name)
|
|
||||||
|
|
||||||
# attendance record with overlapping shift
|
|
||||||
attendance = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Attendance",
|
|
||||||
"employee": employee,
|
|
||||||
"attendance_date": date,
|
|
||||||
"status": "Absent",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"shift": shift_2.name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertRaises(OverlappingShiftAttendanceError, attendance.insert)
|
|
||||||
|
|
||||||
def test_allow_attendance_with_different_shifts(self):
|
|
||||||
# allows attendance with 2 different non-overlapping shifts
|
|
||||||
from erpnext.hr.doctype.shift_type.test_shift_type import setup_shift_type
|
|
||||||
|
|
||||||
employee = make_employee("test_duplicate_attendance@example.com", company="_Test Company")
|
|
||||||
date = nowdate()
|
|
||||||
|
|
||||||
shift_1 = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="10:00:00")
|
|
||||||
shift_2 = setup_shift_type(shift_type="Shift 2", start_time="11:00:00", end_time="12:00:00")
|
|
||||||
|
|
||||||
mark_attendance(employee, date, "Present", shift_1.name)
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Attendance",
|
|
||||||
"employee": employee,
|
|
||||||
"attendance_date": date,
|
|
||||||
"status": "Absent",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"shift": shift_2.name,
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
def test_mark_absent(self):
|
|
||||||
employee = make_employee("test_mark_absent@example.com")
|
|
||||||
date = nowdate()
|
|
||||||
|
|
||||||
attendance = mark_attendance(employee, date, "Absent")
|
|
||||||
fetch_attendance = frappe.get_value(
|
|
||||||
"Attendance", {"employee": employee, "attendance_date": date, "status": "Absent"}
|
|
||||||
)
|
|
||||||
self.assertEqual(attendance, fetch_attendance)
|
|
||||||
|
|
||||||
def test_unmarked_days(self):
|
|
||||||
first_sunday = get_first_sunday(
|
|
||||||
self.holiday_list, for_date=get_last_day(add_months(getdate(), -1))
|
|
||||||
)
|
|
||||||
attendance_date = add_days(first_sunday, 1)
|
|
||||||
|
|
||||||
employee = make_employee(
|
|
||||||
"test_unmarked_days@example.com", date_of_joining=add_days(attendance_date, -1)
|
|
||||||
)
|
|
||||||
frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
|
|
||||||
|
|
||||||
mark_attendance(employee, attendance_date, "Present")
|
|
||||||
month_name = get_month_name(attendance_date)
|
|
||||||
|
|
||||||
unmarked_days = get_unmarked_days(employee, month_name)
|
|
||||||
unmarked_days = [getdate(date) for date in unmarked_days]
|
|
||||||
|
|
||||||
# attendance already marked for the day
|
|
||||||
self.assertNotIn(attendance_date, unmarked_days)
|
|
||||||
# attendance unmarked
|
|
||||||
self.assertIn(getdate(add_days(attendance_date, 1)), unmarked_days)
|
|
||||||
# holiday considered in unmarked days
|
|
||||||
self.assertIn(first_sunday, unmarked_days)
|
|
||||||
|
|
||||||
def test_unmarked_days_excluding_holidays(self):
|
|
||||||
first_sunday = get_first_sunday(
|
|
||||||
self.holiday_list, for_date=get_last_day(add_months(getdate(), -1))
|
|
||||||
)
|
|
||||||
attendance_date = add_days(first_sunday, 1)
|
|
||||||
|
|
||||||
employee = make_employee(
|
|
||||||
"test_unmarked_days@example.com", date_of_joining=add_days(attendance_date, -1)
|
|
||||||
)
|
|
||||||
frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
|
|
||||||
|
|
||||||
mark_attendance(employee, attendance_date, "Present")
|
|
||||||
month_name = get_month_name(attendance_date)
|
|
||||||
|
|
||||||
unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True)
|
|
||||||
unmarked_days = [getdate(date) for date in unmarked_days]
|
|
||||||
|
|
||||||
# attendance already marked for the day
|
|
||||||
self.assertNotIn(attendance_date, unmarked_days)
|
|
||||||
# attendance unmarked
|
|
||||||
self.assertIn(getdate(add_days(attendance_date, 1)), unmarked_days)
|
|
||||||
# holidays not considered in unmarked days
|
|
||||||
self.assertNotIn(first_sunday, unmarked_days)
|
|
||||||
|
|
||||||
def test_unmarked_days_as_per_joining_and_relieving_dates(self):
|
|
||||||
first_sunday = get_first_sunday(
|
|
||||||
self.holiday_list, for_date=get_last_day(add_months(getdate(), -1))
|
|
||||||
)
|
|
||||||
date = add_days(first_sunday, 1)
|
|
||||||
|
|
||||||
doj = add_days(date, 1)
|
|
||||||
relieving_date = add_days(date, 5)
|
|
||||||
employee = make_employee(
|
|
||||||
"test_unmarked_days_as_per_doj@example.com", date_of_joining=doj, relieving_date=relieving_date
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
|
|
||||||
|
|
||||||
attendance_date = add_days(date, 2)
|
|
||||||
mark_attendance(employee, attendance_date, "Present")
|
|
||||||
month_name = get_month_name(attendance_date)
|
|
||||||
|
|
||||||
unmarked_days = get_unmarked_days(employee, month_name)
|
|
||||||
unmarked_days = [getdate(date) for date in unmarked_days]
|
|
||||||
|
|
||||||
# attendance already marked for the day
|
|
||||||
self.assertNotIn(attendance_date, unmarked_days)
|
|
||||||
# date before doj not in unmarked days
|
|
||||||
self.assertNotIn(add_days(doj, -1), unmarked_days)
|
|
||||||
# date after relieving not in unmarked days
|
|
||||||
self.assertNotIn(add_days(relieving_date, 1), unmarked_days)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
frappe.db.rollback()
|
|
||||||
|
|
||||||
|
|
||||||
def get_month_name(date):
|
|
||||||
month_number = date.month
|
|
||||||
for month, number in get_month_map().items():
|
|
||||||
if number == month_number:
|
|
||||||
return month
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"doctype": "Attendance",
|
|
||||||
"name": "_Test Attendance 1",
|
|
||||||
"employee": "_T-Employee-00001",
|
|
||||||
"status": "Present",
|
|
||||||
"attendance_date": "2014-02-01",
|
|
||||||
"company": "_Test Company"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
cur_frm.add_fetch('employee', 'company', 'company');
|
|
||||||
|
|
||||||
frappe.ui.form.on('Attendance Request', {
|
|
||||||
half_day: function(frm) {
|
|
||||||
if(frm.doc.half_day == 1){
|
|
||||||
frm.set_df_property('half_day_date', 'reqd', true);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
frm.set_df_property('half_day_date', 'reqd', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,190 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_import": 1,
|
|
||||||
"allow_rename": 1,
|
|
||||||
"autoname": "HR-ARQ-.YY.-.MM.-.#####",
|
|
||||||
"creation": "2018-04-13 15:37:40.918990",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"employee",
|
|
||||||
"employee_name",
|
|
||||||
"department",
|
|
||||||
"column_break_5",
|
|
||||||
"company",
|
|
||||||
"from_date",
|
|
||||||
"to_date",
|
|
||||||
"half_day",
|
|
||||||
"half_day_date",
|
|
||||||
"reason_section",
|
|
||||||
"reason",
|
|
||||||
"column_break_4",
|
|
||||||
"explanation",
|
|
||||||
"amended_from"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "employee",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Employee",
|
|
||||||
"options": "Employee",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "employee.employee_name",
|
|
||||||
"fieldname": "employee_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Employee Name",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "employee.department",
|
|
||||||
"fieldname": "department",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Department",
|
|
||||||
"options": "Department",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "company",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Company",
|
|
||||||
"options": "Company",
|
|
||||||
"remember_last_selected_value": 1,
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_5",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "from_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "From Date",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "to_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "To Date",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "half_day",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Half Day"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "half_day",
|
|
||||||
"fieldname": "half_day_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"label": "Half Day Date"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "reason_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Reason"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "reason",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Reason",
|
|
||||||
"options": "Work From Home\nOn Duty",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_4",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "explanation",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Explanation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "amended_from",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Amended From",
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Attendance Request",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_submittable": 1,
|
|
||||||
"links": [],
|
|
||||||
"modified": "2019-12-16 11:49:26.943173",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Attendance Request",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"amend": 1,
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 1,
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR Manager",
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 1,
|
|
||||||
"cancel": 1,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR User",
|
|
||||||
"share": 1,
|
|
||||||
"submit": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Employee",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"title_field": "employee_name",
|
|
||||||
"track_changes": 1
|
|
||||||
}
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe.utils import add_days, date_diff, getdate
|
|
||||||
|
|
||||||
from erpnext.hr.doctype.employee.employee import is_holiday
|
|
||||||
from erpnext.hr.utils import validate_active_employee, validate_dates
|
|
||||||
|
|
||||||
|
|
||||||
class AttendanceRequest(Document):
|
|
||||||
def validate(self):
|
|
||||||
validate_active_employee(self.employee)
|
|
||||||
validate_dates(self, self.from_date, self.to_date)
|
|
||||||
if self.half_day:
|
|
||||||
if not getdate(self.from_date) <= getdate(self.half_day_date) <= getdate(self.to_date):
|
|
||||||
frappe.throw(_("Half day date should be in between from date and to date"))
|
|
||||||
|
|
||||||
def on_submit(self):
|
|
||||||
self.create_attendance()
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
attendance_list = frappe.get_list(
|
|
||||||
"Attendance", {"employee": self.employee, "attendance_request": self.name}
|
|
||||||
)
|
|
||||||
if attendance_list:
|
|
||||||
for attendance in attendance_list:
|
|
||||||
attendance_obj = frappe.get_doc("Attendance", attendance["name"])
|
|
||||||
attendance_obj.cancel()
|
|
||||||
|
|
||||||
def create_attendance(self):
|
|
||||||
request_days = date_diff(self.to_date, self.from_date) + 1
|
|
||||||
for number in range(request_days):
|
|
||||||
attendance_date = add_days(self.from_date, number)
|
|
||||||
skip_attendance = self.validate_if_attendance_not_applicable(attendance_date)
|
|
||||||
if not skip_attendance:
|
|
||||||
attendance = frappe.new_doc("Attendance")
|
|
||||||
attendance.employee = self.employee
|
|
||||||
attendance.employee_name = self.employee_name
|
|
||||||
if self.half_day and date_diff(getdate(self.half_day_date), getdate(attendance_date)) == 0:
|
|
||||||
attendance.status = "Half Day"
|
|
||||||
elif self.reason == "Work From Home":
|
|
||||||
attendance.status = "Work From Home"
|
|
||||||
else:
|
|
||||||
attendance.status = "Present"
|
|
||||||
attendance.attendance_date = attendance_date
|
|
||||||
attendance.company = self.company
|
|
||||||
attendance.attendance_request = self.name
|
|
||||||
attendance.save(ignore_permissions=True)
|
|
||||||
attendance.submit()
|
|
||||||
|
|
||||||
def validate_if_attendance_not_applicable(self, attendance_date):
|
|
||||||
# Check if attendance_date is a Holiday
|
|
||||||
if is_holiday(self.employee, attendance_date):
|
|
||||||
frappe.msgprint(
|
|
||||||
_("Attendance not submitted for {0} as it is a Holiday.").format(attendance_date), alert=1
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check if employee on Leave
|
|
||||||
leave_record = frappe.db.sql(
|
|
||||||
"""select half_day from `tabLeave Application`
|
|
||||||
where employee = %s and %s between from_date and to_date
|
|
||||||
and docstatus = 1""",
|
|
||||||
(self.employee, attendance_date),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
if leave_record:
|
|
||||||
frappe.msgprint(
|
|
||||||
_("Attendance not submitted for {0} as {1} on leave.").format(attendance_date, self.employee),
|
|
||||||
alert=1,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
def get_data():
|
|
||||||
return {"fieldname": "attendance_request", "transactions": [{"items": ["Attendance"]}]}
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# See license.txt
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.utils import nowdate
|
|
||||||
|
|
||||||
test_dependencies = ["Employee"]
|
|
||||||
|
|
||||||
|
|
||||||
class TestAttendanceRequest(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
for doctype in ["Attendance Request", "Attendance"]:
|
|
||||||
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
frappe.db.rollback()
|
|
||||||
|
|
||||||
def test_on_duty_attendance_request(self):
|
|
||||||
"Test creation/updation of Attendace from Attendance Request, on duty."
|
|
||||||
today = nowdate()
|
|
||||||
employee = get_employee()
|
|
||||||
attendance_request = frappe.new_doc("Attendance Request")
|
|
||||||
attendance_request.employee = employee.name
|
|
||||||
attendance_request.from_date = date(date.today().year, 1, 1)
|
|
||||||
attendance_request.to_date = date(date.today().year, 1, 2)
|
|
||||||
attendance_request.reason = "On Duty"
|
|
||||||
attendance_request.company = "_Test Company"
|
|
||||||
attendance_request.insert()
|
|
||||||
attendance_request.submit()
|
|
||||||
|
|
||||||
attendance = frappe.db.get_value(
|
|
||||||
"Attendance",
|
|
||||||
filters={
|
|
||||||
"attendance_request": attendance_request.name,
|
|
||||||
"attendance_date": date(date.today().year, 1, 1),
|
|
||||||
},
|
|
||||||
fieldname=["status", "docstatus"],
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
self.assertEqual(attendance.status, "Present")
|
|
||||||
self.assertEqual(attendance.docstatus, 1)
|
|
||||||
|
|
||||||
# cancelling attendance request cancels linked attendances
|
|
||||||
attendance_request.cancel()
|
|
||||||
|
|
||||||
# cancellation alters docname
|
|
||||||
# fetch attendance value again to avoid stale docname
|
|
||||||
attendance_docstatus = frappe.db.get_value(
|
|
||||||
"Attendance",
|
|
||||||
filters={
|
|
||||||
"attendance_request": attendance_request.name,
|
|
||||||
"attendance_date": date(date.today().year, 1, 1),
|
|
||||||
},
|
|
||||||
fieldname="docstatus",
|
|
||||||
)
|
|
||||||
self.assertEqual(attendance_docstatus, 2)
|
|
||||||
|
|
||||||
def test_work_from_home_attendance_request(self):
|
|
||||||
"Test creation/updation of Attendace from Attendance Request, work from home."
|
|
||||||
today = nowdate()
|
|
||||||
employee = get_employee()
|
|
||||||
attendance_request = frappe.new_doc("Attendance Request")
|
|
||||||
attendance_request.employee = employee.name
|
|
||||||
attendance_request.from_date = date(date.today().year, 1, 1)
|
|
||||||
attendance_request.to_date = date(date.today().year, 1, 2)
|
|
||||||
attendance_request.reason = "Work From Home"
|
|
||||||
attendance_request.company = "_Test Company"
|
|
||||||
attendance_request.insert()
|
|
||||||
attendance_request.submit()
|
|
||||||
|
|
||||||
attendance_status = frappe.db.get_value(
|
|
||||||
"Attendance",
|
|
||||||
filters={
|
|
||||||
"attendance_request": attendance_request.name,
|
|
||||||
"attendance_date": date(date.today().year, 1, 1),
|
|
||||||
},
|
|
||||||
fieldname="status",
|
|
||||||
)
|
|
||||||
self.assertEqual(attendance_status, "Work From Home")
|
|
||||||
|
|
||||||
attendance_request.cancel()
|
|
||||||
|
|
||||||
# cancellation alters docname
|
|
||||||
# fetch attendance value again to avoid stale docname
|
|
||||||
attendance_docstatus = frappe.db.get_value(
|
|
||||||
"Attendance",
|
|
||||||
filters={
|
|
||||||
"attendance_request": attendance_request.name,
|
|
||||||
"attendance_date": date(date.today().year, 1, 1),
|
|
||||||
},
|
|
||||||
fieldname="docstatus",
|
|
||||||
)
|
|
||||||
self.assertEqual(attendance_docstatus, 2)
|
|
||||||
|
|
||||||
|
|
||||||
def get_employee():
|
|
||||||
return frappe.get_doc("Employee", "_T-Employee-00001")
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
{
|
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_import": 1,
|
|
||||||
"allow_rename": 1,
|
|
||||||
"autoname": "field:branch",
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-01-10 16:34:13",
|
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "Setup",
|
|
||||||
"editable_grid": 0,
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"fieldname": "branch",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Branch",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "branch",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "fa fa-code-fork",
|
|
||||||
"idx": 1,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2016-07-25 05:24:26.534086",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Branch",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on('Compensatory Leave Request', {
|
|
||||||
refresh: function(frm) {
|
|
||||||
frm.set_query("leave_type", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
"is_compensatory": true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
half_day: function(frm) {
|
|
||||||
if(frm.doc.half_day == 1){
|
|
||||||
frm.set_df_property('half_day_date', 'reqd', true);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
frm.set_df_property('half_day_date', 'reqd', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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