Merge pull request #31467 from ruchamahabal/hr-separation

This commit is contained in:
Rucha Mahabal 2022-07-20 16:54:44 +05:30 committed by GitHub
commit 2b0b53f587
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1051 changed files with 2208 additions and 71077 deletions

View File

@ -49,15 +49,9 @@ class AccountingPeriod(Document):
@frappe.whitelist()
def get_doctypes_for_closing(self):
docs_for_closing = []
doctypes = [
"Sales Invoice",
"Purchase Invoice",
"Journal Entry",
"Payroll Entry",
"Bank Clearance",
"Asset",
"Stock Entry",
]
# get period closing doctypes from all the apps
doctypes = frappe.get_hooks("period_closing_doctypes")
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype)

View File

@ -104,7 +104,7 @@ class BankClearance(Document):
loan_repayment = frappe.qb.DocType("Loan Repayment")
loan_repayments = (
query = (
frappe.qb.from_(loan_repayment)
.select(
ConstantColumn("Loan Repayment").as_("payment_document"),
@ -118,13 +118,17 @@ class BankClearance(Document):
)
.where(loan_repayment.docstatus == 1)
.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.to_date)
.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 = [], []
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.utils import flt
from erpnext import get_company_currency
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
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"
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:
pe_amount_matching = get_pe_matching_query(amount_condition, account_from_to, transaction)
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)
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
@ -467,11 +483,13 @@ def get_lr_matching_query(bank_account, amount_condition, filters):
loan_repayment.posting_date,
)
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.repay_from_salary == 0)
.where(loan_repayment.clearance_date.isnull())
.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:
query.where(loan_repayment.amount_paid == filters.get("amount"))
else:
@ -602,37 +620,3 @@ def get_pi_matching_query(amount_condition):
AND ifnull(clearance_date, '') = ""
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", {
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 {
filters: {
name: [
"in",
[
"Payment Entry",
"Journal Entry",
"Sales Invoice",
"Purchase Invoice",
"Expense Claim",
],
],
name: ["in", payment_doctypes],
},
};
});
},
bank_account: function (frm) {
bank_account: function(frm) {
set_bank_statement_filter(frm);
},
setup: function (frm) {
setup: function(frm) {
frm.set_query("party_type", function () {
return {
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", {

View File

@ -58,18 +58,10 @@ class BankTransaction(StatusUpdater):
def clear_linked_payment_entries(self, for_cancel=False):
for payment_entry in self.payment_entries:
if payment_entry.payment_document in [
"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":
if payment_entry.payment_document == "Sales Invoice":
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):
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):
reconciled_bank_transactions = frappe.get_all(
"Bank Transaction Payments",

View File

@ -5,6 +5,7 @@ import json
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
get_linked_payments,
@ -18,28 +19,21 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(unittest.TestCase):
@classmethod
def setUpClass(cls):
class TestBankTransaction(FrappeTestCase):
def setUp(self):
for dt in [
"Loan Repayment",
"Bank Transaction",
"Payment Entry",
"Payment Entry Reference",
"POS Profile",
]:
frappe.db.delete(dt)
make_pos_profile()
add_transactions()
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.
def test_linked_payments(self):
bank_transaction = frappe.get_doc(
@ -155,6 +149,35 @@ class TestBankTransaction(unittest.TestCase):
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"):
try:
@ -364,3 +387,59 @@ def add_vouchers():
)
si.insert()
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

@ -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) {
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
if(jvd.reference_type==="Journal Entry") {
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 = {
filters: [
[jvd.reference_type, "docstatus", "=", 1]

View File

@ -24,7 +24,6 @@ from erpnext.accounts.utils import (
get_stock_and_account_balance,
)
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
class StockAccountInvalidTransaction(frappe.ValidationError):
@ -66,7 +65,6 @@ class JournalEntry(AccountsController):
self.set_against_account()
self.create_remarks()
self.set_print_format_fields()
self.validate_expense_claim()
self.validate_credit_debit_note()
self.validate_empty_accounts_table()
self.set_account_and_party_balance()
@ -83,27 +81,21 @@ class JournalEntry(AccountsController):
self.check_credit_limit()
self.make_gl_entries()
self.update_advance_paid()
self.update_expense_claim()
self.update_inter_company_jv()
self.update_invoice_discounting()
self.update_status_for_full_and_final_statement()
def on_cancel(self):
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_salary_slip(self.name)
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.make_gl_entries(1)
self.update_advance_paid()
self.update_expense_claim()
self.unlink_advance_entry_reference()
self.unlink_asset_reference()
self.unlink_inter_company_jv()
self.unlink_asset_adjustment_entry()
self.update_invoice_discounting()
self.update_status_for_full_and_final_statement()
def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account
@ -112,21 +104,13 @@ class JournalEntry(AccountsController):
advance_paid = frappe._dict()
for d in self.get("accounts"):
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)
for voucher_type, order_list in advance_paid.items():
for voucher_no in list(set(order_list)):
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):
if (
self.voucher_type == "Inter Company Journal Entry"
@ -935,29 +919,6 @@ class JournalEntry(AccountsController):
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):
if self.stock_entry:
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"];
} else if (frm.doc.party_type == "Supplier") {
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
} else if (frm.doc.party_type == "Employee") {
var doctypes = ["Expense Claim", "Journal Entry"];
} else {
var doctypes = ["Journal Entry"];
}
@ -140,17 +138,12 @@ frappe.ui.form.on('Payment Entry', {
const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company};
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)) {
filters[doc.party_type.toLowerCase()] = doc.party;
}
if(child.reference_doctype == "Expense Claim") {
filters["docstatus"] = 1;
filters["is_paid"] = 0;
}
return {
filters: filters
};
@ -730,7 +723,7 @@ frappe.ui.form.on('Payment Entry', {
c.payment_term = d.payment_term;
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)
total_positive_outstanding += flt(d.outstanding_amount);
else
@ -745,7 +738,7 @@ frappe.ui.form.on('Payment Entry', {
} else {
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;
}
});
@ -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) {
var total_positive_outstanding_including_order = 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]));
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) {

View File

@ -29,7 +29,6 @@ from erpnext.controllers.accounts_controller import (
get_supplier_block_status,
validate_taxes_and_charges,
)
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
from erpnext.setup.utils import get_exchange_rate
@ -88,7 +87,6 @@ class PaymentEntry(AccountsController):
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
self.make_gl_entries()
self.update_expense_claim()
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_payment_schedule()
@ -97,7 +95,6 @@ class PaymentEntry(AccountsController):
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.make_gl_entries(cancel=1)
self.update_expense_claim()
self.update_outstanding_amounts()
self.update_advance_paid()
self.delink_advance_entry_references()
@ -296,14 +293,7 @@ class PaymentEntry(AccountsController):
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
def validate_reference_documents(self):
if self.party_type == "Customer":
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"
valid_reference_doctypes = self.get_valid_reference_doctypes()
for d in self.get("references"):
if not d.allocated_amount:
@ -329,7 +319,7 @@ class PaymentEntry(AccountsController):
else:
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":
ref_party_account = (
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:
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):
no_oustanding_refs = {}
@ -980,24 +980,9 @@ class PaymentEntry(AccountsController):
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in (
"Sales Order",
"Purchase Order",
"Employee Advance",
"Gratuity",
):
if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
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):
self.reference_no = reference_doc.name
self.reference_date = nowdate()
@ -1191,7 +1176,6 @@ def validate_inclusive_tax(tax, doc):
@frappe.whitelist()
def get_outstanding_reference_documents(args):
if isinstance(args, str):
args = json.loads(args)
@ -1260,7 +1244,7 @@ def get_outstanding_reference_documents(args):
for d in outstanding_invoices:
d["exchange_rate"] = 1
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")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
@ -1587,20 +1571,17 @@ def get_outstanding_on_journal_entry(name):
@frappe.whitelist()
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)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
ref_doc.company
)
if reference_doctype == "Fees":
total_amount = ref_doc.get("grand_total")
if reference_doctype == "Dunning":
total_amount = outstanding_amount = ref_doc.get("dunning_amount")
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:
@ -1610,16 +1591,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
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 party_account_currency == company_currency:
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(
party_account_currency, company_currency, ref_doc.posting_date
)
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
elif reference_doctype == "Gratuity":
outstanding_amount = ref_doc.amount - flt(ref_doc.paid_amount)
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
else:
# 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)
@ -1662,114 +1621,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
"total_amount": flt(total_amount),
"outstanding_amount": flt(outstanding_amount),
"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()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=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()
if party_account and bank:
if dt == "Employee Advance":
reference_doc = doc
pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()
if discount_amount:
@ -1924,8 +1778,6 @@ def set_party_type(dt):
party_type = "Customer"
elif dt in ("Purchase Invoice", "Purchase Order"):
party_type = "Supplier"
elif dt in ("Expense Claim", "Employee Advance", "Gratuity"):
party_type = "Employee"
return party_type
@ -1936,12 +1788,6 @@ def set_party_account(dt, dn, doc, party_type):
party_account = doc.credit_to
elif dt == "Fees":
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:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
return party_account
@ -1976,24 +1822,12 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
else:
grand_total = doc.rounded_total or doc.grand_total
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":
grand_total = doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt == "Dunning":
grand_total = 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:
if party_account_currency == doc.company_currency:
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
else:
received_amount = paid_amount * doc.get("conversion_rate", 1)
if dt == "Employee Advance":
received_amount = paid_amount * doc.get("exchange_rate", 1)
else:
received_amount = abs(outstanding_amount)
if bank_amount:
@ -2024,8 +1856,6 @@ def set_paid_amount_and_received_amount(
else:
# if party account currency and bank currency is different then populate paid amount as well
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

View File

@ -19,8 +19,8 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
create_sales_invoice,
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.setup.doctype.employee.test_employee import make_employee
test_dependencies = ["Item"]
@ -297,31 +297,6 @@ class TestPaymentEntry(FrappeTestCase):
self.assertEqual(flt(outstanding_amount), 250)
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):
si = create_sales_invoice(
customer="_Test Customer USD",
@ -762,6 +737,10 @@ class TestPaymentEntry(FrappeTestCase):
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):
payment_entry = frappe.new_doc("Payment Entry")

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

