[Enhancement] Booking of an employee's expense claim (#7267)
* [Enhancement] Auto book of an employee's expense claim
* test cases
* documentation
* fixes and changes
* removed payable from employee and added into the company
* Added party in account, patch to update party type for customer and supplier accounts
* added party type in erpnext, fix patch
* fixed travis
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index c98e77f..fc261a5 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -86,9 +86,9 @@
};
});
- me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
- return {
- filters: {"name": ["in", ["Customer", "Supplier"]]}
+ me.frm.set_query("party_type", "accounts", function() {
+ return{
+ query: "erpnext.setup.doctype.party_type.party_type.get_party_type"
}
});
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index a176680..a13cc7d 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -9,6 +9,7 @@
from erpnext.accounts.utils import get_balance_on, get_account_currency
from erpnext.setup.utils import get_company_currency
from erpnext.accounts.party import get_party_account
+from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
class JournalEntry(AccountsController):
def __init__(self, arg1, arg2=None):
@@ -375,7 +376,7 @@
bank_amount = party_amount = total_amount = 0.0
currency = bank_account_currency = party_account_currency = pay_to_recd_from= None
for d in self.get('accounts'):
- if d.party_type and d.party:
+ if d.party_type in ['Customer', 'Supplier'] and d.party:
if not pay_to_recd_from:
pay_to_recd_from = frappe.db.get_value(d.party_type, d.party,
"customer_name" if d.party_type=="Customer" else "supplier_name")
@@ -503,11 +504,9 @@
def update_expense_claim(self):
for d in self.accounts:
- if d.reference_type=="Expense Claim":
- amt = frappe.db.sql("""select sum(debit) as amt from `tabJournal Entry Account`
- where reference_type = "Expense Claim" and
- reference_name = %s and docstatus = 1""", d.reference_name ,as_dict=1)[0].amt
- frappe.db.set_value("Expense Claim", d.reference_name , "total_amount_reimbursed", amt)
+ if d.reference_type=="Expense Claim" and d.party:
+ doc = frappe.get_doc("Expense Claim", d.reference_name)
+ update_reimbursed_amount(doc)
def validate_expense_claim(self):
for d in self.accounts:
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index d3dbd31..6528c8f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -26,6 +26,12 @@
}
});
+ frm.set_query("party_type", function() {
+ return{
+ query: "erpnext.setup.doctype.party_type.party_type.get_party_type"
+ }
+ });
+
frm.set_query("paid_to", function() {
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
["Bank", "Cash"] : party_account_type;
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index c56a83d..82040fa 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -104,7 +104,7 @@
"columns": 0,
"depends_on": "eval:in_list([\"Receive\", \"Pay\"], doc.payment_type)",
"fieldname": "party_type",
- "fieldtype": "Select",
+ "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -114,7 +114,7 @@
"label": "Party Type",
"length": 0,
"no_copy": 0,
- "options": "Customer\nSupplier",
+ "options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 1,
@@ -1561,7 +1561,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-11-07 05:33:40.371480",
+ "modified": "2016-12-26 14:32:36.900576",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index 6a951a0..272e474 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -6,12 +6,10 @@
erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
onload: function() {
var me = this
- this.frm.set_query('party_type', function() {
- return {
- filters: {
- "name": ["in", ["Customer", "Supplier"]]
- }
- };
+ this.frm.set_query("party_type", function() {
+ return{
+ query: "erpnext.setup.doctype.party_type.party_type.get_party_type"
+ }
});
this.frm.set_query('receivable_payable_account', function() {
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 05a3c11..a8d166e 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -171,13 +171,13 @@
account = frappe.db.get_value("Party Account",
{"parenttype": party_type, "parent": party, "company": company}, "account")
- if not account:
+ if not account and party_type in ['Customer', 'Supplier']:
party_group_doctype = "Customer Group" if party_type=="Customer" else "Supplier Type"
group = frappe.db.get_value(party_type, party, scrub(party_group_doctype))
account = frappe.db.get_value("Party Account",
{"parenttype": party_group_doctype, "parent": group, "company": company}, "account")
- if not account:
+ if not account and party_type in ['Customer', 'Supplier']:
default_account_name = "default_receivable_account" \
if party_type=="Customer" else "default_payable_account"
account = frappe.db.get_value("Company", company, default_account_name)
@@ -249,7 +249,7 @@
if existing_gle_currency and party_account_currency != existing_gle_currency:
frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company))
- if doc.default_currency and party_account_currency and company_default_currency:
+ if doc.get("default_currency") and party_account_currency and company_default_currency:
if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:
frappe.throw(_("Billing currency must be equal to either default comapany's currency or party account currency"))
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index ab7ad49..a422871 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -59,8 +59,8 @@
{
"fieldname":"party_type",
"label": __("Party Type"),
- "fieldtype": "Select",
- "options": ["", "Customer", "Supplier"],
+ "fieldtype": "Link",
+ "options": "Party Type",
"default": ""
},
{
diff --git a/erpnext/accounts/report/unpaid_expense_claim/__init__.py b/erpnext/accounts/report/unpaid_expense_claim/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/unpaid_expense_claim/__init__.py
diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js
new file mode 100644
index 0000000..811414a
--- /dev/null
+++ b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js
@@ -0,0 +1,12 @@
+// 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"
+ }
+ ]
+}
diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.json b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.json
new file mode 100644
index 0000000..a1087c0
--- /dev/null
+++ b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.json
@@ -0,0 +1,18 @@
+{
+ "add_total_row": 0,
+ "apply_user_permissions": 1,
+ "creation": "2017-01-04 16:26:18.309717",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2017-01-04 16:26:18.309717",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Unpaid Expense Claim",
+ "owner": "Administrator",
+ "ref_doctype": "Expense Claim",
+ "report_name": "Unpaid Expense Claim",
+ "report_type": "Script Report"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py
new file mode 100644
index 0000000..eee620b
--- /dev/null
+++ b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ 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)
diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py
index e18a123..2536602 100644
--- a/erpnext/demo/user/hr.py
+++ b/erpnext/demo/user/hr.py
@@ -6,6 +6,7 @@
from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet
from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
from frappe.utils.make_random import get_random
+from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account
from erpnext.hr.doctype.expense_claim.expense_claim import get_expense_approver, make_bank_entry
from erpnext.hr.doctype.leave_application.leave_application import (get_leave_balance_on,
OverlapError, AttendanceAlreadyMarkedError)
@@ -50,6 +51,7 @@
expense_claim.extend('expenses', get_expenses())
expense_claim.employee = get_random("Employee")
expense_claim.company = frappe.flags.company
+ expense_claim.payable_account = get_payable_account(expense_claim.company)
expense_claim.posting_date = frappe.flags.current_date
expense_claim.exp_approver = filter((lambda x: x[0] != 'Administrator'), get_expense_approver(None, '', None, 0, 20, None))[0][0]
expense_claim.insert()
diff --git a/erpnext/docs/assets/img/human-resources/employee_account.png b/erpnext/docs/assets/img/human-resources/employee_account.png
new file mode 100644
index 0000000..a9a6066
--- /dev/null
+++ b/erpnext/docs/assets/img/human-resources/employee_account.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/expense_claim.png b/erpnext/docs/assets/img/human-resources/expense_claim.png
index ddca100..9647d3d 100644
--- a/erpnext/docs/assets/img/human-resources/expense_claim.png
+++ b/erpnext/docs/assets/img/human-resources/expense_claim.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/expense_claim_book.png b/erpnext/docs/assets/img/human-resources/expense_claim_book.png
new file mode 100644
index 0000000..4b81604
--- /dev/null
+++ b/erpnext/docs/assets/img/human-resources/expense_claim_book.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/payment.png b/erpnext/docs/assets/img/human-resources/payment.png
new file mode 100644
index 0000000..f6b6e5d
--- /dev/null
+++ b/erpnext/docs/assets/img/human-resources/payment.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/payment_entry.png b/erpnext/docs/assets/img/human-resources/payment_entry.png
new file mode 100644
index 0000000..a499ccf
--- /dev/null
+++ b/erpnext/docs/assets/img/human-resources/payment_entry.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/unclaimed_expense_claims.png b/erpnext/docs/assets/img/human-resources/unclaimed_expense_claims.png
new file mode 100644
index 0000000..1581bb7
--- /dev/null
+++ b/erpnext/docs/assets/img/human-resources/unclaimed_expense_claims.png
Binary files differ
diff --git a/erpnext/docs/user/manual/en/human-resources/expense-claim.md b/erpnext/docs/user/manual/en/human-resources/expense-claim.md
index da3b2ee..1d4996d 100644
--- a/erpnext/docs/user/manual/en/human-resources/expense-claim.md
+++ b/erpnext/docs/user/manual/en/human-resources/expense-claim.md
@@ -9,6 +9,10 @@
Set the Employee ID, date and the list of expenses that are to be claimed and
“Submit” the record.
+### Set Account for Employee
+Set employee's expense account on the employee form, system books an expense amount of an employee under this account.
+<img class="screenshot" alt="Expense Claim" src="{{docs_base_url}}/assets/img/human-resources/employee_account.png">
+
### Approving Expenses
Approver for the Expense Claim is selected by an Employee himself. Users to whom `Expense Approver` role is assigned will shown in the Expense Claim Approver field.
@@ -18,11 +22,25 @@
Expense Claim Approver can update the “Sanctioned Amounts” against Claimed Amount of an Employee. If submitting, Approval Status should be submitted to Approved or Rejected. If Approved, then Expense Claim gets submitted. If rejected, then Expen
Comments can be added in the Comments section explaining why the claim was approved or rejected.
-### Booking the Expense and Reimbursement
+### Booking the Expense
-The approved Expense Claim must then be converted into a Journal Entry and a
-payment must be made. Note: This amount should not be clubbed with Salary
-because the amount will then be taxable to the Employee.
+On submission of Expense Claim, system books an expense against the expense account and the employee account
+<img class="screenshot" alt="Expense Claim" src="{{docs_base_url}}/assets/img/human-resources/expense_claim_book.png">
+
+User can view unpaid expense claim using report "Unclaimed Expense Claims"
+<img class="screenshot" alt="Expense Claim" src="{{docs_base_url}}/assets/img/human-resources/unclaimed_expense_claims.png">
+
+### Payment for Expense Claim
+
+To make payment against the expense claim, user has to click on Make > Bank Entry
+#### Expense Claim
+<img class="screenshot" alt="Expense Claim" src="{{docs_base_url}}/assets/img/human-resources/payment.png">
+
+#### Payment Entry
+<img class="screenshot" alt="Expense Claim" src="{{docs_base_url}}/assets/img/human-resources/payment_entry.png">
+
+
+Note: This amount should not be clubbed with Salary because the amount will then be taxable to the Employee.
### Linking with Task & Project
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index 8b3c451..7d19051 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -158,6 +158,21 @@
}
}
+frappe.ui.form.on("Expense Claim", {
+ refresh: function(frm) {
+ if(frm.doc.docstatus == 1) {
+ frm.add_custom_button(__('Accounting Ledger'), function() {
+ frappe.route_options = {
+ voucher_no: frm.doc.name,
+ company: frm.doc.company,
+ group_by_voucher: false
+ };
+ frappe.set_route("query-report", "General Ledger");
+ }, __("View"));
+ }
+ }
+})
+
frappe.ui.form.on("Expense Claim Detail", {
claim_amount: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
@@ -176,6 +191,47 @@
}
})
+frappe.ui.form.on("Expense Claim",{
+ setup: function(frm) {
+ frm.trigger("set_query_for_cost_center")
+ frm.trigger("set_query_for_payable_account")
+ frm.add_fetch("company", "cost_center", "cost_center");
+ frm.add_fetch("company", "default_payable_account", "payable_account");
+ },
+
+ refresh: function(frm) {
+ frm.trigger("toggle_fields")
+ },
+
+ set_query_for_cost_center: function(frm) {
+ frm.fields_dict["cost_center"].get_query = function() {
+ return {
+ filters: {
+ "company": frm.doc.company
+ }
+ }
+ }
+ },
+
+ set_query_for_payable_account: function(frm) {
+ frm.fields_dict["payable_account"].get_query = function() {
+ return {
+ filters: {
+ "root_type": "Liability",
+ "account_type": "Payable"
+ }
+ }
+ }
+ },
+
+ is_paid: function(frm) {
+ frm.trigger("toggle_fields")
+ },
+
+ toggle_fields: function(frm) {
+ frm.toggle_reqd("mode_of_payment", frm.doc.is_paid)
+ }
+});
frappe.ui.form.on("Expense Claim", "employee_name", function(frm) {
erpnext.expense_claim.set_title(frm);
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index 67132bf..f517b91 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -46,6 +46,34 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "is_paid",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Is Paid",
+ "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_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"default": "Draft",
"depends_on": "eval:!doc.__islocal",
"fieldname": "approval_status",
@@ -55,7 +83,7 @@
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 0,
- "in_standard_filter": 1,
+ "in_standard_filter": 0,
"label": "Approval Status",
"length": 0,
"no_copy": 1,
@@ -86,7 +114,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "in_standard_filter": 1,
+ "in_standard_filter": 0,
"label": "Approver",
"length": 0,
"no_copy": 0,
@@ -175,7 +203,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
- "in_list_view": 0,
+ "in_list_view": 1,
"in_standard_filter": 0,
"label": "Total Sanctioned Amount",
"length": 0,
@@ -322,7 +350,7 @@
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 0,
- "in_standard_filter": 1,
+ "in_standard_filter": 0,
"label": "From Employee",
"length": 0,
"no_copy": 0,
@@ -375,36 +403,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "company",
- "oldfieldtype": "Link",
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "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": "vehicle_log",
"fieldtype": "Link",
"hidden": 0,
@@ -434,90 +432,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "cb1",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "total_amount_reimbursed",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Total Amount Reimbursed",
- "length": 0,
- "no_copy": 1,
- "options": "Company:company:default_currency",
- "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "remark",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Remark",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "remark",
- "oldfieldtype": "Small Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "project",
"fieldtype": "Link",
"hidden": 0,
@@ -572,6 +486,90 @@
"unique": 0
},
{
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "cb1",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "total_amount_reimbursed",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Total Amount Reimbursed",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "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_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "remark",
+ "fieldtype": "Small Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Remark",
+ "length": 0,
+ "no_copy": 1,
+ "oldfieldname": "remark",
+ "oldfieldtype": "Small Text",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -613,7 +611,7 @@
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Employees Email Address",
+ "label": "Employees Email Id",
"length": 0,
"no_copy": 0,
"oldfieldname": "email_id",
@@ -634,6 +632,238 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "accounting_details",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Accounting Details",
+ "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_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Company",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "company",
+ "oldfieldtype": "Link",
+ "options": "Company",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 1,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "is_paid",
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Mode of Payment",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Mode of Payment",
+ "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_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_24",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 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,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "",
+ "fieldname": "payable_account",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Payable Account",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Account",
+ "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_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Employees Email Address",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cost Center",
+ "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_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "columns": 0,
+ "fieldname": "more_details",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "More Details",
+ "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_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Status",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Draft\nPaid\nUnpaid\nSubmitted\nCancelled",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -663,7 +893,7 @@
],
"hide_heading": 0,
"hide_toolbar": 0,
- "icon": "fa fa-money",
+ "icon": "icon-money",
"idx": 1,
"image_view": 0,
"in_create": 0,
@@ -673,10 +903,11 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2016-11-07 05:52:48.548201",
+ "modified": "2017-01-19 18:15:35.968292",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
+ "name_case": "Title Case",
"owner": "harshada@webnotestech.com",
"permissions": [
{
@@ -689,7 +920,6 @@
"export": 1,
"if_owner": 0,
"import": 0,
- "is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -710,7 +940,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
- "is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -732,7 +961,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
- "is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -754,7 +982,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
- "is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -775,5 +1002,6 @@
"sort_order": "DESC",
"timeline_field": "employee",
"title_field": "title",
+ "track_changes": 0,
"track_seen": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index efdee97..957753a 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -4,13 +4,17 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import get_fullname, flt
+from frappe.utils import get_fullname, flt, cstr
from frappe.model.document import Document
from erpnext.hr.utils import set_employee_name
+from erpnext.accounts.party import get_party_account
+from erpnext.accounts.general_ledger import make_gl_entries
+from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
+from erpnext.controllers.accounts_controller import AccountsController
class InvalidExpenseApproverError(frappe.ValidationError): pass
-class ExpenseClaim(Document):
+class ExpenseClaim(AccountsController):
def get_feed(self):
return _("{0}: From {0} for {1}").format(self.approval_status,
self.employee_name, self.total_claimed_amount)
@@ -21,16 +25,53 @@
self.calculate_total_amount()
set_employee_name(self)
self.set_expense_account()
+ self.set_payable_account()
+ self.set_cost_center()
+ self.set_status()
if self.task and not self.project:
self.project = frappe.db.get_value("Task", self.task, "project")
+ def set_status(self):
+ self.status = {
+ "0": "Draft",
+ "1": "Submitted",
+ "2": "Cancelled"
+ }[cstr(self.docstatus or 0)]
+
+ if self.total_sanctioned_amount == self.total_amount_reimbursed and self.docstatus == 1:
+ self.status = "Paid"
+ elif self.docstatus == 1:
+ self.status = "Unpaid"
+
+ def set_payable_account(self):
+ if not self.payable_account and not self.is_paid:
+ self.payable_account = frappe.db.get_value("Company", self.company, "default_payable_account")
+
+ def set_cost_center(self):
+ if not self.cost_center:
+ self.cost_center = frappe.db.get_value('Company', self.company, 'cost_center')
+
def on_submit(self):
if self.approval_status=="Draft":
frappe.throw(_("""Approval Status must be 'Approved' or 'Rejected'"""))
+
self.update_task_and_project()
+ self.make_gl_entries()
+
+ if self.is_paid:
+ update_reimbursed_amount(self)
+
+ self.set_status()
def on_cancel(self):
self.update_task_and_project()
+ if self.payable_account:
+ self.make_gl_entries(cancel=True)
+
+ if self.is_paid:
+ update_reimbursed_amount(self)
+
+ self.set_status()
def update_task_and_project(self):
if self.task:
@@ -38,6 +79,79 @@
elif self.project:
frappe.get_doc("Project", self.project).update_project()
+ def make_gl_entries(self, cancel = False):
+ if flt(self.total_sanctioned_amount) > 0:
+ gl_entries = self.get_gl_entries()
+ make_gl_entries(gl_entries, cancel)
+
+ def get_gl_entries(self):
+ gl_entry = []
+ self.validate_account_details()
+
+ # payable entry
+ gl_entry.append(
+ self.get_gl_dict({
+ "account": self.payable_account,
+ "credit": self.total_sanctioned_amount,
+ "credit_in_account_currency": self.total_sanctioned_amount,
+ "against": ",".join([d.default_account for d in self.expenses]),
+ "party_type": "Employee",
+ "party": self.employee,
+ "against_voucher_type": self.doctype,
+ "against_voucher": self.name
+ })
+ )
+
+ # expense entries
+ for data in self.expenses:
+ gl_entry.append(
+ self.get_gl_dict({
+ "account": data.default_account,
+ "debit": data.sanctioned_amount,
+ "debit_in_account_currency": data.sanctioned_amount,
+ "against": self.employee,
+ "cost_center": self.cost_center
+ })
+ )
+
+ if self.is_paid:
+ # payment entry
+ payment_account = get_bank_cash_account(self.mode_of_payment, self.company).get("account")
+ gl_entry.append(
+ self.get_gl_dict({
+ "account": payment_account,
+ "credit": self.total_sanctioned_amount,
+ "credit_in_account_currency": self.total_sanctioned_amount,
+ "against": self.employee
+ })
+ )
+
+ gl_entry.append(
+ self.get_gl_dict({
+ "account": self.payable_account,
+ "party_type": "Employee",
+ "party": self.employee,
+ "against": payment_account,
+ "debit": self.total_sanctioned_amount,
+ "debit_in_account_currency": self.total_sanctioned_amount,
+ "against_voucher": self.name,
+ "against_voucher_type": self.doctype,
+ })
+ )
+
+ return gl_entry
+
+ def validate_account_details(self):
+ if not self.cost_center:
+ frappe.throw(_("Cost center is required to book an expense claim"))
+
+ if not self.payable_account:
+ frappe.throw(_("Please set default payable account in the employee {0}").format(self.employee))
+
+ if self.is_paid:
+ if not self.mode_of_payment:
+ frappe.throw(_("Mode of payment is required to make a payment").format(self.employee))
+
def calculate_total_amount(self):
self.total_claimed_amount = 0
self.total_sanctioned_amount = 0
@@ -64,7 +178,18 @@
for expense in self.expenses:
if not expense.default_account:
expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"]
-
+
+def update_reimbursed_amount(doc):
+ amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) as amt
+ from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s
+ and party = %s """, (doc.name, doc.employee) ,as_dict=1)[0].amt
+
+ doc.total_amount_reimbursed = amt
+ frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", amt)
+
+ doc.set_status()
+ frappe.db.set_value("Expense Claim", doc.name , "status", doc.status)
+
@frappe.whitelist()
def get_expense_approver(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""
@@ -80,23 +205,26 @@
expense_claim = frappe.get_doc("Expense Claim", docname)
default_bank_cash_account = get_default_bank_cash_account(expense_claim.company, "Bank")
+ if not default_bank_cash_account:
+ default_bank_cash_account = get_default_bank_cash_account(expense_claim.company, "Cash")
je = frappe.new_doc("Journal Entry")
je.voucher_type = 'Bank Entry'
je.company = expense_claim.company
je.remark = 'Payment against Expense Claim: ' + docname;
- for expense in expense_claim.expenses:
- je.append("accounts", {
- "account": expense.default_account,
- "debit_in_account_currency": expense.sanctioned_amount,
- "reference_type": "Expense Claim",
- "reference_name": expense_claim.name
- })
+ je.append("accounts", {
+ "account": expense_claim.payable_account,
+ "debit_in_account_currency": flt(expense_claim.total_sanctioned_amount - expense_claim.total_amount_reimbursed),
+ "reference_type": "Expense Claim",
+ "party_type": "Employee",
+ "party": expense_claim.employee,
+ "reference_name": expense_claim.name
+ })
je.append("accounts", {
"account": default_bank_cash_account.account,
- "credit_in_account_currency": expense_claim.total_sanctioned_amount,
+ "credit_in_account_currency": flt(expense_claim.total_sanctioned_amount - expense_claim.total_amount_reimbursed),
"reference_type": "Expense Claim",
"reference_name": expense_claim.name,
"balance": default_bank_cash_account.balance,
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_list.js b/erpnext/hr/doctype/expense_claim/expense_claim_list.js
index 54073ed..7cff8e2 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim_list.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim_list.js
@@ -2,7 +2,10 @@
add_fields: ["approval_status", "total_claimed_amount", "docstatus"],
filters:[["approval_status","!=", "Rejected"]],
get_indicator: function(doc) {
- return [__(doc.approval_status), frappe.utils.guess_colour(doc.approval_status),
- "approval_status,=," + doc.approval_status];
+ if(doc.status == "Paid") {
+ return [__("Paid"), "green", "status,=,'Paid'"];
+ } else {
+ return [__("Unpaid"), "orange"];
+ }
}
};
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 59d686e..20df7be 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -4,6 +4,8 @@
import frappe
import unittest
+from frappe.utils import random_string, nowdate
+from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
test_records = frappe.get_test_records('Expense Claim')
@@ -11,8 +13,6 @@
def test_total_expense_claim_for_project(self):
frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """)
frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """)
- frappe.db.sql("""delete from `tabExpense Claim`""")
- frappe.db.sql("""delete from `tabExpense Claim Detail`""")
frappe.get_doc({
"project_name": "_Test Project 1",
@@ -22,15 +22,17 @@
}).save()
task_name = frappe.db.get_value("Task", {"project": "_Test Project 1"})
+ payable_account = get_payable_account("Wind Power LLC")
expense_claim = frappe.get_doc({
"doctype": "Expense Claim",
"employee": "_T-Employee-0001",
+ "payable_account": payable_account,
"approval_status": "Approved",
"project": "_Test Project 1",
"task": task_name,
"expenses":
- [{ "expense_type": "Food", "default_account": "Entertainment Expenses - _TC", "claim_amount": 300, "sanctioned_amount": 200 }]
+ [{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "claim_amount": 300, "sanctioned_amount": 200 }]
})
expense_claim.submit()
@@ -44,7 +46,7 @@
"project": "_Test Project 1",
"task": task_name,
"expenses":
- [{ "expense_type": "Food", "default_account": "Entertainment Expenses - _TC", "claim_amount": 600, "sanctioned_amount": 500 }]
+ [{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "claim_amount": 600, "sanctioned_amount": 500 }]
})
expense_claim2.submit()
@@ -52,6 +54,64 @@
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700)
expense_claim2.cancel()
+ frappe.delete_doc("Expenses Claim", expense_claim2.name)
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)
+
+ def test_expense_claim_status(self):
+ payable_account = get_payable_account("Wind Power LLC")
+ expense_claim = frappe.get_doc({
+ "doctype": "Expense Claim",
+ "employee": "_T-Employee-0001",
+ "payable_account": payable_account,
+ "approval_status": "Approved",
+ "expenses":
+ [{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "claim_amount": 300, "sanctioned_amount": 200 }]
+ })
+ expense_claim.submit()
+
+ je_dict = make_bank_entry(expense_claim.name)
+ je = frappe.get_doc(je_dict)
+ je.posting_date = nowdate()
+ je.cheque_no = random_string(5)
+ je.cheque_date = nowdate()
+ je.submit()
+
+ expense_claim = frappe.get_doc("Expense Claim", expense_claim.name)
+ self.assertEqual(expense_claim.status, "Paid")
+
+ je.cancel()
+ expense_claim = frappe.get_doc("Expense Claim", expense_claim.name)
+ self.assertEqual(expense_claim.status, "Unpaid")
+
+ def test_expense_claim_gl_entry(self):
+ payable_account = get_payable_account("Wind Power LLC")
+ expense_claim = frappe.get_doc({
+ "doctype": "Expense Claim",
+ "employee": "_T-Employee-0001",
+ "payable_account": payable_account,
+ "approval_status": "Approved",
+ "expenses":
+ [{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "claim_amount": 300, "sanctioned_amount": 200 }]
+ })
+ expense_claim.submit()
+
+ gl_entries = frappe.db.sql("""select account, debit, credit
+ from `tabGL Entry` where voucher_type='Expense Claim' and voucher_no=%s
+ order by account asc""", expense_claim.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ expected_values = dict((d[0], d) for d in [
+ [payable_account, 0.0, 200.0],
+ ["Travel Expenses - WP", 200.0, 0.0]
+ ])
+
+ for gle in gl_entries:
+ self.assertEquals(expected_values[gle.account][0], gle.account)
+ self.assertEquals(expected_values[gle.account][1], gle.debit)
+ self.assertEquals(expected_values[gle.account][2], gle.credit)
+
+def get_payable_account(company):
+ return frappe.db.get_value('Company', company, 'default_payable_account')
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 87c070e..9494fbd 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -369,3 +369,4 @@
erpnext.patches.v7_2.set_null_value_to_fields
erpnext.patches.v7_2.update_guardian_name_in_student_master
erpnext.patches.v7_2.update_abbr_in_salary_slips
+erpnext.patches.v7_2.update_party_type
diff --git a/erpnext/patches/v7_2/__init__.py b/erpnext/patches/v7_2/__init__.py
index e69de29..baffc48 100644
--- a/erpnext/patches/v7_2/__init__.py
+++ b/erpnext/patches/v7_2/__init__.py
@@ -0,0 +1 @@
+from __future__ import unicode_literals
diff --git a/erpnext/patches/v7_2/update_party_type.py b/erpnext/patches/v7_2/update_party_type.py
new file mode 100644
index 0000000..147f5a3
--- /dev/null
+++ b/erpnext/patches/v7_2/update_party_type.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc('setup', 'doctype', 'party_type')
+ make_party_type()
+
+def make_party_type():
+ for party_type in ["Customer", "Supplier", "Employee"]:
+ if not frappe.db.get_value("Party Type", party_type):
+ doc = frappe.new_doc("Party Type")
+ doc.party_type = party_type
+ doc.save(ignore_permissions=True)
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index f22e63e..5dd8ceb 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -4,6 +4,17 @@
frappe.provide("erpnext.company");
frappe.ui.form.on("Company", {
+ setup: function(frm) {
+ frm.fields_dict['default_payable_account'].get_query = function() {
+ return{
+ filters: {
+ "root_type": "Liability",
+ "account_type": "Payable"
+ }
+ }
+ }
+ },
+
onload: function(frm) {
erpnext.company.setup_queries(frm);
},
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 32ad8eb..93f02f8 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -153,6 +153,8 @@
self.db_set("default_income_account", frappe.db.get_value("Account",
{"account_name": _("Sales"), "company": self.name}))
+ if not self.default_payable_account:
+ self.db_set("default_payable_account", self.default_payable_account)
def _set_default_account(self, fieldname, account_type):
if self.get(fieldname):
diff --git a/erpnext/setup/doctype/party_type/__init__.py b/erpnext/setup/doctype/party_type/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/setup/doctype/party_type/__init__.py
diff --git a/erpnext/setup/doctype/party_type/party_type.js b/erpnext/setup/doctype/party_type/party_type.js
new file mode 100644
index 0000000..7a96dc5
--- /dev/null
+++ b/erpnext/setup/doctype/party_type/party_type.js
@@ -0,0 +1,15 @@
+// Copyright (c) 2016, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Party Type', {
+ setup: function(frm) {
+ frm.fields_dict["party_type"].get_query = function(frm) {
+ return {
+ filters: {
+ "istable": 0,
+ "is_submittable": 0
+ }
+ }
+ }
+ }
+});
diff --git a/erpnext/setup/doctype/party_type/party_type.json b/erpnext/setup/doctype/party_type/party_type.json
new file mode 100644
index 0000000..f49e7b3
--- /dev/null
+++ b/erpnext/setup/doctype/party_type/party_type.json
@@ -0,0 +1,130 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "field:party_type",
+ "beta": 0,
+ "creation": "2016-12-26 11:26:51.508286",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "party_type",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Party Type",
+ "length": 0,
+ "no_copy": 0,
+ "options": "DocType",
+ "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
+ }
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-01-19 17:55:33.428335",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Party Type",
+ "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": "System Manager",
+ "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": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts 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": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/party_type/party_type.py b/erpnext/setup/doctype/party_type/party_type.py
new file mode 100644
index 0000000..74db037
--- /dev/null
+++ b/erpnext/setup/doctype/party_type/party_type.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class PartyType(Document):
+ pass
+
+@frappe.whitelist()
+def get_party_type(doctype, txt, searchfield, start, page_len, filters):
+ return frappe.db.sql("""select name from `tabParty Type`
+ where `{key}` LIKE %(txt)s
+ order by name limit %(start)s, %(page_len)s"""
+ .format(key=searchfield), {
+ 'txt': "%%%s%%" % frappe.db.escape(txt),
+ 'start': start, 'page_len': page_len
+ })
diff --git a/erpnext/setup/doctype/party_type/test_party_type.py b/erpnext/setup/doctype/party_type/test_party_type.py
new file mode 100644
index 0000000..079fe2f
--- /dev/null
+++ b/erpnext/setup/doctype/party_type/test_party_type.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+# test_records = frappe.get_test_records('Party Type')
+
+class TestPartyType(unittest.TestCase):
+ pass
diff --git a/erpnext/setup/setup_wizard/install_fixtures.py b/erpnext/setup/setup_wizard/install_fixtures.py
index 1589b3b..5e08203 100644
--- a/erpnext/setup/setup_wizard/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/install_fixtures.py
@@ -171,6 +171,10 @@
{'doctype': "Email Account", "email_id": "support@example.com", "append_to": "Issue"},
{'doctype': "Email Account", "email_id": "jobs@example.com", "append_to": "Job Applicant"},
+ {'doctype': "Party Type", "party_type": "Customer"},
+ {'doctype': "Party Type", "party_type": "Supplier"},
+ {'doctype': "Party Type", "party_type": "Employee"},
+
{"doctype": "Offer Term", "offer_term": _("Date of Joining")},
{"doctype": "Offer Term", "offer_term": _("Annual Salary")},
{"doctype": "Offer Term", "offer_term": _("Probationary Period")},