Merge branch 'develop' into make-image-field-obsolete-in-web-item

This commit is contained in:
Marica 2022-07-25 12:43:08 +05:30 committed by GitHub
commit d2d651a0c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1126 changed files with 14526 additions and 222309 deletions

View File

@ -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

View File

@ -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
View 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 }}

View File

@ -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:

View File

@ -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
View 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',
],
],
},
};

View File

@ -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)

View File

@ -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",

View File

@ -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:

View File

@ -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}
"""

View File

@ -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", {

View File

@ -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",

View File

@ -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

View File

@ -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()

View File

@ -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]

View File

@ -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:

View File

@ -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) {

View File

@ -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

View File

@ -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")

View File

@ -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",

View File

@ -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"])

View File

@ -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",

View File

@ -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")

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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",

View File

@ -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"
}

View File

@ -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

View File

@ -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",

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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,
)

View File

@ -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):

View File

@ -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",

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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):

View File

@ -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;
}, },

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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},

View File

@ -1,6 +0,0 @@
Key features:
- Leave and Attendance
- Payroll
- Appraisal
- Expense Claim

View File

@ -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": []
}

View File

@ -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": []
}

View File

@ -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": []
}

View File

@ -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": []
}

View File

@ -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": []
}

View File

@ -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": []
}

View File

@ -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": []
}

View File

@ -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();
}
}
});
}
},
});

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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) {
// }
});

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
Performance of an Employee in a Time Period against given goals.

View File

@ -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');
}
});

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
Goal for the parent Appraisal.

View File

@ -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
}

View File

@ -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

View File

@ -1 +0,0 @@
Standard set of goals for an Employee / Designation / Job Profile. New Appraisal transactions can be created from the Template.

View File

@ -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) {
}
});

View File

@ -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
}

View File

@ -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))

View File

@ -1,7 +0,0 @@
def get_data():
return {
"fieldname": "kra_template",
"transactions": [
{"items": ["Appraisal"]},
],
}

View File

@ -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

View File

@ -1 +0,0 @@
Goal details for the parent Appraisal Template.

View File

@ -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
}

View File

@ -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

View File

@ -1 +0,0 @@
Attendance record of an Employee on a particular date.

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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

View File

@ -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"
};

View File

@ -1,2 +0,0 @@
def get_data():
return {"fieldname": "attendance", "transactions": [{"label": "", "items": ["Employee Checkin"]}]}

View File

@ -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);
});
});
}
};

View File

@ -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

View File

@ -1,10 +0,0 @@
[
{
"doctype": "Attendance",
"name": "_Test Attendance 1",
"employee": "_T-Employee-00001",
"status": "Present",
"attendance_date": "2014-02-01",
"company": "_Test Company"
}
]

View File

@ -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);
}
}
});

View File

@ -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
}

View File

@ -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

View File

@ -1,2 +0,0 @@
def get_data():
return {"fieldname": "attendance_request", "transactions": [{"items": ["Attendance"]}]}

View File

@ -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")

View File

@ -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
}

View File

@ -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