This commit is contained in:
Charles-Henri Decultot
2018-12-10 13:05:46 +00:00
parent 31cb24f48d
commit 818492387a
12 changed files with 289 additions and 80 deletions

View File

@@ -6,7 +6,7 @@ frappe.ui.form.on('Bank Transaction', {
frm.set_query('payment_document', 'payment_entries', function(doc, cdt, cdn) {
return {
"filters": {
"name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice"]]
"name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]]
}
};
});

View File

@@ -13,13 +13,12 @@ class BankTransaction(Document):
self.unallocated_amount = abs(flt(self.credit) - flt(self.debit))
def on_update_after_submit(self):
linked_payments = [x.payment_entry for x in self.payment_entries]
allocated_amount = reduce(lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries])
frappe.log_error(allocated_amount)
if linked_payments:
allocated_amount = frappe.get_all("Payment Entry", filters=[["name", "in", linked_payments]], fields=["sum(paid_amount) as total"])
frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount[0].total))
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)) - flt(allocated_amount[0].total))
if allocated_amount:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount))
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)) - flt(allocated_amount))
else:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)

View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe
import json
from frappe.utils import getdate
from frappe.utils.dateutils import parse_date
@frappe.whitelist()
def upload_bank_statement():
@@ -47,7 +48,7 @@ def create_bank_entries(columns, data, bank_account):
"doctype": "Bank Transaction"
})
bank_transaction.update(fields)
bank_transaction.date = getdate(bank_transaction.date)
bank_transaction.date = getdate(parse_date(bank_transaction.date))
bank_transaction.bank_account = bank_account
bank_transaction.insert()
bank_transaction.submit()

View File

@@ -40,7 +40,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@@ -73,7 +73,39 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 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": "allocated_amount",
"fieldtype": "Currency",
"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": "Allocated Amount",
"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,
@@ -90,7 +122,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-11-28 12:34:41.685571",
"modified": "2018-12-06 10:57:02.635141",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction Payments",

View File