@ -198,12 +198,10 @@ def get_loan_entries(filters):
amount_field = (loan_doc.disbursed_amount).as_("credit")
posting_date = (loan_doc.disbursement_date).as_("posting_date")
account = loan_doc.disbursement_account
salary_condition = loan_doc.docstatus == 1
else:
amount_field = (loan_doc.amount_paid).as_("debit")
posting_date = (loan_doc.posting_date).as_("posting_date")
account = loan_doc.payment_account
salary_condition = loan_doc.repay_from_salary == 0
query = (
frappe.qb.from_(loan_doc)
@ -216,12 +214,14 @@ def get_loan_entries(filters):
posting_date,
)
.where(loan_doc.docstatus == 1)
.where(salary_condition)
.where(account == filters.get("account"))
.where(posting_date <= 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)
loan_docs.extend(entries)
@ -267,23 +267,24 @@ def get_loan_amount(filters):
amount_field = Sum(loan_doc.disbursed_amount)
posting_date = (loan_doc.disbursement_date).as_("posting_date")
account = loan_doc.disbursement_account
salary_condition = loan_doc.docstatus == 1
else:
amount_field = Sum(loan_doc.amount_paid)
posting_date = (loan_doc.posting_date).as_("posting_date")
account = loan_doc.payment_account
salary_condition = loan_doc.repay_from_salary == 0
amount = (
query = (
frappe.qb.from_(loan_doc)
.select(amount_field)
.where(loan_doc.docstatus == 1)
.where(salary_condition)
.where(account == filters.get("account"))
.where(posting_date > 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)
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

@ -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
def reconcile_against_document(args):
def reconcile_against_document(args): # nosemgrep
"""
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
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):

View File

@ -7,7 +7,7 @@ import frappe
from frappe.utils import now
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

View File

@ -7,8 +7,8 @@ from frappe.desk.form import assign_to
from frappe.model.document import Document
from frappe.utils import add_days, flt, unique
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
from erpnext.setup.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday
class EmployeeBoardingController(Document):

View File

@ -80,13 +80,14 @@ calendars = [
"Holiday List",
]
website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner", "Job Opening"]
website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner"]
website_context = {
"favicon": "/assets/erpnext/images/erpnext-favicon.svg",
"splash_image": "/assets/erpnext/images/erpnext-logo.svg",
}
# nosemgrep
website_route_rules = [
{"from_route": "/orders", "to_route": "Sales Order"},
{
@ -163,7 +164,6 @@ website_route_rules = [
"to_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": "/timesheets", "to_route": "Timesheet"},
{"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},
]
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 = {
"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
@ -289,9 +291,9 @@ doc_events = {
},
"User": {
"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": [
"erpnext.hr.doctype.employee.employee.update_user_permissions",
"erpnext.setup.doctype.employee.employee.update_user_permissions",
"erpnext.portal.utils.set_default_role",
],
},
@ -366,8 +368,6 @@ auto_cancel_exempted_doctypes = [
"Payment Entry",
]
after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
scheduler_events = {
"cron": {
"0/5 * * * *": [
@ -387,16 +387,13 @@ scheduler_events = {
},
"all": [
"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",
],
"hourly": [
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails",
"erpnext.accounts.doctype.subscription.subscription.process_all",
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
"erpnext.projects.doctype.project.project.hourly_reminder",
"erpnext.projects.doctype.project.project.collect_project_status",
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
],
"hourly_long": [
"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.controllers.accounts_controller.update_invoice_status",
"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.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.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards",
"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.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
"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": [
"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.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_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
"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": [
"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",
@ -472,6 +460,28 @@ payment_gateway_enabled = "erpnext.accounts.utils.create_payment_gateway_account
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 = [
"GL Entry",
"Payment Ledger Entry",
@ -479,12 +489,8 @@ accounting_dimension_doctypes = [
"Purchase Invoice",
"Payment Entry",
"Asset",
"Expense Claim",
"Expense Claim Detail",
"Expense Taxes and Charges",
"Stock Entry",
"Budget",
"Payroll Entry",
"Delivery Note",
"Sales Invoice Item",
"Purchase Invoice Item",
@ -502,7 +508,6 @@ accounting_dimension_doctypes = [
"Asset Value Adjustment",
"Loyalty Program",
"Stock Reconciliation",
"Travel Request",
"POS Profile",
"Opening Invoice Creation Tool",
"Opening Invoice Creation Tool Item",
@ -515,14 +520,15 @@ accounting_dimension_doctypes = [
"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 = {
"France": {
"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": {
"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",
@ -570,9 +576,6 @@ global_search_doctypes = {
{"doctype": "Material Request", "index": 16},
{"doctype": "Delivery Trip", "index": 17},
{"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": "Lead", "index": 23},
{"doctype": "Opportunity", "index": 24},
@ -588,13 +591,7 @@ global_search_doctypes = {
{"doctype": "Batch", "index": 34},
{"doctype": "Branch", "index": 35},
{"doctype": "Department", "index": 36},
{"doctype": "Employee Grade", "index": 37},
{"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": "Maintenance Schedule", "index": 45},
{"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);
}
}
});

View File

@ -1,575 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "HR-CMP-.YY.-.MM.-.#####",
"beta": 0,
"creation": "2018-04-13 14:51:39.326768",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "leave_type",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Leave Type",
"length": 0,
"no_copy": 0,
"options": "Leave Type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "leave_allocation",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Leave Allocation",
"length": 0,
"no_copy": 0,
"options": "Leave Allocation",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "worked_on",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Worked On Holiday",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "work_from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Work From Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "work_end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Work End Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "half_day",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Half Day",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "half_day",
"fieldname": "half_day_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Half Day Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reason",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reason",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Compensatory Leave Request",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:36.266924",
"modified_by": "Administrator",
"module": "HR",
"name": "Compensatory Leave Request",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "employee_name",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@ -1,155 +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, cint, date_diff, format_date, getdate
from erpnext.hr.utils import (
create_additional_leave_ledger_entry,
get_holiday_dates_for_employee,
get_leave_period,
validate_active_employee,
validate_dates,
validate_overlap,
)
class CompensatoryLeaveRequest(Document):
def validate(self):
validate_active_employee(self.employee)
validate_dates(self, self.work_from_date, self.work_end_date)
if self.half_day:
if not self.half_day_date:
frappe.throw(_("Half Day Date is mandatory"))
if (
not getdate(self.work_from_date) <= getdate(self.half_day_date) <= getdate(self.work_end_date)
):
frappe.throw(_("Half Day Date should be in between Work From Date and Work End Date"))
validate_overlap(self, self.work_from_date, self.work_end_date)
self.validate_holidays()
self.validate_attendance()
if not self.leave_type:
frappe.throw(_("Leave Type is madatory"))
def validate_attendance(self):
attendance = frappe.get_all(
"Attendance",
filters={
"attendance_date": ["between", (self.work_from_date, self.work_end_date)],
"status": "Present",
"docstatus": 1,
"employee": self.employee,
},
fields=["attendance_date", "status"],
)
if len(attendance) < date_diff(self.work_end_date, self.work_from_date) + 1:
frappe.throw(_("You are not present all day(s) between compensatory leave request days"))
def validate_holidays(self):
holidays = get_holiday_dates_for_employee(self.employee, self.work_from_date, self.work_end_date)
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
if date_diff(self.work_end_date, self.work_from_date):
msg = _("The days between {0} to {1} are not valid holidays.").format(
frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date))
)
else:
msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date)))
frappe.throw(msg)
def on_submit(self):
company = frappe.db.get_value("Employee", self.employee, "company")
date_difference = date_diff(self.work_end_date, self.work_from_date) + 1
if self.half_day:
date_difference -= 0.5
leave_period = get_leave_period(self.work_from_date, self.work_end_date, company)
if leave_period:
leave_allocation = self.get_existing_allocation_for_period(leave_period)
if leave_allocation:
leave_allocation.new_leaves_allocated += date_difference
leave_allocation.validate()
leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
# generate additional ledger entry for the new compensatory leaves off
create_additional_leave_ledger_entry(
leave_allocation, date_difference, add_days(self.work_end_date, 1)
)
else:
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
self.db_set("leave_allocation", leave_allocation.name)
else:
frappe.throw(
_("There is no leave period in between {0} and {1}").format(
format_date(self.work_from_date), format_date(self.work_end_date)
)
)
def on_cancel(self):
if self.leave_allocation:
date_difference = date_diff(self.work_end_date, self.work_from_date) + 1
if self.half_day:
date_difference -= 0.5
leave_allocation = frappe.get_doc("Leave Allocation", self.leave_allocation)
if leave_allocation:
leave_allocation.new_leaves_allocated -= date_difference
if leave_allocation.new_leaves_allocated - date_difference <= 0:
leave_allocation.new_leaves_allocated = 0
leave_allocation.validate()
leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
# create reverse entry on cancelation
create_additional_leave_ledger_entry(
leave_allocation, date_difference * -1, add_days(self.work_end_date, 1)
)
def get_existing_allocation_for_period(self, leave_period):
leave_allocation = frappe.db.sql(
"""
select name
from `tabLeave Allocation`
where employee=%(employee)s and leave_type=%(leave_type)s
and docstatus=1
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
""",
{
"from_date": leave_period[0].from_date,
"to_date": leave_period[0].to_date,
"employee": self.employee,
"leave_type": self.leave_type,
},
as_dict=1,
)
if leave_allocation:
return frappe.get_doc("Leave Allocation", leave_allocation[0].name)
else:
return False
def create_leave_allocation(self, leave_period, date_difference):
is_carry_forward = frappe.db.get_value("Leave Type", self.leave_type, "is_carry_forward")
allocation = frappe.get_doc(
dict(
doctype="Leave Allocation",
employee=self.employee,
employee_name=self.employee_name,
leave_type=self.leave_type,
from_date=add_days(self.work_end_date, 1),
to_date=leave_period[0].to_date,
carry_forward=cint(is_carry_forward),
new_leaves_allocated=date_difference,
total_leaves_allocated=date_difference,
description=self.reason,
)
)
allocation.insert(ignore_permissions=True)
allocation.submit()
return allocation

View File

@ -1,157 +0,0 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.utils import add_days, add_months, today
from erpnext.hr.doctype.attendance_request.test_attendance_request import get_employee
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
test_dependencies = ["Employee"]
class TestCompensatoryLeaveRequest(unittest.TestCase):
def setUp(self):
frappe.db.sql(""" delete from `tabCompensatory Leave Request`""")
frappe.db.sql(""" delete from `tabLeave Ledger Entry`""")
frappe.db.sql(""" delete from `tabLeave Allocation`""")
frappe.db.sql(
""" delete from `tabAttendance` where attendance_date in {0} """.format(
(today(), add_days(today(), -1))
)
) # nosec
create_leave_period(add_months(today(), -3), add_months(today(), 3), "_Test Company")
create_holiday_list()
employee = get_employee()
employee.holiday_list = "_Test Compensatory Leave"
employee.save()
def test_leave_balance_on_submit(self):
"""check creation of leave allocation on submission of compensatory leave request"""
employee = get_employee()
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
before = get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, today())
compensatory_leave_request.submit()
self.assertEqual(
get_leave_balance_on(
employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)
),
before + 1,
)
def test_leave_allocation_update_on_submit(self):
employee = get_employee()
mark_attendance(employee, date=add_days(today(), -1))
compensatory_leave_request = get_compensatory_leave_request(
employee.name, leave_date=add_days(today(), -1)
)
compensatory_leave_request.submit()
# leave allocation creation on submit
leaves_allocated = frappe.db.get_value(
"Leave Allocation",
{"name": compensatory_leave_request.leave_allocation},
["total_leaves_allocated"],
)
self.assertEqual(leaves_allocated, 1)
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
compensatory_leave_request.submit()
# leave allocation updates on submission of second compensatory leave request
leaves_allocated = frappe.db.get_value(
"Leave Allocation",
{"name": compensatory_leave_request.leave_allocation},
["total_leaves_allocated"],
)
self.assertEqual(leaves_allocated, 2)
def test_creation_of_leave_ledger_entry_on_submit(self):
"""check creation of leave ledger entry on submission of leave request"""
employee = get_employee()
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
compensatory_leave_request.submit()
filters = dict(transaction_name=compensatory_leave_request.leave_allocation)
leave_ledger_entry = frappe.get_all("Leave Ledger Entry", fields="*", filters=filters)
self.assertEqual(len(leave_ledger_entry), 1)
self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
self.assertEqual(leave_ledger_entry[0].leaves, 1)
# check reverse leave ledger entry on cancellation
compensatory_leave_request.cancel()
leave_ledger_entry = frappe.get_all(
"Leave Ledger Entry", fields="*", filters=filters, order_by="creation desc"
)
self.assertEqual(len(leave_ledger_entry), 2)
self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
self.assertEqual(leave_ledger_entry[0].leaves, -1)
def get_compensatory_leave_request(employee, leave_date=today()):
prev_comp_leave_req = frappe.db.get_value(
"Compensatory Leave Request",
dict(
leave_type="Compensatory Off",
work_from_date=leave_date,
work_end_date=leave_date,
employee=employee,
),
"name",
)
if prev_comp_leave_req:
return frappe.get_doc("Compensatory Leave Request", prev_comp_leave_req)
return frappe.get_doc(
dict(
doctype="Compensatory Leave Request",
employee=employee,
leave_type="Compensatory Off",
work_from_date=leave_date,
work_end_date=leave_date,
reason="test",
)
).insert()
def mark_attendance(employee, date=today(), status="Present"):
if not frappe.db.exists(
dict(doctype="Attendance", employee=employee.name, attendance_date=date, status="Present")
):
attendance = frappe.get_doc(
{"doctype": "Attendance", "employee": employee.name, "attendance_date": date, "status": status}
)
attendance.save()
attendance.submit()
def create_holiday_list():
if frappe.db.exists("Holiday List", "_Test Compensatory Leave"):
return
holiday_list = frappe.get_doc(
{
"doctype": "Holiday List",
"from_date": add_months(today(), -3),
"to_date": add_months(today(), 3),
"holidays": [
{"description": "Test Holiday", "holiday_date": today()},
{"description": "Test Holiday 1", "holiday_date": add_days(today(), -1)},
],
"holiday_list_name": "_Test Compensatory Leave",
}
)
holiday_list.save()

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('Daily Work Summary', {
refresh: function (frm) {
}
});

View File

@ -1,175 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-11-08 04:58:20.001780",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "daily_work_summary_group",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Daily Work Summary Group",
"length": 0,
"no_copy": 0,
"options": "Daily Work Summary Group",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Open",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "Open\nSent",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email_sent_to",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email Sent To",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-16 11:02:06.727749",
"modified_by": "Administrator",
"module": "HR",
"name": "Daily Work Summary",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Employee",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 1,
"email": 1,
"export": 1,
"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": 0
}
],
"quick_entry": 1,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}

View File

@ -1,119 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from email_reply_parser import EmailReplyParser
from frappe import _
from frappe.model.document import Document
from frappe.utils import global_date_format
class DailyWorkSummary(Document):
def send_mails(self, dws_group, emails):
"""Send emails to get daily work summary to all users \
in selected daily work summary group"""
incoming_email_account = frappe.db.get_value(
"Email Account", dict(enable_incoming=1, default_incoming=1), "email_id"
)
self.db_set("email_sent_to", "\n".join(emails))
frappe.sendmail(
recipients=emails,
message=dws_group.message,
subject=dws_group.subject,
reference_doctype=self.doctype,
reference_name=self.name,
reply_to=incoming_email_account,
)
def send_summary(self):
"""Send summary of all replies. Called at midnight"""
args = self.get_message_details()
emails = get_user_emails_from_group(self.daily_work_summary_group)
frappe.sendmail(
recipients=emails,
template="daily_work_summary",
args=args,
subject=_(self.daily_work_summary_group),
reference_doctype=self.doctype,
reference_name=self.name,
)
self.db_set("status", "Sent")
def get_message_details(self):
"""Return args for template"""
dws_group = frappe.get_doc("Daily Work Summary Group", self.daily_work_summary_group)
replies = frappe.get_all(
"Communication",
fields=["content", "text_content", "sender"],
filters=dict(
reference_doctype=self.doctype,
reference_name=self.name,
communication_type="Communication",
sent_or_received="Received",
),
order_by="creation asc",
)
did_not_reply = self.email_sent_to.split()
for d in replies:
user = frappe.db.get_values(
"User", {"email": d.sender}, ["full_name", "user_image"], as_dict=True
)
d.sender_name = user[0].full_name if user else d.sender
d.image = user[0].image if user and user[0].image else None
original_image = d.image
# make thumbnail image
try:
if original_image:
file_name = frappe.get_list("File", {"file_url": original_image})
if file_name:
file_name = file_name[0].name
file_doc = frappe.get_doc("File", file_name)
thumbnail_image = file_doc.make_thumbnail(
set_as_thumbnail=False, width=100, height=100, crop=True
)
d.image = thumbnail_image
except Exception:
d.image = original_image
if d.sender in did_not_reply:
did_not_reply.remove(d.sender)
if d.text_content:
d.content = frappe.utils.md_to_html(EmailReplyParser.parse_reply(d.text_content))
did_not_reply = [
(frappe.db.get_value("User", {"email": email}, "full_name") or email) for email in did_not_reply
]
return dict(
replies=replies,
original_message=dws_group.message,
title=_("Work Summary for {0}").format(global_date_format(self.creation)),
did_not_reply=", ".join(did_not_reply) or "",
did_not_reply_title=_("No replies from"),
)
def get_user_emails_from_group(group):
"""Returns list of email of enabled users from the given group
:param group: Daily Work Summary Group `name`"""
group_doc = group
if isinstance(group_doc, str):
group_doc = frappe.get_doc("Daily Work Summary Group", group)
emails = get_users_email(group_doc)
return emails
def get_users_email(doc):
return [d.email for d in doc.users if frappe.db.get_value("User", d.user, "enabled")]

View File

@ -1,105 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import os
import unittest
import frappe
import frappe.utils
# test_records = frappe.get_test_records('Daily Work Summary')
class TestDailyWorkSummary(unittest.TestCase):
def test_email_trigger(self):
self.setup_and_prepare_test()
for d in self.users:
# check that email is sent to users
if d.message:
self.assertTrue(
d.email in [d.recipient for d in self.emails if self.groups.subject in d.message]
)
def test_email_trigger_failed(self):
hour = "00:00"
if frappe.utils.nowtime().split(":")[0] == "00":
hour = "01:00"
self.setup_and_prepare_test(hour)
for d in self.users:
# check that email is not sent to users
self.assertFalse(
d.email in [d.recipient for d in self.emails if self.groups.subject in d.message]
)
def test_incoming(self):
# get test mail with message-id as in-reply-to
self.setup_and_prepare_test()
with open(os.path.join(os.path.dirname(__file__), "test_data", "test-reply.raw"), "r") as f:
if not self.emails:
return
test_mails = [
f.read()
.replace("{{ sender }}", self.users[-1].email)
.replace("{{ message_id }}", self.emails[-1].message_id)
]
# pull the mail
email_account = frappe.get_doc("Email Account", "_Test Email Account 1")
email_account.db_set("enable_incoming", 1)
email_account.receive(test_mails=test_mails)
daily_work_summary = frappe.get_doc(
"Daily Work Summary", frappe.get_all("Daily Work Summary")[0].name
)
args = daily_work_summary.get_message_details()
self.assertTrue("I built Daily Work Summary!" in args.get("replies")[0].content)
def setup_and_prepare_test(self, hour=None):
frappe.db.sql("delete from `tabDaily Work Summary`")
frappe.db.sql("delete from `tabEmail Queue`")
frappe.db.sql("delete from `tabEmail Queue Recipient`")
frappe.db.sql("delete from `tabCommunication`")
frappe.db.sql("delete from `tabDaily Work Summary Group`")
self.users = frappe.get_all(
"User", fields=["email"], filters=dict(email=("!=", "test@example.com"))
)
self.setup_groups(hour)
from erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group import trigger_emails
trigger_emails()
# check if emails are created
self.emails = frappe.db.sql(
"""select r.recipient, q.message, q.message_id \
from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r \
where q.name = r.parent""",
as_dict=1,
)
def setup_groups(self, hour=None):
# setup email to trigger at this hour
if not hour:
hour = frappe.utils.nowtime().split(":")[0]
hour = hour + ":00"
groups = frappe.get_doc(
dict(
doctype="Daily Work Summary Group",
name="Daily Work Summary",
users=self.users,
send_emails_at=hour,
subject="this is a subject for testing summary emails",
message="this is a message for testing summary emails",
)
)
groups.insert()
self.groups = groups
self.groups.save()

View File

@ -1,75 +0,0 @@
From: {{ sender }}
Content-Type: multipart/alternative;
boundary="Apple-Mail=_29597CF7-20DD-4184-B3FA-85582C5C4361"
Message-Id: <07D687F6-10AA-4B9F-82DE-27753096164E@gmail.com>
Mime-Version: 1.0 (Mac OS X Mail 9.3 \(3124\))
X-Smtp-Server: 73CC8281-7E8F-4B47-8324-D5DA86EEDD4F
Subject: Re: What did you work on today?
Date: Thu, 10 Nov 2016 16:04:43 +0530
X-Universally-Unique-Identifier: A4D9669F-179C-42D8-A3D3-AA6A8C49A6F2
References: <{{ message_id }}>
To: test_in@iwebnotes.com
In-Reply-To: <{{ message_id }}>
--Apple-Mail=_29597CF7-20DD-4184-B3FA-85582C5C4361
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
charset=us-ascii
I built Daily Work Summary!
> On 10-Nov-2016, at 3:20 PM, Frappe <test@erpnext.com> wrote:
>=20
> Please share what did you do today. If you reply by midnight, your =
response will be recorded!
>=20
> This email was sent to rmehta@gmail.com
> Unsubscribe from this list =
<http://demo-test.erpnext.com.dev/api/method/frappe.email.queue.unsubscrib=
e?email=3Drmehta%40gmail.com&name=3D26cc3e5a5d&doctype=3DDaily+Work+Summar=
y&_signature=3D2c7ab37e6d775e5a481e9b4376154a41>
> Sent via ERPNext <https://erpnext.com/?source=3Dvia_email_footer>
--Apple-Mail=_29597CF7-20DD-4184-B3FA-85582C5C4361
Content-Transfer-Encoding: 7bit
Content-Type: text/html;
charset=us-ascii
<html><head><meta http-equiv="Content-Type" content="text/html charset=us-ascii"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">I built Daily Work Summary!<div class=""><br class=""><div><blockquote type="cite" class=""><div class="">On 10-Nov-2016, at 3:20 PM, Frappe &lt;<a href="mailto:test@erpnext.com" class="">test@erpnext.com</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class="">
<meta name="viewport" content="width=device-width" class="">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" class="">
<title class="">What did you work on today?</title>
<div style="line-height: 1.5; color: #36414C;" class="">
<!-- body -->
<div style="font-family: -apple-system, BlinkMacSystemFont,
" segoe="" ui",="" "roboto",="" "oxygen",="" "ubuntu",="" "cantarell",="" "fira="" sans",="" "droid="" "helvetica="" neue",="" sans-serif;="" font-size:="" 14px;="" padding:="" 10px;"="" class=""><p class="">Please share what did you do today. If you reply by midnight, your response will be recorded!</p>
</div>
<!-- footer -->
<div style="margin-top: 30px; font-family: Helvetica, Arial, sans-serif; font-size: 11px;
margin-bottom: 15px; border-top: 1px solid #d1d8dd;" data-email-footer="true" class="">
<div style="margin: 15px auto; padding: 0px 7px; text-align: center; color: #8d99a6;" class="">
This email was sent to <a href="mailto:rmehta@gmail.com" class="">rmehta@gmail.com</a>
<p style="margin: 15px auto;" class="">
<a href="http://demo-test.erpnext.com.dev/api/method/frappe.email.queue.unsubscribe?email=rmehta%40gmail.com&amp;name=26cc3e5a5d&amp;doctype=Daily+Work+Summary&amp;_signature=2c7ab37e6d775e5a481e9b4376154a41" style="color: #8d99a6; text-decoration: underline;
target=" _blank"="" class="">Unsubscribe from this list
</a>
</p>
</div><div style="margin: 15px auto;" class=""><div style="text-align: center;" class="">
<a href="https://erpnext.com/?source=via_email_footer" target="_blank" style="color: #8d99a6;" class="">
Sent via ERPNext
</a>
</div></div>
</div>
<!-- /footer -->
<div class="print-html"></div>
</div>
</div></blockquote></div><br class=""></div></body></html>
--Apple-Mail=_29597CF7-20DD-4184-B3FA-85582C5C4361--

View File

@ -1,12 +0,0 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Daily Work Summary Group', {
refresh: function (frm) {
if (!frm.is_new()) {
frm.add_custom_button(__('Daily Work Summary'), function () {
frappe.set_route('List', 'Daily Work Summary');
});
}
}
});

View File

@ -1,309 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "Prompt",
"beta": 0,
"creation": "2018-02-12 15:06:18.767239",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "enabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enabled",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "select_users",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Select Users",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "users",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Users",
"length": 0,
"no_copy": 0,
"options": "Daily Work Summary Group User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "send_emails_at",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Send Emails At",
"length": 0,
"no_copy": 0,
"options": "00:00\n01:00\n02:00\n03:00\n04:00\n05:00\n06:00\n07:00\n08:00\n09:00\n10:00\n11:00\n12:00\n13:00\n14:00\n15:00\n16:00\n17:00\n18:00\n19:00\n20:00\n21:00\n22:00\n23:00",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "holiday_list",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Holiday List",
"length": 0,
"no_copy": 0,
"options": "Holiday List",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mail_details",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reminder",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "What did you work on today?",
"fieldname": "subject",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subject",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "<p>Please share what did you do today. If you reply by midnight, your response will be recorded!</p>",
"fieldname": "message",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-16 10:56:03.998495",
"modified_by": "Administrator",
"module": "HR",
"name": "Daily Work Summary Group",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}

View File

@ -1,55 +0,0 @@
# # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# # For license information, please see license.txt
import frappe
import frappe.utils
from frappe import _
from frappe.model.document import Document
from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_user_emails_from_group
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
class DailyWorkSummaryGroup(Document):
def validate(self):
if self.users:
if not frappe.flags.in_test and not is_incoming_account_enabled():
frappe.throw(
_("Please enable default incoming account before creating Daily Work Summary Group")
)
def trigger_emails():
"""Send emails to Employees at the given hour asking
them what did they work on today"""
groups = frappe.get_all("Daily Work Summary Group")
for d in groups:
group_doc = frappe.get_doc("Daily Work Summary Group", d)
if (
is_current_hour(group_doc.send_emails_at)
and not is_holiday(group_doc.holiday_list)
and group_doc.enabled
):
emails = get_user_emails_from_group(group_doc)
# find emails relating to a company
if emails:
daily_work_summary = frappe.get_doc(
dict(doctype="Daily Work Summary", daily_work_summary_group=group_doc.name)
).insert()
daily_work_summary.send_mails(group_doc, emails)
def is_current_hour(hour):
return frappe.utils.nowtime().split(":")[0] == hour.split(":")[0]
def send_summary():
"""Send summary to everyone"""
for d in frappe.get_all("Daily Work Summary", dict(status="Open")):
daily_work_summary = frappe.get_doc("Daily Work Summary", d.name)
daily_work_summary.send_summary()
def is_incoming_account_enabled():
return frappe.db.get_value("Email Account", dict(enable_incoming=1, default_incoming=1))

View File

@ -1,106 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-02-12 14:57:38.332692",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "user.email",
"fieldname": "email",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "email",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-05-16 22:43:00.481019",
"modified_by": "Administrator",
"module": "HR",
"name": "Daily Work Summary Group User",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,9 +0,0 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class DailyWorkSummaryGroupUser(Document):
pass

View File

@ -1,76 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
"beta": 0,
"creation": "2018-04-08 16:31:02.433252",
"custom": 0,
"description": "",
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "approver",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Approver",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "200"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-04-17 11:05:46.294340",
"modified_by": "Administrator",
"module": "HR",
"name": "Department Approver",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}

View File

@ -1,93 +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
class DepartmentApprover(Document):
pass
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_approvers(doctype, txt, searchfield, start, page_len, filters):
if not filters.get("employee"):
frappe.throw(_("Please select Employee first."))
approvers = []
department_details = {}
department_list = []
employee = frappe.get_value(
"Employee",
filters.get("employee"),
["employee_name", "department", "leave_approver", "expense_approver", "shift_request_approver"],
as_dict=True,
)
employee_department = filters.get("department") or employee.department
if employee_department:
department_details = frappe.db.get_value(
"Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True
)
if department_details:
department_list = frappe.db.sql(
"""select name from `tabDepartment` where lft <= %s
and rgt >= %s
and disabled=0
order by lft desc""",
(department_details.lft, department_details.rgt),
as_list=True,
)
if filters.get("doctype") == "Leave Application" and employee.leave_approver:
approvers.append(
frappe.db.get_value("User", employee.leave_approver, ["name", "first_name", "last_name"])
)
if filters.get("doctype") == "Expense Claim" and employee.expense_approver:
approvers.append(
frappe.db.get_value("User", employee.expense_approver, ["name", "first_name", "last_name"])
)
if filters.get("doctype") == "Shift Request" and employee.shift_request_approver:
approvers.append(
frappe.db.get_value(
"User", employee.shift_request_approver, ["name", "first_name", "last_name"]
)
)
if filters.get("doctype") == "Leave Application":
parentfield = "leave_approvers"
field_name = "Leave Approver"
elif filters.get("doctype") == "Expense Claim":
parentfield = "expense_approvers"
field_name = "Expense Approver"
elif filters.get("doctype") == "Shift Request":
parentfield = "shift_request_approver"
field_name = "Shift Request Approver"
if department_list:
for d in department_list:
approvers += frappe.db.sql(
"""select user.name, user.first_name, user.last_name from
tabUser user, `tabDepartment Approver` approver where
approver.parent = %s
and user.name like %s
and approver.parentfield = %s
and approver.approver=user.name""",
(d, "%" + txt + "%", parentfield),
as_list=True,
)
if len(approvers) == 0:
error_msg = _("Please set {0} for the Employee: {1}").format(
field_name, frappe.bold(employee.employee_name)
)
if department_list:
error_msg += " " + _("or for Department: {0}").format(frappe.bold(employee_department))
frappe.throw(error_msg, title=_(field_name + " Missing"))
return set(tuple(approver) for approver in approvers)

View File

@ -1,198 +0,0 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:designation_name",
"beta": 0,
"creation": "2013-01-10 16:34:13",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "designation_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Designation",
"length": 0,
"no_copy": 0,
"oldfieldname": "designation_name",
"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,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "description",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "required_skills_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Required Skills",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "skills",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Skills",
"length": 0,
"no_copy": 0,
"options": "Designation Skill",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_toolbar": 0,
"icon": "fa fa-bookmark",
"idx": 1,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2019-04-16 10:02:23.277734",
"modified_by": "Administrator",
"module": "HR",
"name": "Designation",
"owner": "Administrator",
"permissions": [
{
"amend": 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
},
{
"read": 1,
"role": "Sales User"
}
],
"quick_entry": 1,
"read_only": 0,
"show_name_in_global_search": 1,
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@ -1,74 +0,0 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2019-04-16 10:01:05.259881",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "skill",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Skill",
"length": 0,
"no_copy": 0,
"options": "Skill",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_toolbar": 0,
"idx": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-04-16 13:42:10.760449",
"modified_by": "Administrator",
"module": "HR",
"name": "Designation Skill",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

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 DesignationSkill(Document):
pass

Some files were not shown because too many files have changed in this diff Show More