mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-19 03:17:55 +00:00
Bank reconciliation wip
This commit is contained in:
@@ -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 {
|
||||
display: inline-block;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
border-radius: 16px;
|
||||
cursor: pointer;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
border-radius: 16px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@@ -459,3 +459,30 @@ body[data-route="pos"] {
|
||||
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