@@ -3401,6 +3401,38 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "clearance_date",
"fieldtype": "Date",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Clearance 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,
@@ -4726,7 +4758,11 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
<<<<<<< 31cb24f48d6d57cd8ab6991622c174390f6c2c51
"modified": "2019-01-07 16:51:59.800081",
=======
"modified": "2018-12-06 09:00:41.508642",
>>>>>>> WIP
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -8,14 +8,14 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
constructor(wrapper) {
this.page = frappe.ui.make_app_page({
parent: wrapper,
title: 'Bank Reconciliation',
title: __("Bank Reconciliation"),
single_column: true
});
this.parent = wrapper;
this.page = this.parent.page;
this.make();
this.add_plaid_btn();
this.make();
}
make() {
@@ -44,9 +44,12 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
const me = this;
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
if (r && r.enabled == "1") {
me.plaid_status = "active"
me.parent.page.add_inner_button(__('Link a new bank account'), function() {
new erpnext.accounts.plaidLink(this)
})
} else {
me.plaid_status = "inactive"
}
})
}
@@ -60,10 +63,14 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
me.clear_page_content();
new erpnext.accounts.bankTransactionUpload(me);
}, true)
me.page.add_action_item(__("Synchronize this account"), function() {
me.clear_page_content();
new erpnext.accounts.bankTransactionSync(me);
}, true)
if (me.plaid_status==="active") {
me.page.add_action_item(__("Synchronize this account"), function() {
me.clear_page_content();
new erpnext.accounts.bankTransactionSync(me);
}, true)
}
me.page.add_action_item(__("Reconcile this account"), function() {
me.clear_page_content();
me.make_reconciliation_tool();
@@ -79,7 +86,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
make_reconciliation_tool() {
const me = this;
frappe.model.with_doctype("Bank Transaction", () => {
new erpnext.accounts.ReconciliationTool({
erpnext.accounts.ReconciliationList = new erpnext.accounts.ReconciliationTool({
parent: me.parent,
doctype: "Bank Transaction"
});
@@ -310,11 +317,13 @@ erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.vi
constructor(opts) {
super(opts);
this.show();
console.log(this)
}
setup_defaults() {
super.setup_defaults();
this.page_title = __("Bank Reconciliation");
this.doctype = 'Bank Transaction';
this.fields = ['date', 'description', 'debit', 'credit', 'currency']
@@ -374,11 +383,6 @@ erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.vi
me.$result.append(frappe.render_template("bank_transaction_header"));
}
}
static trigger_list_update() {
const reconciliation_list = erpnext.accounts.ReconciliationTool;
reconciliation_list && reconciliation_list.on_update();
}
}
erpnext.accounts.ReconciliationRow = class ReconciliationRow {
@@ -400,6 +404,11 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
me.show_dialog($(this).attr("data-name"));
})
$(me.row).on('click', '.new-reconciliation', function() {
me.bank_entry = $(this).attr("data-name");
me.show_dialog($(this).attr("data-name"));
})
$(me.row).on('click', '.new-payment', function() {
me.bank_entry = $(this).attr("data-name");
me.new_payment();
@@ -438,7 +447,6 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
{bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")}
).then((result) => {
console.log(result)
me.make_dialog(result)
})
}
@@ -470,7 +478,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
get_query: () => {
return {
filters : {
"name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice"]]
"name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]]
}
}
}
@@ -504,6 +512,20 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
return {
query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.sales_invoices_query"
}
} else if (dt === "Purchase Invoice") {
return {
filters : [
["Purchase Invoice", "ifnull(clearance_date, '')", "=", ""],
["Purchase Invoice", "docstatus", "=", 1]
]
}
} else if (dt === "Expense Claim") {
return {
filters : [
["Expense Claim", "ifnull(clearance_date, '')", "=", ""],
["Expense Claim", "docstatus", "=", 1]
]
}
}
},
onchange: function() {
@@ -531,6 +553,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
const proposals_wrapper = me.dialog.fields_dict.payment_proposals.$wrapper;
if (data && data.length > 0) {
proposals_wrapper.append(frappe.render_template("linked_payment_header"));
data.map(value => {
proposals_wrapper.append(frappe.render_template("linked_payment_row", value))
})
@@ -543,9 +566,11 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
const payment_entry = $(e.target).attr('data-name');
const payment_doctype = $(e.target).attr('data-doctype');
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile',
{bank_transaction: me.bank_entry, payment_doctype: payment_doctype, payment_entry: payment_entry})
{bank_transaction: me.bank_entry, payment_doctype: payment_doctype, payment_name: payment_entry})
.then((result) => {
erpnext.accounts.ReconciliationTool.trigger_list_update();
setTimeout(function(){
erpnext.accounts.ReconciliationList.refresh();
}, 2000);
me.dialog.hide();
})
})
@@ -592,6 +617,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
}
const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper;
details_wrapper.append(frappe.render_template("linked_payment_header"));
displayed_docs.forEach(values => {
details_wrapper.append(frappe.render_template("linked_payment_row", values));
})

View File

