Bank reconciliation wip
This commit is contained in:
@@ -151,7 +151,7 @@
|
|||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "plaid_access_token",
|
"fieldname": "plaid_access_token",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Plaid Access Token",
|
"label": "Plaid Access Token",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 1,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-10-25 15:20:38.837772",
|
"modified": "2018-11-27 16:12:13.938776",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bank",
|
"name": "Bank",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"allow_guest_to_view": 0,
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:account_name",
|
"autoname": "",
|
||||||
"beta": 0,
|
"beta": 0,
|
||||||
"creation": "2017-05-29 21:35:13.136357",
|
"creation": "2017-05-29 21:35:13.136357",
|
||||||
"custom": 0,
|
"custom": 0,
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 1
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
@@ -830,7 +830,7 @@
|
|||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "integration_id",
|
"fieldname": "integration_id",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
@@ -839,7 +839,7 @@
|
|||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Integration ID",
|
"label": "Integration ID",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 1,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
@@ -860,6 +860,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"description": "Change this date manually to setup the next synchronization start date",
|
||||||
"fieldname": "last_integration_date",
|
"fieldname": "last_integration_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -959,7 +960,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-11-15 17:37:10.340070",
|
"modified": "2018-11-27 16:32:17.612257",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bank Account",
|
"name": "Bank Account",
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ class BankAccount(Document):
|
|||||||
"""Load address and contacts in `__onload`"""
|
"""Load address and contacts in `__onload`"""
|
||||||
load_address_and_contact(self)
|
load_address_and_contact(self)
|
||||||
|
|
||||||
|
def autoname(self):
|
||||||
|
self.name = self.account_name + " - " + self.bank
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
delete_contact_and_address('BankAccount', self.name)
|
delete_contact_and_address('BankAccount', self.name)
|
||||||
|
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="col-md-3 col-sm-4 col-xs-6 new-account-card-container">
|
|
||||||
<div class="account-card text-center"
|
|
||||||
@click="handleOnClick()"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
<div class="account-card-header flex justify-between">
|
|
||||||
<div class="ellipsis">
|
|
||||||
<div class="new-account-card-text ellipsis text-muted" v-html='subtitle'></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'PlaidLink',
|
|
||||||
props: {
|
|
||||||
plaidUrl: {
|
|
||||||
type: String,
|
|
||||||
default: 'https://cdn.plaid.com/link/v2/stable/link-initialize.js'
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
type: String,
|
|
||||||
default: 'sandbox'
|
|
||||||
},
|
|
||||||
institution: String,
|
|
||||||
selectAccount: Boolean,
|
|
||||||
token: String,
|
|
||||||
product: {
|
|
||||||
type: Array,
|
|
||||||
default: ["transactions"]
|
|
||||||
},
|
|
||||||
clientName: String,
|
|
||||||
publicKey: String,
|
|
||||||
webhook: String,
|
|
||||||
plaidSuccess: Function,
|
|
||||||
onExit: Function,
|
|
||||||
onEvent: Function,
|
|
||||||
subtitle: String
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.loadScript(this.plaidUrl)
|
|
||||||
.then(this.onScriptLoaded)
|
|
||||||
.catch(this.onScriptError)
|
|
||||||
},
|
|
||||||
beforeDestroy () {
|
|
||||||
if (window.linkHandler && window.linkHandler.open.lenth > 0) {
|
|
||||||
window.linkHandler.exit()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onScriptError (error) {
|
|
||||||
console.error('There was an issue loading the link-initialize.js script')
|
|
||||||
},
|
|
||||||
onScriptLoaded () {
|
|
||||||
window.linkHandler = window.Plaid.create({
|
|
||||||
clientName: this.clientName,
|
|
||||||
env: this.env,
|
|
||||||
key: this.publicKey,
|
|
||||||
onExit: this.onExit,
|
|
||||||
onEvent: this.onEvent,
|
|
||||||
onSuccess: this.plaidSuccess,
|
|
||||||
product: this.product,
|
|
||||||
selectAccount: this.selectAccount,
|
|
||||||
token: this.token,
|
|
||||||
webhook: this.webhook
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleOnClick () {
|
|
||||||
const institution = this.institution || null
|
|
||||||
if (window.linkHandler) {
|
|
||||||
window.linkHandler.open(institution)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadScript (src) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
if (document.querySelector('script[src="' + src + '"]')) {
|
|
||||||
resolve()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const el = document.createElement('script')
|
|
||||||
el.type = 'text/javascript'
|
|
||||||
el.async = true
|
|
||||||
el.src = src
|
|
||||||
el.addEventListener('load', resolve)
|
|
||||||
el.addEventListener('error', reject)
|
|
||||||
el.addEventListener('abort', reject)
|
|
||||||
document.head.appendChild(el)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="less" scoped>
|
|
||||||
@import "../../../../../../frappe/frappe/public/less/variables.less";
|
|
||||||
.account-card {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid @border-color;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-style: dashed;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover .account-card-overlay {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.account-card-header {
|
|
||||||
position: relative;
|
|
||||||
padding: 12px 15px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
.account-card-body {
|
|
||||||
position: relative;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
.account-card-overlay {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
.account-card-overlay-body {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.account-card-overlay-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 15px;
|
|
||||||
bottom: 15px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.provide("erpnext.accounts");
|
|
||||||
|
|
||||||
|
|
||||||
frappe.ui.form.on('Bank Reconciliation Dashboard', {
|
|
||||||
refresh: function(frm) {
|
|
||||||
frm.disable_save();
|
|
||||||
toggle_sidebar(frm);
|
|
||||||
frm.page.add_menu_item(__("Toggle Sidebar"), function() {
|
|
||||||
toggle_sidebar(frm);
|
|
||||||
});
|
|
||||||
|
|
||||||
new erpnext.accounts.newInstitution(frm);
|
|
||||||
},
|
|
||||||
import_data: function(frm) {
|
|
||||||
new erpnext.accounts.bankTransactionUpload(frm);
|
|
||||||
},
|
|
||||||
sync_data: function(frm) {
|
|
||||||
new erpnext.accounts.bankTransactionSync(frm);
|
|
||||||
},
|
|
||||||
reconcile_data: function(frm) {
|
|
||||||
console.log("test")
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
let toggle_sidebar = function(frm) {
|
|
||||||
frm.sidebar.sidebar.toggle();
|
|
||||||
frm.page.current_view.find('.layout-main-section-wrapper').toggleClass('col-md-10 col-md-12');
|
|
||||||
}
|
|
||||||
|
|
||||||
erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
|
|
||||||
constructor(frm) {
|
|
||||||
this.frm = frm;
|
|
||||||
this.data = [];
|
|
||||||
this.import_wrapper = $(frm.fields_dict['import_html'].wrapper);
|
|
||||||
this.table_container = $(frm.fields_dict['table_container'].wrapper);
|
|
||||||
|
|
||||||
const assets = [
|
|
||||||
"/assets/frappe/css/frappe-datatable.css",
|
|
||||||
"/assets/frappe/js/lib/clusterize.min.js",
|
|
||||||
"/assets/frappe/js/lib/Sortable.min.js",
|
|
||||||
"/assets/frappe/js/lib/frappe-datatable.js"
|
|
||||||
];
|
|
||||||
|
|
||||||
frappe.require(assets, () => {
|
|
||||||
this.make();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
make() {
|
|
||||||
let me = this;
|
|
||||||
frappe.upload.make({
|
|
||||||
parent: me.import_wrapper,
|
|
||||||
args: {
|
|
||||||
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
|
|
||||||
allow_multiple: 0
|
|
||||||
},
|
|
||||||
no_socketio: true,
|
|
||||||
sample_url: "e.g. http://example.com/somefile.csv",
|
|
||||||
callback: function(attachment, r) {
|
|
||||||
if (!r.exc && r.message) {
|
|
||||||
me.data = r.message;
|
|
||||||
me.setup_transactions_dom();
|
|
||||||
me.create_datatable();
|
|
||||||
me.bind_events();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_transactions_dom() {
|
|
||||||
this.table_container.append(`
|
|
||||||
<div class="transactions-table"></div>
|
|
||||||
<div class="transactions-btn margin-top text-right">
|
|
||||||
<button class= "btn btn-primary btn-submit"> ${ __("Submit") } </button>
|
|
||||||
</div>`)
|
|
||||||
}
|
|
||||||
|
|
||||||
create_datatable() {
|
|
||||||
this.datatable = new DataTable('.transactions-table', {
|
|
||||||
columns: this.data.columns,
|
|
||||||
data: this.data.data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
bind_events() {
|
|
||||||
this.table_container.on('click', '.transactions-btn', function() {
|
|
||||||
console.log("Test")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
add_bank_entries() {
|
|
||||||
frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries',
|
|
||||||
{columns: this.data.datamanager.columns, data: this.data.datamanager.data, bank_account: this.frm.doc.bank_account}
|
|
||||||
).then((result) => {
|
|
||||||
console.log(result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
erpnext.accounts.bankTransactionSync = class bankTransactionSync {
|
|
||||||
constructor(frm) {
|
|
||||||
this.frm = frm;
|
|
||||||
this.data = [];
|
|
||||||
this.import_wrapper = $(frm.fields_dict['import_html'].wrapper);
|
|
||||||
this.table_container = $(frm.fields_dict['table_container'].wrapper);
|
|
||||||
|
|
||||||
|
|
||||||
this.init_config()
|
|
||||||
const assets = [
|
|
||||||
"/assets/frappe/css/frappe-datatable.css",
|
|
||||||
"/assets/frappe/js/lib/clusterize.min.js",
|
|
||||||
"/assets/frappe/js/lib/Sortable.min.js",
|
|
||||||
"/assets/frappe/js/lib/frappe-datatable.js"
|
|
||||||
];
|
|
||||||
|
|
||||||
frappe.require(assets, () => {
|
|
||||||
this.make();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
init_config() {
|
|
||||||
let me = this;
|
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
|
|
||||||
.then(result => {
|
|
||||||
me.plaid_env = result.plaid_env;
|
|
||||||
me.plaid_public_key = result.plaid_public_key;
|
|
||||||
me.client_name = result.client_name;
|
|
||||||
me.sync_transactions()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_transactions() {
|
|
||||||
let me = this;
|
|
||||||
frappe.db.get_value("Bank Account", me.frm.doc.bank_account, "bank", (v) => {
|
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
|
|
||||||
bank: v['bank'],
|
|
||||||
bank_account: me.frm.doc.bank_account
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
console.log(result)
|
|
||||||
me.get_transactions();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
get_transactions() {
|
|
||||||
let me = this;
|
|
||||||
frappe.db.get_list('Bank Transaction', {
|
|
||||||
fields: ['name', 'date', 'status', 'debit', 'credit', 'currency', 'description'],
|
|
||||||
filters: {"docstatus": 1},
|
|
||||||
or_filters: [['reference_number', '=', '']]
|
|
||||||
|
|
||||||
}).then((transactions) => {
|
|
||||||
me.transactions = transactions;
|
|
||||||
console.log(me)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
make() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
erpnext.accounts.newInstitution = class newInstitution {
|
|
||||||
constructor(frm) {
|
|
||||||
this.frm = frm;
|
|
||||||
this.init_config()
|
|
||||||
}
|
|
||||||
|
|
||||||
init_config() {
|
|
||||||
let me = this;
|
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
|
|
||||||
.then(result => {
|
|
||||||
if (result) {
|
|
||||||
me.plaid_env = result.plaid_env;
|
|
||||||
me.plaid_public_key = result.plaid_public_key;
|
|
||||||
me.client_name = result.client_name;
|
|
||||||
me.new_plaid_link()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
plaid_success(token, response) {
|
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
|
|
||||||
.then((result) => {
|
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response, bank: result})
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
this.getBankAccounts();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
new_plaid_link() {
|
|
||||||
let me = this;
|
|
||||||
frappe.require('assets/js/frappe-vue.js', () => {
|
|
||||||
new Vue({
|
|
||||||
el: $(frm.fields_dict['new_institution'].wrapper),
|
|
||||||
render(h) {
|
|
||||||
return h(PlaidLink, {
|
|
||||||
props: {
|
|
||||||
env: me.plaid_env,
|
|
||||||
publicKey: me.plaid_public_key,
|
|
||||||
clientName: me.client_name,
|
|
||||||
product: ["transactions", "auth"],
|
|
||||||
subtitle: "Test",
|
|
||||||
plaidSuccess: me.plaid_success
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,539 +0,0 @@
|
|||||||
{
|
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 1,
|
|
||||||
"creation": "2018-11-14 17:30:33.401641",
|
|
||||||
"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": "section_break_1",
|
|
||||||
"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 a bank account",
|
|
||||||
"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": "bank_account",
|
|
||||||
"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": "Bank Account",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Bank 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": 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": "column_break_3",
|
|
||||||
"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": "new_institution",
|
|
||||||
"fieldtype": "HTML",
|
|
||||||
"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": "Add a new institution/account",
|
|
||||||
"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": "eval:doc.bank_account",
|
|
||||||
"fieldname": "section_break_2",
|
|
||||||
"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 an action",
|
|
||||||
"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": "import_data",
|
|
||||||
"fieldtype": "Button",
|
|
||||||
"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": "Import Data",
|
|
||||||
"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": "sync_data",
|
|
||||||
"fieldtype": "Button",
|
|
||||||
"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": "Synchronize Data",
|
|
||||||
"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_7",
|
|
||||||
"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": "reconcile_data",
|
|
||||||
"fieldtype": "Button",
|
|
||||||
"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": "Reconcile Data",
|
|
||||||
"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": 1,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "action_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,
|
|
||||||
"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": "import_html",
|
|
||||||
"fieldtype": "HTML",
|
|
||||||
"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": "Bank Statement Import",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "reconcile_html",
|
|
||||||
"fieldtype": "HTML",
|
|
||||||
"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": "section_break_10",
|
|
||||||
"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,
|
|
||||||
"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": "table_container",
|
|
||||||
"fieldtype": "HTML",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 1,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-11-15 18:02:39.720945",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Bank Reconciliation Dashboard",
|
|
||||||
"name_case": "",
|
|
||||||
"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": 0,
|
|
||||||
"role": "System Manager",
|
|
||||||
"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",
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
class BankReconciliationDashboard(Document):
|
|
||||||
pass
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Bank Reconciliation Dashboard", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Bank Reconciliation Dashboard
|
|
||||||
() => frappe.tests.make('Bank Reconciliation Dashboard', [
|
|
||||||
// values to be set
|
|
||||||
{key: 'value'}
|
|
||||||
]),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.key, 'value');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# See license.txt
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
class TestBankReconciliationDashboard(unittest.TestCase):
|
|
||||||
pass
|
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Debit",
|
"label": "Debit",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
@@ -255,7 +255,7 @@
|
|||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Credit",
|
"label": "Credit",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
@@ -382,7 +382,7 @@
|
|||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
@@ -526,6 +526,39 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "payment_entry",
|
||||||
|
"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": "Payment Entry",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Payment Entry",
|
||||||
|
"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,
|
"has_web_view": 0,
|
||||||
@@ -538,7 +571,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-10-26 15:58:53.400200",
|
"modified": "2018-11-27 13:26:53.794350",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bank Transaction",
|
"name": "Bank Transaction",
|
||||||
@@ -609,6 +642,7 @@
|
|||||||
"show_name_in_global_search": 0,
|
"show_name_in_global_search": 0,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"title_field": "bank_account",
|
||||||
"track_changes": 0,
|
"track_changes": 0,
|
||||||
"track_seen": 0,
|
"track_seen": 0,
|
||||||
"track_views": 0
|
"track_views": 0
|
||||||
|
|||||||
@@ -32,10 +32,12 @@ def upload_bank_statement():
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_bank_entries(columns, data, bank_account):
|
def create_bank_entries(columns, data, bank_account):
|
||||||
bank_account = json.loads(bank_account)
|
|
||||||
header_map = get_header_mapping(columns, bank_account)
|
header_map = get_header_mapping(columns, bank_account)
|
||||||
|
|
||||||
|
count = 0
|
||||||
for d in json.loads(data):
|
for d in json.loads(data):
|
||||||
|
if all(item is None for item in d) is True:
|
||||||
|
continue
|
||||||
fields = {}
|
fields = {}
|
||||||
for key, value in header_map.iteritems():
|
for key, value in header_map.iteritems():
|
||||||
fields.update({key: d[int(value)-1]})
|
fields.update({key: d[int(value)-1]})
|
||||||
@@ -46,10 +48,12 @@ def create_bank_entries(columns, data, bank_account):
|
|||||||
})
|
})
|
||||||
bank_transaction.update(fields)
|
bank_transaction.update(fields)
|
||||||
bank_transaction.date = getdate(bank_transaction.date)
|
bank_transaction.date = getdate(bank_transaction.date)
|
||||||
bank_transaction.bank_account = bank_account["name"]
|
bank_transaction.bank_account = bank_account
|
||||||
bank_transaction.insert()
|
bank_transaction.insert()
|
||||||
|
bank_transaction.submit()
|
||||||
|
count = count + 1
|
||||||
|
|
||||||
return 'success'
|
return count
|
||||||
|
|
||||||
def get_header_mapping(columns, bank_account):
|
def get_header_mapping(columns, bank_account):
|
||||||
mapping = get_bank_mapping(bank_account)
|
mapping = get_bank_mapping(bank_account)
|
||||||
@@ -62,7 +66,7 @@ def get_header_mapping(columns, bank_account):
|
|||||||
return header_map
|
return header_map
|
||||||
|
|
||||||
def get_bank_mapping(bank_account):
|
def get_bank_mapping(bank_account):
|
||||||
bank_name = frappe.db.get_value("Bank Account", bank_account["name"], "bank")
|
bank_name = frappe.db.get_value("Bank Account", bank_account, "bank")
|
||||||
bank = frappe.get_doc("Bank", bank_name)
|
bank = frappe.get_doc("Bank", bank_name)
|
||||||
|
|
||||||
mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping}
|
mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping}
|
||||||
|
|||||||
491
erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
Normal file
491
erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
frappe.provide("erpnext.accounts");
|
||||||
|
|
||||||
|
frappe.pages['bank-reconciliation'].on_page_load = function(wrapper) {
|
||||||
|
new erpnext.accounts.bankReconciliation(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.bankReconciliation = class BankReconciliation {
|
||||||
|
constructor(wrapper) {
|
||||||
|
this.page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: 'Bank Reconciliation',
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
this.parent = wrapper;
|
||||||
|
this.page = this.parent.page;
|
||||||
|
|
||||||
|
this.make();
|
||||||
|
this.add_plaid_btn();
|
||||||
|
}
|
||||||
|
|
||||||
|
make() {
|
||||||
|
const me = this;
|
||||||
|
|
||||||
|
me.$main_section = $(`<div class="reconciliation page-main-content"></div>`).appendTo(me.page.main);
|
||||||
|
|
||||||
|
me.page.add_field({
|
||||||
|
fieldtype: 'Link',
|
||||||
|
label: __('Bank Account'),
|
||||||
|
fieldname: 'bank_account',
|
||||||
|
options: "Bank Account",
|
||||||
|
onchange: function() {
|
||||||
|
if (this.value) {
|
||||||
|
me.bank_account = this.value;
|
||||||
|
me.add_actions();
|
||||||
|
} else {
|
||||||
|
me.bank_account = null;
|
||||||
|
me.page.hide_actions_menu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
add_plaid_btn() {
|
||||||
|
const me = this;
|
||||||
|
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
|
||||||
|
if (r.enabled == "1") {
|
||||||
|
me.parent.page.add_inner_button(__('Link a new bank account'), function() {
|
||||||
|
new erpnext.accounts.plaidLink(this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
add_actions() {
|
||||||
|
const me = this;
|
||||||
|
|
||||||
|
me.page.show_actions_menu()
|
||||||
|
|
||||||
|
me.page.add_action_item(__("Upload a statement"), function() {
|
||||||
|
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)
|
||||||
|
me.page.add_action_item(__("Reconcile this account"), function() {
|
||||||
|
me.clear_page_content();
|
||||||
|
me.make_reconciliation_tool();
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_page_content() {
|
||||||
|
const me = this;
|
||||||
|
$(me.page.body).find('.frappe-list').remove();
|
||||||
|
me.$main_section.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
make_reconciliation_tool() {
|
||||||
|
const me = this;
|
||||||
|
console.log(me)
|
||||||
|
frappe.model.with_doctype("Bank Transaction", () => {
|
||||||
|
new erpnext.accounts.ReconciliationTool({
|
||||||
|
parent: me.parent,
|
||||||
|
doctype: "Bank Transaction"
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
|
||||||
|
constructor(parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.data = [];
|
||||||
|
|
||||||
|
const assets = [
|
||||||
|
"/assets/frappe/css/frappe-datatable.css",
|
||||||
|
"/assets/frappe/js/lib/clusterize.min.js",
|
||||||
|
"/assets/frappe/js/lib/Sortable.min.js",
|
||||||
|
"/assets/frappe/js/lib/frappe-datatable.js"
|
||||||
|
];
|
||||||
|
|
||||||
|
frappe.require(assets, () => {
|
||||||
|
this.make();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
make() {
|
||||||
|
const me = this;
|
||||||
|
frappe.upload.make({
|
||||||
|
args: {
|
||||||
|
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
|
||||||
|
allow_multiple: 0
|
||||||
|
},
|
||||||
|
no_socketio: true,
|
||||||
|
sample_url: "e.g. http://example.com/somefile.csv",
|
||||||
|
callback: function(attachment, r) {
|
||||||
|
if (!r.exc && r.message) {
|
||||||
|
me.data = r.message;
|
||||||
|
me.setup_transactions_dom();
|
||||||
|
me.create_datatable();
|
||||||
|
me.add_primary_action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_transactions_dom() {
|
||||||
|
const me = this;
|
||||||
|
me.parent.$main_section.append(`<div class="transactions-table"></div>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
create_datatable() {
|
||||||
|
this.datatable = new DataTable('.transactions-table', {
|
||||||
|
columns: this.data.columns,
|
||||||
|
data: this.data.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
add_primary_action() {
|
||||||
|
const me = this;
|
||||||
|
me.parent.page.set_primary_action(__("Submit"), function() {
|
||||||
|
me.add_bank_entries()
|
||||||
|
}, null, __("Creating bank entries..."))
|
||||||
|
}
|
||||||
|
|
||||||
|
add_bank_entries() {
|
||||||
|
const me = this;
|
||||||
|
frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries',
|
||||||
|
{columns: this.datatable.datamanager.columns, data: this.datatable.datamanager.data, bank_account: me.parent.bank_account}
|
||||||
|
).then((result) => {
|
||||||
|
let result_title = __("{0} bank transaction(s) created", [result])
|
||||||
|
let result_msg = `
|
||||||
|
<div class="text-center">
|
||||||
|
<h5 class="text-muted">${result_title}</h5>
|
||||||
|
</div>`
|
||||||
|
me.parent.page.clear_primary_action();
|
||||||
|
me.parent.$main_section.empty();
|
||||||
|
me.parent.$main_section.append(result_msg);
|
||||||
|
frappe.show_alert({message:__("All bank transactions have been created"), indicator:'green'});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.bankTransactionSync = class bankTransactionSync {
|
||||||
|
constructor(parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.data = [];
|
||||||
|
|
||||||
|
this.init_config()
|
||||||
|
}
|
||||||
|
|
||||||
|
init_config() {
|
||||||
|
const me = this;
|
||||||
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
|
||||||
|
.then(result => {
|
||||||
|
me.plaid_env = result.plaid_env;
|
||||||
|
me.plaid_public_key = result.plaid_public_key;
|
||||||
|
me.client_name = result.client_name;
|
||||||
|
me.sync_transactions()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_transactions() {
|
||||||
|
const me = this;
|
||||||
|
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => {
|
||||||
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
|
||||||
|
bank: v['bank'],
|
||||||
|
bank_account: me.parent.bank_account,
|
||||||
|
freeze: true
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
console.log(result)
|
||||||
|
let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized")
|
||||||
|
let result_msg = `
|
||||||
|
<div class="text-center">
|
||||||
|
<h5 class="text-muted">${result_title}</h5>
|
||||||
|
</div>`
|
||||||
|
this.parent.$main_section.append(result_msg)
|
||||||
|
frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.plaidLink = class plaidLink {
|
||||||
|
constructor(parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.product = ["transactions", "auth"];
|
||||||
|
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
|
||||||
|
this.init_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
init_config() {
|
||||||
|
const me = this;
|
||||||
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
|
||||||
|
.then(result => {
|
||||||
|
if (result !== "disabled") {
|
||||||
|
me.plaid_env = result.plaid_env;
|
||||||
|
me.plaid_public_key = result.plaid_public_key;
|
||||||
|
me.client_name = result.client_name;
|
||||||
|
me.init_plaid()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
init_plaid() {
|
||||||
|
const me = this;
|
||||||
|
me.loadScript(me.plaidUrl)
|
||||||
|
.then(() => {
|
||||||
|
me.onScriptLoaded(me);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (me.linkHandler) {
|
||||||
|
me.linkHandler.open();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
me.onScriptError(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadScript(src) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
if (document.querySelector('script[src="' + src + '"]')) {
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const el = document.createElement('script')
|
||||||
|
el.type = 'text/javascript'
|
||||||
|
el.async = true
|
||||||
|
el.src = src
|
||||||
|
el.addEventListener('load', resolve)
|
||||||
|
el.addEventListener('error', reject)
|
||||||
|
el.addEventListener('abort', reject)
|
||||||
|
document.head.appendChild(el)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onScriptLoaded(me) {
|
||||||
|
me.linkHandler = window.Plaid.create({
|
||||||
|
clientName: me.client_name,
|
||||||
|
env: me.plaid_env,
|
||||||
|
key: me.plaid_public_key,
|
||||||
|
onSuccess: me.plaid_success,
|
||||||
|
product: me.product
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onScriptError(error) {
|
||||||
|
console.error('There was an issue loading the link-initialize.js script');
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
plaid_success(token, response) {
|
||||||
|
const me = this;
|
||||||
|
|
||||||
|
frappe.prompt({
|
||||||
|
fieldtype:"Link",
|
||||||
|
options: "Company",
|
||||||
|
label:__("Company"),
|
||||||
|
fieldname:"company",
|
||||||
|
reqd:1
|
||||||
|
}, (data) => {
|
||||||
|
me.company = data.company;
|
||||||
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
|
||||||
|
.then((result) => {
|
||||||
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response,
|
||||||
|
bank: result, company: me.company})
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
console.log(result)
|
||||||
|
frappe.show_alert({message:__("Bank accounts added"), indicator:'green'});
|
||||||
|
})
|
||||||
|
}, __("Select a company"), __("Continue"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.views.BaseList {
|
||||||
|
constructor(opts) {
|
||||||
|
super(opts);
|
||||||
|
this.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_defaults() {
|
||||||
|
super.setup_defaults();
|
||||||
|
|
||||||
|
this.doctype = 'Bank Transaction';
|
||||||
|
this.fields = ['date', 'description', 'debit', 'credit', 'currency']
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_view() {
|
||||||
|
this.render_header();
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_side_bar() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
make_standard_filters() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
freeze() {
|
||||||
|
this.$result.find('.list-count').html(`<span>${__('Refreshing')}...</span>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_args() {
|
||||||
|
const args = super.get_args();
|
||||||
|
|
||||||
|
return Object.assign({}, args, {
|
||||||
|
...args.filters.push(["Bank Transaction", "docstatus", "=", 1],
|
||||||
|
["Bank Transaction", "payment_entry", "=", ""])
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
update_data(r) {
|
||||||
|
let data = r.message || [];
|
||||||
|
|
||||||
|
if (this.start === 0) {
|
||||||
|
this.data = data;
|
||||||
|
} else {
|
||||||
|
this.data = this.data.concat(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const me = this;
|
||||||
|
this.$result.find('.list-row-container').remove();
|
||||||
|
$('[data-fieldname="name"]').remove();
|
||||||
|
me.data.map((value) => {
|
||||||
|
const row = $('<div class="list-row-container">').data("data", value).appendTo(me.$result).get(0);
|
||||||
|
new erpnext.accounts.ReconciliationRow(row, value);
|
||||||
|
})
|
||||||
|
|
||||||
|
me.parent.page.hide_menu()
|
||||||
|
}
|
||||||
|
|
||||||
|
render_header() {
|
||||||
|
const me = this;
|
||||||
|
if ($(this.wrapper).find('.transaction-header').length === 0) {
|
||||||
|
me.$result.append(frappe.render_template("bank_transaction_header"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.ReconciliationRow = class ReconciliationRow {
|
||||||
|
constructor(row, data) {
|
||||||
|
this.data = data;
|
||||||
|
this.row = row;
|
||||||
|
this.make();
|
||||||
|
this.bind_events();
|
||||||
|
}
|
||||||
|
|
||||||
|
make() {
|
||||||
|
$(this.row).append(frappe.render_template("bank_transaction_row", this.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_events() {
|
||||||
|
const me = this;
|
||||||
|
$(me.row).on('click', '.clickable-section', 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();
|
||||||
|
})
|
||||||
|
|
||||||
|
$(me.row).on('click', '.new-invoice', function() {
|
||||||
|
me.bank_entry = $(this).attr("data-name");
|
||||||
|
me.new_invoice();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
new_payment() {
|
||||||
|
const me = this;
|
||||||
|
const paid_amount = me.data.credit > 0 ? me.data.credit : me.data.debit;
|
||||||
|
const payment_type = me.data.credit > 0 ? "Receive": "Pay";
|
||||||
|
const party_type = me.data.credit > 0 ? "Customer": "Supplier";
|
||||||
|
|
||||||
|
frappe.new_doc("Payment Entry", {"payment_type": payment_type, "paid_amount": paid_amount,
|
||||||
|
"party_type": party_type, "paid_from": me.data.bank_account})
|
||||||
|
}
|
||||||
|
|
||||||
|
new_invoice() {
|
||||||
|
const me = this;
|
||||||
|
const invoice_type = me.data.credit > 0 ? "Sales Invoice" : "Purchase Invoice";
|
||||||
|
|
||||||
|
frappe.new_doc(invoice_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
show_dialog(data) {
|
||||||
|
const me = this;
|
||||||
|
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
|
||||||
|
{bank_transaction: data}
|
||||||
|
)
|
||||||
|
.then((result) => {
|
||||||
|
me.make_dialog(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
make_dialog(data) {
|
||||||
|
const me = this;
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
fieldtype: 'Section Break',
|
||||||
|
fieldname: 'section_break_1',
|
||||||
|
label: __('Automatic Reconciliation')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'HTML',
|
||||||
|
fieldname: 'payment_proposals'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Section Break',
|
||||||
|
fieldname: 'section_break_2',
|
||||||
|
label: __('Search for a payment')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Link',
|
||||||
|
fieldname: 'payment_entry',
|
||||||
|
options: 'Payment Entry',
|
||||||
|
label: 'Payment Entry'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'HTML',
|
||||||
|
fieldname: 'payment_details'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
me.dialog = new frappe.ui.Dialog({
|
||||||
|
title: __("Choose a corresponding payment"),
|
||||||
|
fields: fields
|
||||||
|
});
|
||||||
|
|
||||||
|
const proposals_wrapper = me.dialog.fields_dict.payment_proposals.$wrapper;
|
||||||
|
if (data.length > 0) {
|
||||||
|
data.map(value => {
|
||||||
|
proposals_wrapper.append(frappe.render_template("linked_payment_row", value))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const empty_data_msg = __("ERPNext could not find any matching payment entry")
|
||||||
|
proposals_wrapper.append(`<div class="text-center"><h5 class="text-muted">${empty_data_msg}</h5></div>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
$(me.dialog.body).on('click', '.reconciliation-btn', (e) => {
|
||||||
|
const payment_entry = $(e.target).attr('data-name');
|
||||||
|
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile',
|
||||||
|
{bank_transaction: me.bank_entry, payment_entry: payment_entry})
|
||||||
|
.then((result) => console.log(result))
|
||||||
|
})
|
||||||
|
|
||||||
|
$(me.dialog.body).on('blur', '.input-with-feedback', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
me.dialog.fields_dict['payment_details'].$wrapper.empty();
|
||||||
|
frappe.db.get_doc("Payment Entry", e.target.value)
|
||||||
|
.then(doc => {
|
||||||
|
const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper;
|
||||||
|
details_wrapper.append(frappe.render_template("linked_payment_row", doc));
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
me.dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"content": null,
|
||||||
|
"creation": "2018-11-24 12:03:14.646669",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Page",
|
||||||
|
"idx": 0,
|
||||||
|
"modified": "2018-11-24 12:03:14.646669",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "bank-reconciliation",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"page_name": "bank-reconciliation",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "System Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts User"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"script": null,
|
||||||
|
"standard": "Yes",
|
||||||
|
"style": null,
|
||||||
|
"system_page": 0,
|
||||||
|
"title": "Bank Reconciliation"
|
||||||
|
}
|
||||||
109
erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
Normal file
109
erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
import difflib
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_linked_payments(bank_transaction):
|
||||||
|
|
||||||
|
transaction = frappe.get_doc("Bank Transaction", bank_transaction)
|
||||||
|
|
||||||
|
amount_matching = check_matching_amount(transaction)
|
||||||
|
description_matching = check_matching_descriptions(transaction)
|
||||||
|
|
||||||
|
if amount_matching:
|
||||||
|
match = check_amount_vs_description(amount_matching, description_matching)
|
||||||
|
if match:
|
||||||
|
return match
|
||||||
|
else:
|
||||||
|
return merge_matching_lists(amount_matching, description_matching)
|
||||||
|
|
||||||
|
else:
|
||||||
|
linked_payments = get_matching_transactions_payments(description_matching)
|
||||||
|
return linked_payments
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def reconcile(bank_transaction, payment_entry):
|
||||||
|
transaction = frappe.get_doc("Bank Transaction", bank_transaction)
|
||||||
|
payment_entry = frappe.get_doc("Payment Entry", payment_entry)
|
||||||
|
|
||||||
|
if transaction.payment_entry:
|
||||||
|
frappe.throw(_("This bank transaction is already linked to a payment entry"))
|
||||||
|
|
||||||
|
if transaction.credit > 0 and payment_entry.payment_type == "Pay":
|
||||||
|
frappe.throw(_("The selected payment entry should be linked with a debitor bank transaction"))
|
||||||
|
|
||||||
|
if transaction.debit > 0 and payment_entry.payment_type == "Receive":
|
||||||
|
frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction"))
|
||||||
|
|
||||||
|
frappe.db.set_value("Bank Transaction", bank_transaction, "payment_entry", payment_entry)
|
||||||
|
linked_bank_transactions = frappe.get_all("Bank Transaction", filters={"payment_entry": payment_entry, "docstatus": 1},
|
||||||
|
fields=["sum(debit) as debit", "sum(credit) as credit"])
|
||||||
|
cleared_amount = (linked_bank_transactions[0].credit - linked_bank_transactions[0].debit)
|
||||||
|
|
||||||
|
if cleared_amount == payment_entry.total_allocated_amount:
|
||||||
|
frappe.db.set_value("Payment Entry", payment_entry, "clearance_date", transaction.date)
|
||||||
|
|
||||||
|
def check_matching_amount(transaction):
|
||||||
|
amount = transaction.credit if transaction.credit > 0 else transaction.debit
|
||||||
|
payment_type = "Receive" if transaction.credit > 0 else "Pay"
|
||||||
|
|
||||||
|
payments = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date",
|
||||||
|
"party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["paid_amount", "like", "{0}%".format(amount)],
|
||||||
|
["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["clearance_date", "=", ""]])
|
||||||
|
|
||||||
|
return payments
|
||||||
|
|
||||||
|
|
||||||
|
def check_matching_descriptions(transaction):
|
||||||
|
bank_transactions = frappe.get_all("Bank Transaction", fields=["name", "description", "payment_entry", "date"],
|
||||||
|
filters=[["docstatus", "=", "1"], ["payment_entry", "!=", ""]])
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for bank_transaction in bank_transactions:
|
||||||
|
if bank_transaction.description:
|
||||||
|
seq=difflib.SequenceMatcher(lambda x: x == " ", transaction.description, bank_transaction.description)
|
||||||
|
|
||||||
|
if seq.ratio() > 0.5:
|
||||||
|
bank_transaction["ratio"] = seq.ratio()
|
||||||
|
result.append(bank_transaction)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def check_amount_vs_description(amount_matching, description_matching):
|
||||||
|
result = []
|
||||||
|
for match in amount_matching:
|
||||||
|
result.append([match for x in description_matching if match["name"]==x["payment_entry"]])
|
||||||
|
|
||||||
|
return match
|
||||||
|
|
||||||
|
def merge_matching_lists(amount_matching, description_matching):
|
||||||
|
|
||||||
|
for match in amount_matching:
|
||||||
|
if match["name"] in map(itemgetter('payment_entry'), description_matching):
|
||||||
|
index = map(itemgetter('payment_entry'), description_matching).index(match["name"])
|
||||||
|
del description_matching[index]
|
||||||
|
|
||||||
|
linked_payments = get_matching_transactions_payments(description_matching)
|
||||||
|
|
||||||
|
result = amount_matching.append(linked_payments)
|
||||||
|
return sorted(result, key = lambda x: x["posting_date"], reverse=True)
|
||||||
|
|
||||||
|
def get_matching_transactions_payments(description_matching):
|
||||||
|
payments = [x["payment_entry"] for x in description_matching]
|
||||||
|
|
||||||
|
payment_by_ratio = {x["payment_entry"]: x["ratio"] for x in description_matching}
|
||||||
|
|
||||||
|
if payments:
|
||||||
|
payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date",
|
||||||
|
"party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["name", "in", payments]])
|
||||||
|
|
||||||
|
return sorted(payment_list, key=lambda x: payment_by_ratio[x["name"]])
|
||||||
|
|
||||||
|
else:
|
||||||
|
return []
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<div class="transaction-header">
|
||||||
|
<div class="level list-row list-row-head text-muted small">
|
||||||
|
<div class="col-sm-2 ellipsis hidden-xs">
|
||||||
|
{{ __("Date") }}
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-11 col-sm-4 ellipsis list-subject">
|
||||||
|
{{ __("Description") }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 ellipsis hidden-xs">
|
||||||
|
{{ __("Debit") }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 ellipsis hidden-xs">
|
||||||
|
{{ __("Credit") }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1 ellipsis hidden-xs">
|
||||||
|
{{ __("Currency") }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1 ellipsis">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<div class="list-row transaction-item">
|
||||||
|
<div>
|
||||||
|
<div class="clickable-section" data-name={{ name }}>
|
||||||
|
<div class="col-sm-2 ellipsis hidden-xs">
|
||||||
|
{%= frappe.datetime.str_to_user(date) %}
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-8 col-sm-4 ellipsis list-subject">
|
||||||
|
{{ description }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 ellipsis hidden-xs">
|
||||||
|
{%= format_currency(debit, currency) %}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 ellipsis hidden-xs">
|
||||||
|
{%= format_currency(credit, currency) %}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1 ellipsis hidden-xs">
|
||||||
|
{{ currency }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3 col-sm-1">
|
||||||
|
<div class="btn-group">
|
||||||
|
<a class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu reports-dropdown" style="max-height: 300px; overflow-y: auto; right: 0px; left: auto;">
|
||||||
|
<li><a class="new-payment" data-name={{ name }}>{{ __("New Payment") }}</a></li>
|
||||||
|
<li><a class="new-invoice" data-name={{ name }}>{{ __("New Invoice") }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<div class="grid-row">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<label class="control-label">{{ name }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5 ellipsis hidden-xs">
|
||||||
|
<h4>{{ __("Date:") }}</h4><h6> {{ posting_date }}</h6>
|
||||||
|
<h4>{{ __("Reference Date:") }}</h4><h6>{{ reference_date }}</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-7 ellipsis list-subject">
|
||||||
|
<h4>{{ __("Amount:") }}</h4><h6>{{ format_currency(paid_amount, paid_to_account_currency) }}</h6>
|
||||||
|
<h4>{{ __("Party:") }}</h4><h6>{{ party }}</h6>
|
||||||
|
<h4>{{ __("Reference:") }}</h4><h6>{{ reference_no }}</h6>
|
||||||
|
</div>
|
||||||
|
<div class="text-right margin-bottom">
|
||||||
|
<button class="btn btn-primary btn-xs reconciliation-btn" data-name={{ name }}>{{ __("Reconcile") }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -76,6 +76,14 @@ def get_data():
|
|||||||
{
|
{
|
||||||
"type": "doctype",
|
"type": "doctype",
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Bank",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Bank Account",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -135,6 +143,12 @@ def get_data():
|
|||||||
"name": "Bank Reconciliation",
|
"name": "Bank Reconciliation",
|
||||||
"description": _("Update bank payment dates with journals.")
|
"description": _("Update bank payment dates with journals.")
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "page",
|
||||||
|
"label": _("Reconcile payments and bank transactions"),
|
||||||
|
"name": "bank-reconciliation",
|
||||||
|
"description": _("Link bank transactions with payments.")
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "doctype",
|
"type": "doctype",
|
||||||
"label": _("Match Payments with Invoices"),
|
"label": _("Match Payments with Invoices"),
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ class PlaidConnector():
|
|||||||
|
|
||||||
def auth(self):
|
def auth(self):
|
||||||
try:
|
try:
|
||||||
print(self.access_token)
|
|
||||||
self.client.Auth.get(self.access_token)
|
self.client.Auth.get(self.access_token)
|
||||||
print("Authentication successful.....")
|
print("Authentication successful.....")
|
||||||
except ItemError as e:
|
except ItemError as e:
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ frappe.ui.form.on('Plaid Settings', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
connect_btn: function(frm) {
|
connect_btn: function(frm) {
|
||||||
frappe.set_route('bankreconciliation/synchronization');
|
frappe.set_route('bank-reconciliation');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -16,13 +16,14 @@ class PlaidSettings(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def plaid_configuration():
|
def plaid_configuration():
|
||||||
return {"plaid_public_key": frappe.conf.get("plaid_public_key") or None, "plaid_env": frappe.conf.get("plaid_env") or None, "client_name": frappe.local.site }
|
if frappe.db.get_value("Plaid Settings", None, "enabled") == "1":
|
||||||
|
return {"plaid_public_key": frappe.conf.get("plaid_public_key") or None, "plaid_env": frappe.conf.get("plaid_env") or None, "client_name": frappe.local.site }
|
||||||
|
else:
|
||||||
|
return "disabled"
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_institution(token, response):
|
def add_institution(token, response):
|
||||||
response = json.loads(response)
|
response = json.loads(response)
|
||||||
frappe.log_error(response)
|
|
||||||
|
|
||||||
plaid = PlaidConnector()
|
plaid = PlaidConnector()
|
||||||
access_token = plaid.get_access_token(token)
|
access_token = plaid.get_access_token(token)
|
||||||
@@ -46,12 +47,14 @@ def add_institution(token, response):
|
|||||||
return bank
|
return bank
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_bank_accounts(response, bank):
|
def add_bank_accounts(response, bank, company):
|
||||||
response = json.loads(response)
|
response = json.loads(response)
|
||||||
bank = json.loads(bank)
|
bank = json.loads(bank)
|
||||||
company = "Dokos"
|
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
default_gl_account = get_default_bank_cash_account(company, "Bank")
|
default_gl_account = get_default_bank_cash_account(company, "Bank")
|
||||||
|
if not default_gl_account:
|
||||||
|
frappe.throw(_("Please setup a default bank account for company {0}".format(company)))
|
||||||
|
|
||||||
for account in response["accounts"]:
|
for account in response["accounts"]:
|
||||||
acc_type = frappe.db.get_value("Account Type", account["type"])
|
acc_type = frappe.db.get_value("Account Type", account["type"])
|
||||||
@@ -80,6 +83,8 @@ def add_bank_accounts(response, bank):
|
|||||||
|
|
||||||
result.append(new_account.name)
|
result.append(new_account.name)
|
||||||
|
|
||||||
|
except frappe.UniqueValidationError as e:
|
||||||
|
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name))
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.throw(frappe.get_traceback())
|
frappe.throw(frappe.get_traceback())
|
||||||
|
|
||||||
@@ -135,7 +140,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
|||||||
access_token = None
|
access_token = None
|
||||||
|
|
||||||
if bank_account:
|
if bank_account:
|
||||||
related_bank = frappe.db.get_values("Bank Account", dict(account_name=bank_account), ["bank", "integration_id"], as_dict=True)
|
related_bank = frappe.db.get_values("Bank Account", bank_account, ["bank", "integration_id"], as_dict=True)
|
||||||
access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
|
access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
|
||||||
account_id = related_bank[0].integration_id
|
account_id = related_bank[0].integration_id
|
||||||
|
|
||||||
@@ -175,6 +180,7 @@ def new_bank_transaction(transaction):
|
|||||||
"description": transaction["name"]
|
"description": transaction["name"]
|
||||||
})
|
})
|
||||||
new_transaction.insert()
|
new_transaction.insert()
|
||||||
|
new_transaction.submit()
|
||||||
|
|
||||||
result.append(new_transaction.name)
|
result.append(new_transaction.name)
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div ref="bankreconciliation" class="bankreconciliation" :data-page-name="current_page">
|
|
||||||
<component
|
|
||||||
:is="current_page.component"
|
|
||||||
v-bind="{ getBankAccounts }"
|
|
||||||
:company='company'
|
|
||||||
:accounts='accounts'
|
|
||||||
>
|
|
||||||
</component>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import Dashboard from './pages/Dashboard.vue';
|
|
||||||
import Upload from './pages/Upload.vue';
|
|
||||||
import Reconciliation from './pages/Reconciliation.vue';
|
|
||||||
|
|
||||||
function get_route_map() {
|
|
||||||
return {
|
|
||||||
'bankreconciliation/home': {
|
|
||||||
'component': Dashboard
|
|
||||||
},
|
|
||||||
'bankreconciliation/upload': {
|
|
||||||
'component': Upload
|
|
||||||
},
|
|
||||||
'bankreconciliation/reconciliation': {
|
|
||||||
'component': Reconciliation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default {
|
|
||||||
props: ['initCompany'],
|
|
||||||
components: {
|
|
||||||
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
current_page: this.get_current_page(),
|
|
||||||
company: this.initCompany,
|
|
||||||
accounts: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
erpnext.bankreconciliation.on('company_changed', (e) => {
|
|
||||||
this.company = e;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
frappe.route.on('change', () => {
|
|
||||||
if (frappe.get_route()[0] === 'bankreconciliation') {
|
|
||||||
this.set_current_page();
|
|
||||||
frappe.utils.scroll_to(0);
|
|
||||||
$("body").attr("data-route", frappe.get_route_str());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
set_current_page() {
|
|
||||||
this.current_page = this.get_current_page();
|
|
||||||
},
|
|
||||||
get_current_page() {
|
|
||||||
const route_map = get_route_map();
|
|
||||||
const route = frappe.get_route_str();
|
|
||||||
if (route_map[route]) {
|
|
||||||
return route_map[route];
|
|
||||||
} else {
|
|
||||||
return route_map[route.substring(0, route.lastIndexOf('/')) + '/*'] || route_map['not_found']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getBankAccounts() {
|
|
||||||
frappe.db.get_list('Bank Account', {
|
|
||||||
fields: ['name', 'bank', 'bank_account_no', 'iban', 'branch_code', 'swift_number'],
|
|
||||||
filters: {'is_company_account': 1, 'company': this.company}
|
|
||||||
}).then((accounts) => {
|
|
||||||
this.accounts = accounts;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div ref="sidebar-container">
|
|
||||||
<ul class="list-unstyled bankreconciliation-sidebar-group" data-nav-buttons>
|
|
||||||
<li class="bankreconciliation-sidebar-item" v-for="item in items" :key="item.label" v-route="item.route">
|
|
||||||
{{ item.label }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: __('Dashboard'),
|
|
||||||
route: 'bankreconciliation/home'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __('Statement upload'),
|
|
||||||
route: 'bankreconciliation/upload'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __('Bank reconciliation'),
|
|
||||||
route: 'bankreconciliation/reconciliation',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.update_sidebar_state();
|
|
||||||
frappe.route.on('change', () => this.update_sidebar_state());
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
update_sidebar_state() {
|
|
||||||
const container = $(this.$refs['sidebar-container']);
|
|
||||||
const route = frappe.get_route();
|
|
||||||
const route_str = route.join('/');
|
|
||||||
const part_route_str = route.slice(0, 2).join('/');
|
|
||||||
const $sidebar_item = container.find(`[data-route="${route_str}"], [data-route="${part_route_str}"]`);
|
|
||||||
const $siblings = container.find('[data-route]');
|
|
||||||
$siblings.removeClass('active').addClass('text-muted');
|
|
||||||
$sidebar_item.addClass('active').removeClass('text-muted');
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="col-md-3 col-sm-4 col-xs-6 account-card-container">
|
|
||||||
<div class="account-card text-center"
|
|
||||||
@click="on_click(account)"
|
|
||||||
>
|
|
||||||
<div v-bind:class="getClass">
|
|
||||||
<div class="ellipsis" :style="{ width: '85%' }">
|
|
||||||
<div class="account-card-title ellipsis bold">{{ title }}</div>
|
|
||||||
<div class="account-card-subtitle ellipsis text-muted" v-html='subtitle'></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'account-card',
|
|
||||||
props: ['account', 'account_id_fieldname', 'on_click', 'selected_account'],
|
|
||||||
computed: {
|
|
||||||
title() {
|
|
||||||
const account_name = this.account.name;
|
|
||||||
return account_name;
|
|
||||||
},
|
|
||||||
subtitle() {
|
|
||||||
return "Test"
|
|
||||||
},
|
|
||||||
getClass() {
|
|
||||||
let value = (this.account.name == this.selected_account.name) ? "account-card-header flex justify-between selected" : "account-card-header flex justify-between"
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
method: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
@import "../../../../../../frappe/frappe/public/less/variables.less";
|
|
||||||
.account-card {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid @border-color;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover .account-card-overlay {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.account-card-header {
|
|
||||||
position: relative;
|
|
||||||
padding: 12px 15px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
.account-card-body {
|
|
||||||
position: relative;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
.account-card-overlay {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
.account-card-overlay-body {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.account-card-overlay-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 15px;
|
|
||||||
bottom: 15px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="bank-accounts-container">
|
|
||||||
<account-card
|
|
||||||
v-for="account in accounts"
|
|
||||||
:key="container_name + '_' +account[account_id_fieldname]"
|
|
||||||
:account="account"
|
|
||||||
:on_click="on_click"
|
|
||||||
:account_id_fieldname="account_id_fieldname"
|
|
||||||
:selected_account="selected_account"
|
|
||||||
>
|
|
||||||
</account-card>
|
|
||||||
<new-account-card
|
|
||||||
v-if="accounts.length > 0 && !show_plaid_link"
|
|
||||||
>
|
|
||||||
</new-account-card>
|
|
||||||
<PlaidLink
|
|
||||||
v-if="show_plaid_link"
|
|
||||||
:env="plaid_env"
|
|
||||||
:publicKey="plaid_public_key"
|
|
||||||
:clientName="client_name"
|
|
||||||
:product='["transactions", "auth"]'
|
|
||||||
v-bind="{ plaidSuccess }"
|
|
||||||
:subtitle="plaid_subtitle">
|
|
||||||
</PlaidLink>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import AccountCard from './AccountCard.vue';
|
|
||||||
import NewAccountCard from './NewAccountCard.vue';
|
|
||||||
import PlaidLink from '../components/PlaidLink.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'account-cards-container',
|
|
||||||
props: {
|
|
||||||
container_name: String,
|
|
||||||
accounts: Array,
|
|
||||||
account_id_fieldname: String,
|
|
||||||
is_local: Boolean,
|
|
||||||
on_click: Function,
|
|
||||||
editable: Boolean,
|
|
||||||
selected_account: Object,
|
|
||||||
show_plaid_link: Boolean,
|
|
||||||
plaid_env: String,
|
|
||||||
plaid_public_key: String,
|
|
||||||
client_name: String,
|
|
||||||
plaidSuccess: Function,
|
|
||||||
plaid_subtitle: String
|
|
||||||
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
AccountCard,
|
|
||||||
NewAccountCard,
|
|
||||||
PlaidLink
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
section_title: __("Please select a bank account"),
|
|
||||||
onSuccess: this.plaid_on_success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.bank-accounts-container {
|
|
||||||
margin: 0 -15px;
|
|
||||||
overflow: overlay;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="empty-state flex flex-column"
|
|
||||||
:class="{ 'bordered': bordered, 'align-center': centered, 'justify-center': centered }"
|
|
||||||
:style="{ height: height + 'px' }"
|
|
||||||
>
|
|
||||||
<p class="text-muted">{{ message }}</p>
|
|
||||||
<p v-if="action">
|
|
||||||
<button class="btn btn-default btn-xs"
|
|
||||||
@click="action.on_click"
|
|
||||||
>
|
|
||||||
{{ action.label }}
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'empty-state',
|
|
||||||
props: {
|
|
||||||
message: String,
|
|
||||||
bordered: Boolean,
|
|
||||||
height: Number,
|
|
||||||
action: Object,
|
|
||||||
centered: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less">
|
|
||||||
@import "../../../../../../frappe/frappe/public/less/variables.less";
|
|
||||||
.empty-state {
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
.empty-state.bordered {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid @border-color;
|
|
||||||
border-style: dashed;
|
|
||||||
// bad, due to item card column layout, that is inner 15px margin
|
|
||||||
margin: 0 15px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="col-md-3 col-sm-4 col-xs-6 new-account-card-container">
|
|
||||||
<div class="account-card text-center"
|
|
||||||
@click="on_click()"
|
|
||||||
>
|
|
||||||
<div class="account-card-header flex justify-between">
|
|
||||||
<div class="ellipsis">
|
|
||||||
<div class="new-account-card-text ellipsis text-muted" v-html='subtitle'></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'new-account-card',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
subtitle: __("Add a new account")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
on_click() {
|
|
||||||
frappe.new_doc("Bank Account")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
@import "../../../../../../frappe/frappe/public/less/variables.less";
|
|
||||||
.account-card {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid @border-color;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-style: dashed;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover .account-card-overlay {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.account-card-header {
|
|
||||||
position: relative;
|
|
||||||
padding: 12px 15px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
.account-card-body {
|
|
||||||
position: relative;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
.account-card-overlay {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
.account-card-overlay-body {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.account-card-overlay-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 15px;
|
|
||||||
bottom: 15px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<template>
|
|
||||||
<tr class="transaction-card text-center"
|
|
||||||
@click="on_click(transaction)"
|
|
||||||
>
|
|
||||||
<td>{{ transaction.description }}</td>
|
|
||||||
<td>{{ amount }}</td>
|
|
||||||
<td>{{ transaction.currency }}</td>
|
|
||||||
<td>{{ date }}</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'transaction-card',
|
|
||||||
props: ['transaction', 'transaction_id_fieldname', 'on_click', 'selected_transaction'],
|
|
||||||
computed: {
|
|
||||||
amount() {
|
|
||||||
const amount = (parseFloat(this.transaction.credit) > 0) ? -Math.abs(parseFloat(this.transaction.credit)) : parseFloat(this.transaction.debit);
|
|
||||||
return amount;
|
|
||||||
},
|
|
||||||
date() {
|
|
||||||
const date = moment(this.transaction.date)
|
|
||||||
return frappe.datetime.obj_to_user(date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
@import "../../../../../../frappe/frappe/public/less/variables.less";
|
|
||||||
.transaction-card {
|
|
||||||
height: 60px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
td {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="transactions-container">
|
|
||||||
<empty-state
|
|
||||||
v-if="transactions.length === 0"
|
|
||||||
:message="empty_state_message"
|
|
||||||
:action="empty_state_action"
|
|
||||||
:bordered="false"
|
|
||||||
:height="empty_state_height"
|
|
||||||
/>
|
|
||||||
<table class="table table-bordered table-hover" v-if="transactions.length > 0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{ __('Description') }}</th>
|
|
||||||
<th>{{ __('Amount') }}</th>
|
|
||||||
<th>{{ __('Currency') }}</th>
|
|
||||||
<th>{{ __('Date') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<transaction-card
|
|
||||||
v-for="transaction in transactions"
|
|
||||||
:key="container_name + '_' +transaction[transaction_id_fieldname]"
|
|
||||||
:transaction="transaction"
|
|
||||||
:on_click="on_click"
|
|
||||||
:transaction_id_fieldname="transaction_id_fieldname"
|
|
||||||
:selected_transaction="selected_transaction"
|
|
||||||
>
|
|
||||||
</transaction-card>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import TransactionCard from './TransactionCard.vue';
|
|
||||||
import EmptyState from './EmptyState.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'transactions-container',
|
|
||||||
props: {
|
|
||||||
container_name: String,
|
|
||||||
transactions: Array,
|
|
||||||
transaction_id_fieldname: String,
|
|
||||||
is_local: Boolean,
|
|
||||||
on_click: Function,
|
|
||||||
editable: Boolean,
|
|
||||||
empty_state_message: String,
|
|
||||||
empty_state_action: Object,
|
|
||||||
empty_state_height: Number,
|
|
||||||
empty_state_bordered: Boolean,
|
|
||||||
selected_transaction: Object
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
TransactionCard,
|
|
||||||
EmptyState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.transactions-container {
|
|
||||||
margin: 35px -15px;
|
|
||||||
overflow: overlay;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
tr {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="dashboard-container">
|
|
||||||
<div>
|
|
||||||
Dashboard
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['company'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="reconciliation-container">
|
|
||||||
<bank-accounts-container
|
|
||||||
:container_name="page_title"
|
|
||||||
:accounts="accounts"
|
|
||||||
:account_id_fieldname="account_id_fieldname"
|
|
||||||
:on_click="select_account"
|
|
||||||
:empty_state_message="empty_state_message"
|
|
||||||
:selected_account="selected_account"
|
|
||||||
:show_plaid_link="show_plaid_link"
|
|
||||||
:plaid_env="plaid_env"
|
|
||||||
:plaid_public_key="plaid_public_key"
|
|
||||||
:client_name="client_name"
|
|
||||||
:plaidSuccess="plaid_success"
|
|
||||||
:plaid_subtitle="plaid_subtitle"
|
|
||||||
>
|
|
||||||
</bank-accounts-container>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div ref="from-date" class="col-xs-6"></div>
|
|
||||||
<div ref="to-date" class="col-xs-6"></div>
|
|
||||||
</div>
|
|
||||||
<div v-show="mandatory_fields_completed" class="text-center">
|
|
||||||
<button
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="sync_account">
|
|
||||||
{{ __('Synchronize this account') }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="get_transactions">
|
|
||||||
{{ __('Get unreconciled transactions') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<transactions-container
|
|
||||||
:container_name="page_title"
|
|
||||||
:transactions="transactions"
|
|
||||||
:transaction_id_fieldname="transaction_id_fieldname"
|
|
||||||
:on_click="select_transaction"
|
|
||||||
:empty_state_message="empty_state_message"
|
|
||||||
:selected_transaction="selected_transaction"
|
|
||||||
>
|
|
||||||
</transactions-container>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import BankAccountsContainer from '../components/BankAccountsContainer.vue';
|
|
||||||
import TransactionsContainer from '../components/TransactionsContainer.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
company: String,
|
|
||||||
accounts: Array,
|
|
||||||
getBankAccounts: Function
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
BankAccountsContainer,
|
|
||||||
TransactionsContainer
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
account_id_fieldname: 'name',
|
|
||||||
page_title: __('Accounts'),
|
|
||||||
empty_state_message: __(`Please select an account first.`),
|
|
||||||
selected_account: {},
|
|
||||||
bank_entries: {},
|
|
||||||
transactions: [],
|
|
||||||
transaction_id_fieldname: 'name',
|
|
||||||
selected_transaction: {},
|
|
||||||
client_name: "Test App",
|
|
||||||
plaid_env : null,
|
|
||||||
plaid_public_key: null,
|
|
||||||
show_plaid_link: false,
|
|
||||||
plaid_subtitle: __("Add a new account")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
let me = this;
|
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
|
|
||||||
.then(result => {
|
|
||||||
me.plaid_env = result.plaid_env;
|
|
||||||
me.plaid_public_key = result.plaid_public_key;
|
|
||||||
me.client_name = result.client_name;
|
|
||||||
me.show_plaid_link = true;
|
|
||||||
})
|
|
||||||
|
|
||||||
this.getBankAccounts();
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
mandatory_fields_completed() {
|
|
||||||
if (this.selected_account.name !== undefined) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
select_account(account) {
|
|
||||||
if (this.selected_account == account) {
|
|
||||||
this.selected_account = {}
|
|
||||||
} else {
|
|
||||||
this.selected_account = account
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get_transactions() {
|
|
||||||
frappe.db.get_list('Bank Transaction', {
|
|
||||||
fields: ['name', 'date', 'status', 'debit', 'credit', 'currency', 'description'],
|
|
||||||
filters: {"docstatus": 1},
|
|
||||||
or_filters: [['reference_number', '=', '']]
|
|
||||||
|
|
||||||
}).then((transactions) => {
|
|
||||||
this.transactions = transactions;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
plaid_success(token, response) {
|
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
|
|
||||||
.then((result) => {
|
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response, bank: result})
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
this.getBankAccounts();
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
sync_account() {
|
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
|
|
||||||
bank: this.selected_account.bank,
|
|
||||||
bank_account: this.selected_account.name
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
this.get_transactions();
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
select_transaction() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang="less" scoped>
|
|
||||||
button {
|
|
||||||
margin: 25px 25px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<bank-accounts-container
|
|
||||||
:container_name="page_title"
|
|
||||||
:accounts="accounts"
|
|
||||||
:account_id_fieldname="account_id_fieldname"
|
|
||||||
:on_click="select_account"
|
|
||||||
:empty_state_message="empty_state_message"
|
|
||||||
:selected_account="selected_account"
|
|
||||||
>
|
|
||||||
</bank-accounts-container>
|
|
||||||
|
|
||||||
<div v-show="selected_account.name !== undefined">
|
|
||||||
<hr>
|
|
||||||
<div ref="upload-container" class="upload-btn-container"></div>
|
|
||||||
<div class="table-container"></div>
|
|
||||||
|
|
||||||
<div v-show="datatable_not_empty">
|
|
||||||
<button
|
|
||||||
class="btn btn-primary btn-xl"
|
|
||||||
@click="add_bank_entries">
|
|
||||||
{{ __('Add bank entries') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import DataTable from 'frappe-datatable';
|
|
||||||
import BankAccountsContainer from '../components/BankAccountsContainer.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
company: String,
|
|
||||||
accounts: Array,
|
|
||||||
getBankAccounts: Function
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
BankAccountsContainer
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
account_id_fieldname: 'name',
|
|
||||||
page_title: __('Accounts'),
|
|
||||||
empty_state_message: __(`You haven't added any bank account yet.`),
|
|
||||||
selected_account: {},
|
|
||||||
bank_entries: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getBankAccounts();
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.add_upload_section();
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
datatable_not_empty() {
|
|
||||||
return Object.keys(this.bank_entries).length > 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
add_upload_section() {
|
|
||||||
let me = this;
|
|
||||||
let wrapper = $(this.$refs['upload-container']);
|
|
||||||
frappe.upload.make({
|
|
||||||
parent: wrapper,
|
|
||||||
args: {
|
|
||||||
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
|
|
||||||
allow_multiple: 0
|
|
||||||
},
|
|
||||||
no_socketio: true,
|
|
||||||
sample_url: "e.g. http://example.com/somefile.csv",
|
|
||||||
callback: function(attachment, r) {
|
|
||||||
if (!r.exc && r.message) {
|
|
||||||
me.bank_entries = new DataTable('.table-container', {
|
|
||||||
columns: r.message.columns,
|
|
||||||
data: r.message.data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
select_account(account) {
|
|
||||||
if (this.selected_account == account) {
|
|
||||||
this.selected_account = {}
|
|
||||||
} else {
|
|
||||||
this.selected_account = account
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
upload_file() {
|
|
||||||
erpnext.bankreconciliation.upload_statement.show()
|
|
||||||
},
|
|
||||||
|
|
||||||
add_bank_entries() {
|
|
||||||
console.log(this.bank_entries.datamanager)
|
|
||||||
frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries',
|
|
||||||
{columns: this.bank_entries.datamanager.columns, data: this.bank_entries.datamanager.data, bank_account: this.selected_account}
|
|
||||||
).then((result) => {
|
|
||||||
console.log(result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang="less" scoped>
|
|
||||||
button {
|
|
||||||
margin-top: 35px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
frappe.provide('erpnext.bankreconciliation');
|
|
||||||
|
|
||||||
frappe.views.bankreconciliationFactory = class bankreconciliationFactory extends frappe.views.Factory {
|
|
||||||
show() {
|
|
||||||
if (frappe.pages.bankreconciliation) {
|
|
||||||
frappe.container.change_to('bankreconciliation');
|
|
||||||
} else {
|
|
||||||
this.make('bankreconciliation');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
make(page_name) {
|
|
||||||
const assets = [
|
|
||||||
'/assets/js/bankreconciliation.min.js'
|
|
||||||
];
|
|
||||||
frappe.require(assets, () => {
|
|
||||||
erpnext.bankreconciliation.home = new erpnext.bankreconciliation.Home({
|
|
||||||
parent: this.make_page(true, page_name)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$(document).on('toolbar_setup', () => {
|
|
||||||
$('#toolbar-user .navbar-reload').after(`
|
|
||||||
<li>
|
|
||||||
<a class="bankreconciliation-link" href="#bankreconciliation/home">${__("Bank Reconciliation")}</a>
|
|
||||||
</li>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import Vue from 'vue/dist/vue.js';
|
|
||||||
import './vue-plugins';
|
|
||||||
|
|
||||||
import Home from './Home.vue';
|
|
||||||
import Sidebar from './Sidebar.vue';
|
|
||||||
|
|
||||||
import EventEmitter from '../hub/event_emitter';
|
|
||||||
|
|
||||||
frappe.provide('erpnext.bankreconciliation');
|
|
||||||
frappe.provide('frappe.route');
|
|
||||||
frappe.provide('frappe.upload');
|
|
||||||
|
|
||||||
$.extend(erpnext.bankreconciliation, EventEmitter.prototype);
|
|
||||||
$.extend(frappe.route, EventEmitter.prototype);
|
|
||||||
|
|
||||||
erpnext.bankreconciliation.Home = class bankreconciliation {
|
|
||||||
constructor({ parent }) {
|
|
||||||
this.$parent = $(parent);
|
|
||||||
this.page = parent.page;
|
|
||||||
this.company = frappe.defaults.get_user_default("Company");
|
|
||||||
this.setup_header();
|
|
||||||
this.make_sidebar();
|
|
||||||
this.make_body();
|
|
||||||
this.setup_events();
|
|
||||||
this.set_secondary_action();
|
|
||||||
}
|
|
||||||
|
|
||||||
make_sidebar() {
|
|
||||||
this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs');
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: $('<div>').appendTo(this.$sidebar)[0],
|
|
||||||
render: h => h(Sidebar)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
make_body() {
|
|
||||||
let me = this;
|
|
||||||
me.$body = me.$parent.find('.layout-main-section');
|
|
||||||
me.$page_container = $('<div class="bankreconciliation-page-container">').appendTo(this.$body);
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: me.$page_container[0],
|
|
||||||
render(h) {
|
|
||||||
return h(Home, {props: { initCompany: me.company}})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_header() {
|
|
||||||
this.page.set_title(__('Bank Reconciliation'));
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_events() {
|
|
||||||
this.$parent.on('click', '[data-route]', (e) => {
|
|
||||||
const $target = $(e.currentTarget);
|
|
||||||
const route = $target.data().route;
|
|
||||||
frappe.set_route(route);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$parent.on('click', '[data-action]', e => {
|
|
||||||
const $target = $(e.currentTarget);
|
|
||||||
const action = $target.data().action;
|
|
||||||
|
|
||||||
if (action && this[action]) {
|
|
||||||
this[action].apply(this, $target);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
set_secondary_action() {
|
|
||||||
let me = this;
|
|
||||||
this.page.set_secondary_action(this.company, function () {
|
|
||||||
me.company_selection_dialog();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
company_selection_dialog() {
|
|
||||||
let me = this;
|
|
||||||
let dialog = new frappe.ui.Dialog({
|
|
||||||
title: __('Select another company'),
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
"label": "Company",
|
|
||||||
"fieldname": "company",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Company"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
primary_action_label: __('Confirm'),
|
|
||||||
primary_action: function(v) {
|
|
||||||
me.company = v.company;
|
|
||||||
erpnext.bankreconciliation.trigger('company_changed', v.company);
|
|
||||||
me.set_secondary_action();
|
|
||||||
dialog.hide();
|
|
||||||
},
|
|
||||||
})
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import Vue from 'vue/dist/vue.js';
|
|
||||||
|
|
||||||
Vue.prototype.__ = window.__;
|
|
||||||
Vue.prototype.frappe = window.frappe;
|
|
||||||
|
|
||||||
Vue.directive('route', {
|
|
||||||
bind(el, binding) {
|
|
||||||
const route = binding.value;
|
|
||||||
if (!route) return;
|
|
||||||
el.classList.add('cursor-pointer');
|
|
||||||
el.dataset.route = route;
|
|
||||||
el.addEventListener('click', () => frappe.set_route(route));
|
|
||||||
},
|
|
||||||
unbind(el) {
|
|
||||||
el.classList.remove('cursor-pointer');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
|
|
||||||
@import "variables.less";
|
|
||||||
@import (reference) 'common.less';
|
|
||||||
|
|
||||||
body[data-route*="bankreconciliation"] {
|
|
||||||
|
|
||||||
.layout-side-section {
|
|
||||||
padding-top: 25px;
|
|
||||||
padding-left: 5px;
|
|
||||||
padding-right: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-route], [data-action] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-main-section {
|
|
||||||
border: none;
|
|
||||||
font-size: @text-medium;
|
|
||||||
padding-top: 25px;
|
|
||||||
|
|
||||||
@media (max-width: @screen-xs) {
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input, textarea {
|
|
||||||
font-size: @text-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bankreconciliation-sidebar {
|
|
||||||
padding-top: 25px;
|
|
||||||
padding-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bankreconciliation-sidebar-group {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bankreconciliation-sidebar-item {
|
|
||||||
padding: 5px 8px;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
|
|
||||||
&.active, &:hover:not(.is-title) {
|
|
||||||
border-color: @border-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container {
|
|
||||||
.frappe-control {
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-btn-container {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-card {
|
|
||||||
.selected {
|
|
||||||
background-color: #e2f4d0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-container {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
|
|
||||||
.app-icon-svg {
|
.app-icon-svg {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15);
|
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,3 +459,30 @@ body[data-route="pos"] {
|
|||||||
padding-right: 45px;
|
padding-right: 45px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bank Reconciliation
|
||||||
|
|
||||||
|
.plaid-btn {
|
||||||
|
margin-top: 24px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #5bc0de;
|
||||||
|
border-color: #46b8da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transactions-btn {
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-fieldname='reconcile_data'],
|
||||||
|
[data-fieldname='sync_data'],
|
||||||
|
[data-fieldname='import_data'] {
|
||||||
|
.btn {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #8d99a6;
|
||||||
|
border-color: #7f8c9b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-fieldname='table_container'] {
|
||||||
|
margin: -15px -30px;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user