@@ -11,41 +11,77 @@ from frappe.utils import flt
from six import iteritems
@frappe.whitelist()
def reconcile(bank_transaction, payment_doctype, payment_entry):
def reconcile(bank_transaction, payment_doctype, payment_name):
transaction = frappe.get_doc("Bank Transaction", bank_transaction)
payment_entry = frappe.get_doc(payment_doctype, payment_entry)
payment_entry = frappe.get_doc(payment_doctype, payment_name)
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
"""
if transaction.credit > 0 and payment_entry.payment_type == "Pay":
if transaction.credit > 0 and gl_entry.credit > 0:
frappe.throw(_("The selected payment entry should be linked with a debitor bank transaction"))
if transaction.debit > 0 and payment_entry.payment_type == "Receive":
if transaction.debit > 0 and gl_entry.debit > 0:
frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction"))
"""
add_payment_to_transaction(transaction, payment_doctype, payment_entry)
#clear_payment_entry(transaction, payment_doctype, payment_entry)
add_payment_to_transaction(transaction, payment_entry, gl_entry)
clear_payment_entry(transaction, payment_entry, gl_entry)
return 'reconciled'
def add_payment_to_transaction(transaction, payment_doctype, payment_entry):
transaction.append("payment_entries", {"payment_document": payment_doctype, "payment_entry": payment_entry.name})
def add_payment_to_transaction(transaction, payment_entry, gl_entry):
transaction.append("payment_entries", {
"payment_document": payment_entry.doctype,
"payment_entry": payment_entry.name,
"allocated_amount": gl_entry.credit if gl_entry.credit > 0 else gl_entry.debit
})
transaction.save()
def clear_payment_entry(transaction, payment_doctype, payment_entry):
pass
"""
linked_bank_transactions = frappe.get_all("Bank Transaction Payments", filters={"payment_entry": payment_entry, "docstatus": 1},
fields=["sum(debit) as debit", "sum(credit) as credit"])
def clear_payment_entry(transaction, payment_entry, gl_entry):
linked_bank_transactions = frappe.db.sql("""
SELECT
bt.credit, bt.debit
FROM
`tabBank Transaction Payments` as btp
LEFT JOIN
`tabBank Transaction` as bt on btp.parent=bt.name
WHERE
btp.payment_document = '%s'
AND
btp.payment_entry = '%s'
AND
bt.docstatus = 1
""" % (payment_entry.doctype, payment_entry.name), as_dict=True)
cleared_amount = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit))
amount_cleared = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit))
amount_to_be_cleared = (flt(gl_entry.credit) - flt(gl_entry.debit))
if cleared_amount == payment_entry.paid_amount:
frappe.db.set_value(payment_doctype, payment_entry.name, "clearance_date", transaction.date)
"""
if payment_entry.doctype == "Payment Entry":
clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction)
elif payment_entry.doctype == "Journal Entry":
clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction)
elif payment_entry.doctype == "Sales Invoice":
clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, transaction)
elif payment_entry.doctype == "Purchase Invoice":
clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction)
elif payment_entry.doctype == "Expense Claim":
clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction)
def clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction):
if amount_cleared == amount_to_be_cleared:
frappe.db.set_value(payment_entry.doctype, payment_entry.name, "clearance_date", transaction.date)
def clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, transaction):
if amount_cleared == amount_to_be_cleared:
frappe.db.set_value("Sales Invoice Payment", dict(parenttype=payment_entry.doctype,
parent=payment_entry.name), "clearance_date", transaction.date)
@frappe.whitelist()
def get_linked_payments(bank_transaction):
@@ -63,10 +99,11 @@ def get_linked_payments(bank_transaction):
if amount_matching:
return check_amount_vs_description(amount_matching, description_matching)
elif description_matching:
return sorted(description_matching, key = lambda x: x["posting_date"], reverse=True)
else:
print("else")
#linked_payments = get_matching_transactions_payments(description_matching)
#return linked_payments
return []
def check_matching_amount(bank_account, transaction):
payments = []
@@ -147,10 +184,12 @@ def get_matching_descriptions_data(bank_account, transaction):
if bank_transaction.description:
seq=difflib.SequenceMatcher(lambda x: x == " ", transaction.description, bank_transaction.description)
if seq.ratio() > 0.5:
if seq.ratio() > 0.6:
bank_transaction["ratio"] = seq.ratio()
selection.append(bank_transaction)
print(selection)
document_types = set([x["payment_document"] for x in selection])
links = {}
@@ -165,9 +204,11 @@ def get_matching_descriptions_data(bank_account, transaction):
if key == "Journal Entry":
data.extend(frappe.get_all("Journal Entry", filters=[["name", "in", value]], fields=["'Journal Entry' as doctype", "posting_date", "paid_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date"]))
if key == "Sales Invoice":
data.extend(frappe.get_all("Sales Invoice", filters=[["name", "in", value]], fields=["'Sales Invoice' as doctype", "posting_date", "customer as party"]))
#if key == "Purchase Invoice":
# data.append(frappe.get_all("Purchase Invoice", filters=[["name", "in", value]], fields=["posting_date", "customer as party"]))
data.extend(frappe.get_all("Sales Invoice", filters=[["name", "in", value]], fields=["'Sales Invoice' as doctype", "posting_date", "customer_name as party"]))
if key == "Purchase Invoice":
data.append(frappe.get_all("Purchase Invoice", filters=[["name", "in", value]], fields=["'Purchase Invoice' as doctype", "posting_date", "supplier_name as party"]))
if key == "Purchase Invoice":
data.append(frappe.get_all("Expense Claim", filters=[["name", "in", value]], fields=["'Expense Claim' as doctype", "posting_date", "employee_name as party"]))
return data
@@ -178,14 +219,18 @@ def check_amount_vs_description(amount_matching, description_matching):
for am_match in amount_matching:
for des_match in description_matching:
if am_match["party"] == des_match["party"]:
result.append(am_match)
continue
if am_match not in result:
result.append(am_match)
continue
if hasattr(am_match, "reference_no") and hasattr(des_match, "reference_no"):
if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]) > 70:
result.append(am_match)
return sorted(result, key = lambda x: x["posting_date"], reverse=True)
if am_match not in result:
result.append(am_match)
if result:
return sorted(result, key = lambda x: x["posting_date"], reverse=True)
else:
return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True)
else:
return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True)
@@ -222,7 +267,7 @@ def journal_entry_query(doctype, txt, searchfield, start, page_len, filters):
AND
jea.account = %(account)s
AND
jea.parent like %(txt)s
(jea.parent like %(txt)s or je.pay_to_recd_from like %(txt)s)
AND
je.docstatus = 1
ORDER BY
@@ -253,7 +298,7 @@ def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters):
WHERE
(sip.clearance_date is null or sip.clearance_date='0000-00-00')
AND
sip.parent like %(txt)s
(sip.parent like %(txt)s or si.customer like %(txt)s)
ORDER BY
if(locate(%(_txt)s, sip.parent), locate(%(_txt)s, sip.parent), 99999),
sip.parent

View File

@@ -0,0 +1,21 @@
<div class="transaction-header">
<div class="level list-row list-row-head text-muted small">
<div class="col-xs-3 col-sm-2 ellipsis">
{{ __("Payment Name") }}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{{ __("Reference Date") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Amount") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Party") }}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{{ __("Reference Number") }}
</div>
<div class="col-xs-2 col-sm-2">
</div>
</div>
</div>

View File

@@ -1,25 +1,36 @@
<div class="grid-row">
<div class="row">
<div class="col-xs-12">
<label class="control-label">{{ name }}</label>
<div class="list-row">
<div>
<div class="col-xs-3 col-sm-2 ellipsis">
{{ name }}
</div>
<div class="col-xs-5 ellipsis hidden-xs">
<h4>{{ __("Date") }}</h4><h6> {%= frappe.datetime.str_to_user(posting_date) %}</h6>
<div class="col-xs-3 col-sm-2 ellipsis">
{% if (typeof reference_date !== "undefined") %}
<h4>{{ __("Reference Date") }}</h4><h6>{%= frappe.datetime.str_to_user(reference_date) %}</h6>
{%= frappe.datetime.str_to_user(reference_date) %}
{% else %}
{% if (typeof posting_date !== "undefined") %}
{%= frappe.datetime.str_to_user(posting_date) %}
{% endif %}
{% endif %}
</div>
<div class="col-xs-7 ellipsis list-subject">
<h4>{{ __("Amount") }}</h4><h6>{{ format_currency(paid_amount, currency) }}</h6>
<div class="col-sm-2 ellipsis hidden-xs">
{{ format_currency(paid_amount, currency) }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{% if (typeof party !== "undefined") %}
<h4>{{ __("Party") }}</h4><h6>{{ party }}</h6>
{% endif %}
{% if (typeof reference_no !== "undefined") %}
<h4>{{ __("Reference") }}</h4><h6>{{ reference_no }}</h6>
{{ party }}
{% endif %}
</div>
<div class="text-right margin-bottom">
<button class="btn btn-primary btn-xs reconciliation-btn" data-doctype={{ doctype }} data-name={{ name }}>{{ __("Reconcile") }}</button>
<div class="col-xs-3 col-sm-2 ellipsis">
{% if (typeof reference_no !== "undefined") %}
{{ reference_no }}
{% else %}
{{ "" }}
{% endif %}
</div>
<div class="col-xs-2 col-sm-2">
<div class="text-right margin-bottom">
<button class="btn btn-primary btn-xs reconciliation-btn" data-doctype="{{ doctype }}" data-name="{{ name }}">{{ __("Reconcile") }}</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -35,6 +35,11 @@ def get_data():
"type": "doctype",
"name": "Amazon MWS Settings",
"description": _("Connect Amazon with ERPNext"),
},
{
"type": "doctype",
"name": "Plaid Settings",
"description": _("Connect your bank accounts to ERPNext"),
}
]
}

View File

@@ -52,7 +52,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.enabled==1",
"depends_on": "eval:(doc.enabled==1)&&(!doc.__islocal)",
"fieldname": "connect_btn",
"fieldtype": "Button",
"hidden": 0,
@@ -89,7 +89,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-11-15 17:37:48.531027",
"modified": "2018-12-07 10:28:10.837885",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Plaid Settings",

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
@@ -843,6 +844,38 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "clearance_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": "Clearance 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,
@@ -1153,7 +1186,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-08-21 14:44:42.340662",
"modified": "2018-12-06 09:43:25.056554",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",