2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 03:19:01 +00:00

* Dynamic Filters

* Report Actions
This commit is contained in:
thefalconx33 2019-07-18 12:19:12 +05:30
parent 0a4e127b79
commit 90411d3ec9
15 changed files with 391 additions and 219 deletions

View File

@ -18,7 +18,7 @@ module.exports = {
{
fieldname: 'date',
label: 'Date',
fieldtype: 'Date',
fieldtype: 'Date'
// default: (new Date()).toISOString()
},
{
@ -27,7 +27,13 @@ module.exports = {
fieldtype: 'Link',
target: 'Party',
required: 1,
getFilters: (query) => {
getFilters: query => {
if (query)
return {
keywords: ['like', query],
customer: 1
};
return {
customer: 1
};
@ -38,8 +44,14 @@ module.exports = {
label: 'Account',
fieldtype: 'Link',
target: 'Account',
formula: (doc) => doc.getFrom('Party', doc.customer , 'defaultAccount'),
formula: doc => doc.getFrom('Party', doc.customer, 'defaultAccount'),
getFilters: (query, control) => {
if (query)
return {
keywords: ['like', query],
isGroup: 0,
accountType: 'Receivable'
};
return {
isGroup: 0,
accountType: 'Receivable'
@ -57,7 +69,7 @@ module.exports = {
fieldname: 'netTotal',
label: 'Net Total',
fieldtype: 'Currency',
formula: (doc) => doc.getSum('items', 'amount'),
formula: doc => doc.getSum('items', 'amount'),
disabled: true,
readOnly: 1
},
@ -85,7 +97,7 @@ module.exports = {
fieldname: 'grandTotal',
label: 'Grand Total',
fieldtype: 'Currency',
formula: (doc) => doc.getGrandTotal(),
formula: doc => doc.getGrandTotal(),
disabled: true,
readOnly: 1
},
@ -105,31 +117,22 @@ module.exports = {
layout: [
// section 1
{
columns: [
{ fields: ['customer', 'account'] },
{ fields: ['date'] }
]
columns: [{ fields: ['customer', 'account'] }, { fields: ['date'] }]
},
// section 2
{
columns: [
{ fields: ['items'] }
]
columns: [{ fields: ['items'] }]
},
// section 3
{
columns: [
{ fields: ['netTotal', 'taxes', 'grandTotal'] }
]
columns: [{ fields: ['netTotal', 'taxes', 'grandTotal'] }]
},
// section 4
{
columns: [
{ fields: ['terms'] }
]
columns: [{ fields: ['terms'] }]
}
],
@ -137,18 +140,25 @@ module.exports = {
utils.ledgerLink,
{
label: 'Make Payment',
condition: form => form.doc.submitted && form.doc.outstandingAmount !== 0.0,
condition: form =>
form.doc.submitted && form.doc.outstandingAmount !== 0.0,
action: async form => {
const payment = await frappe.getNewDoc('Payment');
payment.party = form.doc.customer;
payment.account = form.doc.account;
payment.for = [{ referenceType: form.doc.doctype, referenceName: form.doc.name, amount: form.doc.grandTotal }];
payment.for = [
{
referenceType: form.doc.doctype,
referenceName: form.doc.name,
amount: form.doc.grandTotal
}
];
payment.on('afterInsert', async () => {
form.$formModal.close();
const _payment = await frappe.getDoc('Payment', payment.name);
await _payment.submit();
})
});
await form.$formModal.open(payment);
}
}

View File

@ -1,54 +1,67 @@
module.exports = {
"name": "Party",
"doctype": "DocType",
"isSingle": 0,
"istable": 0,
"keywordFields": [
"name"
],
"fields": [{
"fieldname": "name",
"label": "Name",
"fieldtype": "Data",
"required": 1
},
{
fieldname: "address",
label: "Address",
fieldtype: "Link",
target: "Address"
},
{
fieldname: 'defaultAccount',
label: 'Default Account',
fieldtype: 'Link',
target: 'Account',
getFilters: (query, control) => {
return {
isGroup: 0,
accountType: 'Receivable'
};
}
},
{
"fieldname": "customer",
"label": "Customer",
"fieldtype": "Check"
},
{
"fieldname": "supplier",
"label": "Supplier",
"fieldtype": "Check"
}
],
name: 'Party',
doctype: 'DocType',
documentClass: require('./PartyDocument.js'),
isSingle: 0,
istable: 0,
keywordFields: ['name'],
fields: [
{
fieldname: 'name',
label: 'Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'address',
label: 'Address',
fieldtype: 'Link',
target: 'Address'
},
{
fieldname: 'defaultAccount',
label: 'Default Account',
fieldtype: 'Link',
target: 'Account',
getFilters: (query, doc) => {
return {
isGroup: 0,
accountType: doc.customer === 1 ? 'Receivable' : 'Payable'
};
}
},
{
fieldname: 'customer',
label: 'Customer',
fieldtype: 'Check'
},
{
fieldname: 'supplier',
label: 'Supplier',
fieldtype: 'Check'
}
],
links: [{
label: 'Invoices',
condition: (form) => form.doc.customer,
action: form => {
form.$router.push({
path: `/report/sales-register?&customer=${form.doc.name}`
});
}
}]
}
links: [
{
label: 'Invoices',
condition: form => form.doc.customer,
action: form => {
form.$router.push({
path: `/report/sales-register?&customer=${form.doc.name}`
});
}
},
{
label: 'Delete',
condition: form => form.doc.customer,
action: async form => {
const party = await frappe.getDoc('Party', form.doc.name);
await party.delete();
form.$router.push({
path: `/list/Customer`
});
}
}
]
};

View File

@ -0,0 +1,9 @@
const BaseDocument = require('frappejs/model/document');
module.exports = class Party extends BaseDocument {
vaildate() {
if (this.customer && this.supplier) {
frappe.msgDialog('Invalid Party Type');
}
}
};

View File

@ -0,0 +1,10 @@
import { _ } from 'frappejs/utils';
export default {
doctype: 'Party',
title: _('Supplier'),
columns: ['name'],
filters: {
supplier: 1
}
};

View File

@ -14,11 +14,64 @@ class GeneralLedger {
}
let data = await frappe.db.getAll({
doctype: 'AccountingLedgerEntry',
fields: ['date', 'account', 'party', 'referenceType', 'referenceName', 'debit', 'credit'],
fields: [
'date',
'account',
'party',
'referenceType',
'referenceName',
'debit',
'credit'
],
filters: filters
});
return data;
return this.appendOpeningEntry(data);
}
appendOpeningEntry(data) {
let glEntries = [];
let balance = 0,
debitTotal = 0,
creditTotal = 0;
glEntries.push({
date: '',
account: 'Opening',
party: '',
debit: 0,
credit: 0,
balance: 0,
referenceType: '',
referenceName: ''
});
for (let entry of data) {
balance += entry.debit > 0 ? entry.debit : -entry.credit;
debitTotal += entry.debit;
creditTotal += entry.credit;
entry.balance = balance;
glEntries.push(entry);
}
glEntries.push({
date: '',
account: 'Total',
party: '',
debit: debitTotal,
credit: creditTotal,
balance: balance,
referenceType: '',
referenceName: ''
});
glEntries.push({
date: '',
account: 'Closing',
party: '',
debit: debitTotal,
credit: creditTotal,
balance: balance,
referenceType: '',
referenceName: ''
});
return glEntries;
}
}

View File

@ -5,7 +5,8 @@ module.exports = class GeneralLedgerView extends ReportPage {
constructor() {
super({
title: frappe._('General Ledger'),
filterFields: [{
filterFields: [
{
fieldtype: 'Select',
options: ['', 'Invoice', 'Payment'],
label: 'Reference Type',
@ -42,7 +43,8 @@ module.exports = class GeneralLedgerView extends ReportPage {
}
getColumns() {
return [{
return [
{
label: 'Date',
fieldtype: 'Date'
},

View File

@ -1,44 +1,78 @@
const title = 'General Ledger';
module.exports = {
title: title,
let title = 'General Ledger';
let filterFields = [
{
fieldtype: 'Select',
options: ['', 'Invoice', 'Payment'],
label: 'Reference Type',
fieldname: 'referenceType'
},
{
fieldtype: 'DynamicLink',
references: 'referenceType',
label: 'Reference Name',
fieldname: 'referenceName'
},
{
fieldtype: 'Link',
target: 'Account',
label: 'Account',
fieldname: 'account'
},
{
fieldtype: 'Link',
target: 'Party',
label: 'Party',
fieldname: 'party'
},
{
fieldtype: 'Date',
label: 'From Date',
fieldname: 'fromDate'
},
{
fieldtype: 'Date',
label: 'To Date',
fieldname: 'toDate'
}
];
const viewConfig = {
title,
filterFields,
method: 'general-ledger',
filterFields: [{
fieldtype: 'Select',
options: ['', 'Invoice', 'Payment'],
label: 'Reference Type',
fieldname: 'referenceType'
},
linkFields: [
{
fieldtype: 'DynamicLink',
references: 'referenceType',
label: 'Reference Name',
fieldname: 'referenceName'
},
{
fieldtype: 'Link',
target: 'Account',
label: 'Account',
fieldname: 'account'
},
{
fieldtype: 'Link',
target: 'Party',
label: 'Party',
fieldname: 'party'
},
{
fieldtype: 'Date',
label: 'From Date',
fieldname: 'fromDate'
},
{
fieldtype: 'Date',
label: 'To Date',
fieldname: 'toDate'
label: 'Export',
action: async report => {
async function getReportDetails() {
let [rows, columns] = await report.getReportData(filterFields);
let columnData = columns.map(column => {
return {
id: column.id,
content: column.content,
checked: true
};
});
return {
title: title,
rows: rows,
columnData: columnData
};
}
report.$modal.show({
modalProps: {
title: `Export ${title}`,
noFooter: true
},
component: require('../../src/components/ExportWizard').default,
props: await getReportDetails()
});
}
}
],
getColumns() {
return [{
return [
{
label: 'Date',
fieldtype: 'Date'
},
@ -77,3 +111,5 @@ module.exports = {
];
}
};
module.exports = viewConfig;

View File

@ -8,26 +8,28 @@
<!-- <transition-group name="slide-fade-group"> -->
<div v-for="group in groups" :key="group">
<div
:class="['sidebar-item px-3 py-2 ', activeGroup === group ? 'active' : '']"
:class="['sidebar-item px-1 py-2', activeGroup === group ? 'active' : '']"
@click="toggleGroup(group)"
style="user-select: none;"
>
{{
group }}
<div class="d-flex align-items-center">
<feather-icon
class="mr-1"
:name="openGroup === group ? 'chevron-down' : 'chevron-right'"
/>
{{group }}
</div>
</div>
<transition name="slide-fade">
<div v-if="openGroup === group">
<div
v-for="item in groupItems"
style="user-select: none;"
:class="['sidebar-item px-3 py-2 ', isCurrentRoute(item.route) ? 'active' : '']"
:class="['sidebar-item pl-4 py-2 ', isCurrentRoute(item.route) ? 'active' : '']"
@click="routeTo(item.route)"
:key="item.label"
>
<div class="d-flex align-items-center">
<feather-icon class="mr-1" name="chevron-right" />
{{ item.label }}
</div>
<div class="d-flex align-items-center">{{ item.label }}</div>
</div>
</div>
</transition>
@ -55,45 +57,45 @@ export default {
groups: [],
groupItems: [],
activeGroup: undefined,
openGroup: undefined,
items: [
{
label: 'Chart of Accounts',
route: '/chartOfAccounts'
},
{
label: 'Customers',
route: '/list/Customer'
},
{
label: 'Items',
route: '/list/Item'
},
{
label: 'Tax',
route: '/list/Tax'
},
{
label: 'Payments',
route: '/list/Payment'
},
{
label: 'Journal Entry',
route: '/list/JournalEntry'
},
{
label: 'Invoices',
route: '/list/Invoice'
},
{
label: 'Reports',
route: '/reportList'
},
{
label: 'Settings',
route: '/settings'
}
]
openGroup: undefined
// items: [
// {
// label: 'Chart of Accounts',
// route: '/chartOfAccounts'
// },
// {
// label: 'Customers',
// route: '/list/Customer'
// },
// {
// label: 'Items',
// route: '/list/Item'
// },
// {
// label: 'Tax',
// route: '/list/Tax'
// },
// {
// label: 'Payments',
// route: '/list/Payment'
// },
// {
// label: 'Journal Entry',
// route: '/list/JournalEntry'
// },
// {
// label: 'Invoices',
// route: '/list/Invoice'
// },
// {
// label: 'Reports',
// route: '/reportList'
// },
// {
// label: 'Settings',
// route: '/settings'
// }
// ]
};
},
async mounted() {
@ -144,7 +146,7 @@ export default {
&.active {
color: $white;
background-color: $primary;
background-color: $frappe;
}
}
.slide-fade-enter-active {

View File

@ -89,7 +89,9 @@ export default {
this.doc.on('change', this.setLinks);
} catch (e) {
this.notFound = true;
this.$router.push(`/list/${this.doctype}`); //if reloaded while insert new Item,Invoice etc form.
this.$router.push(
`/list/${this.doctype === 'Party' ? 'Customer' : this.doctype}`
); //if reloaded while insert new Item,Invoice etc form.
}
},
async save() {

View File

@ -5,7 +5,7 @@
</list-row>
<list-row v-for="doc in data" :key="doc.name" @click.native="openForm(doc.name)">
<list-cell v-for="column in columns" :key="column.label" class="d-flex align-items-center">
<indicator v-if="column.getIndicator" :color="column.getIndicator(doc)" class="mr-2"/>
<indicator v-if="column.getIndicator" :color="column.getIndicator(doc)" class="mr-2" />
<span>{{ frappe.format(column.getValue(doc), column.fieldtype || {}) }}</span>
</list-cell>
</list-row>
@ -25,7 +25,11 @@ export default {
},
watch: {
listConfig(oldValue, newValue) {
if (oldValue.doctype !== newValue.doctype) {
if (
oldValue.doctype !== newValue.doctype ||
// To differntiate Customer and Supplier List based on same Party Doctype
oldValue.title !== newValue.title
) {
this.setupColumnsAndData();
}
}
@ -36,17 +40,17 @@ export default {
data: []
};
},
mounted() {
this.setupColumnsAndData();
async mounted() {
await this.setupColumnsAndData();
frappe.listView.on('filterList', this.updateData.bind(this));
},
methods: {
setupColumnsAndData() {
async setupColumnsAndData() {
this.doctype = this.listConfig.doctype;
this.meta = frappe.getMeta(this.doctype);
this.prepareColumns();
this.updateData();
await this.prepareColumns();
await this.updateData();
},
openForm(name) {
this.$router.push(`/edit/${this.doctype}/${name}`);

View File

@ -1,5 +1,6 @@
import Invoice from '../../../models/doctype/Invoice/InvoiceList';
import Customer from '../../../models/doctype/Party/CustomerList';
import Supplier from '../../../models/doctype/Party/SupplierList';
import Item from '../../../models/doctype/Item/ItemList';
import Payment from '../../../models/doctype/Payment/PaymentList';
import Tax from '../../../models/doctype/Tax/TaxList';
@ -8,6 +9,7 @@ import JournalEntry from '../../../models/doctype/JournalEntry/JournalEntryList'
export default {
Invoice,
Customer,
Supplier,
Item,
Payment,
Tax,

View File

@ -1,27 +1,30 @@
<template>
<div>
<div class="p-4">
<h4 class="pb-2">{{ reportConfig.title }}</h4>
<div class="col-12 text-right mt-4 mb-4">
<f-button primary @click="openExportWizard">{{ 'Export' }}</f-button>
<div class="row pb-4">
<h4 class="col-6 d-flex">{{ reportConfig.title }}</h4>
<report-links class="col-6 d-flex pr-0 flex-row-reverse" v-if="linksExists" :links="links"></report-links>
</div>
<report-filters
v-if="filtersExists"
:filters="reportConfig.filterFields"
:filterDefaults="filters"
@change="getReportData"
></report-filters>
<div class="pt-2" ref="datatable" v-once></div>
<div class="row pb-4">
<report-filters
class="col-12 pr-0"
v-if="filtersExists"
:filters="reportConfig.filterFields"
:filterDefaults="filters"
@change="getReportData"
></report-filters>
</div>
<div class="pt-2 pr-3" ref="datatable" v-once></div>
</div>
<not-found v-if="!reportConfig"/>
<not-found v-if="!reportConfig" />
</div>
</template>
<script>
import DataTable from 'frappe-datatable';
import frappe from 'frappejs';
import ReportFilters from 'frappejs/ui/pages/Report/ReportFilters';
import ReportLinks from 'frappejs/ui/pages/Report/ReportLinks';
import utils from 'frappejs/client/ui/utils';
import ExportWizard from '../../components/ExportWizard';
export default {
name: 'Report',
@ -29,35 +32,21 @@ export default {
computed: {
filtersExists() {
return (this.reportConfig.filterFields || []).length;
},
linksExists() {
return (this.reportConfig.linkFields || []).length;
}
},
data() {
return {
links: []
};
},
async created() {
this.setLinks();
// this.doc.on('change', this.setLinks);
},
methods: {
async openExportWizard() {
this.$modal.show({
modalProps: {
title: `Export ${this.reportConfig.title}`,
noFooter: true
},
component: ExportWizard,
props: await this.getReportDetails()
});
},
async getReportDetails() {
let { title, filterFields } = this.reportConfig;
let [rows, columns] = await this.getReportData(filterFields || []);
let columnData = columns.map(column => {
return {
id: column.id,
content: column.content,
checked: true
};
});
return {
title: title,
rows: rows,
columnData: columnData
};
},
async getReportData(filters) {
let data = await frappe.call({
method: this.reportConfig.method,
@ -97,15 +86,33 @@ export default {
}
return [rows, columns];
},
setLinks() {
if (this.linksExists) {
let links = [];
for (let link of this.reportConfig.linkFields) {
links.push({
label: link.label,
handler: () => {
link.action(this);
}
});
}
this.links = links;
}
},
getColumns(data) {
const columns = this.reportConfig.getColumns(data);
return utils.convertFieldsToDatatableColumns(columns);
}
},
components: {
ReportFilters
ReportFilters,
ReportLinks
}
};
</script>
<style>
.datatable {
/* font-size: 12px; */
}
</style>

View File

@ -37,6 +37,10 @@ export default {
label: _('Customer'),
route: '/list/Customer'
},
{
label: _('Supplier'),
route: '/list/Supplier'
},
{
label: _('Tax'),
route: '/list/Tax'
@ -70,6 +74,10 @@ export default {
label: _('General Ledger'),
route: '/report/general-ledger'
},
{
label: _('Trial Balance'),
route: '/report/trial-balance'
},
{
label: _('Sales Register'),
route: '/report/sales-register'
@ -90,6 +98,10 @@ export default {
{
label: _('Data Import'),
route: '/data-import'
},
{
label: _('Settings'),
route: '/settings'
}
]
}

View File

@ -1,5 +1,9 @@
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import '~bootstrap/scss/functions';
@import '~bootstrap/scss/variables';
$dark: #252529;
$blue: #1665D8;
$blue: #1665d8;
$frappe: #5e64ff;
$theme-colors: (
'primary': #5e64ff
);

View File

@ -49,9 +49,9 @@
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*":
version "12.6.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.3.tgz#44d507c5634f85e7164707ca36bba21b5213d487"
integrity sha512-7TEYTQT1/6PP53NftXXabIZDaZfaoBdeBm8Md/i7zsWRoBe0YwOXguyK8vhHs8ehgB/w9U4K/6EWuTyp0W6nIA==
version "12.6.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.6.tgz#831587377c35bb28fa33b6fe5f849a26a3f4a412"
integrity sha512-SMgj3x28MkJyHdWaMv/g/ca3LYDi5gR7O8mX0VKazvFOnmlDXctSEdd/8jfSqozjKFK1R9If1QZWkafX7yQTpA==
"@types/node@^10.12.18":
version "10.14.12"
@ -2802,7 +2802,7 @@ frappe-datatable@^1.13.5:
"frappejs@github:thefalconx33/frappejs#test":
version "0.0.10"
resolved "https://codeload.github.com/thefalconx33/frappejs/tar.gz/6d6e904a2982de061d87f80d1b7e6a989fe5eae3"
resolved "https://codeload.github.com/thefalconx33/frappejs/tar.gz/8fb509d3c1a2db7eca95a1269d8bbf1096a65428"
dependencies:
awesomplete "^1.1.2"
babel-core "^6.26.3"
@ -2825,6 +2825,7 @@ frappe-datatable@^1.13.5:
express "^4.16.2"
feather-icons "^4.7.3"
file-loader "^1.1.11"
file-saver "^2.0.2"
flatpickr "^4.3.2"
frappe-datatable "^1.13.5"
friendly-errors-webpack-plugin "^1.7.0"
@ -3551,11 +3552,16 @@ ip@^1.1.0, ip@^1.1.5:
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
ipaddr.js@1.9.0, ipaddr.js@^1.9.0:
ipaddr.js@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
ipaddr.js@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
is-accessor-descriptor@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
@ -4537,9 +4543,9 @@ ms@^2.1.1:
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
multer@^1.3.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.1.tgz#24b12a416a22fec2ade810539184bf138720159e"
integrity sha512-zzOLNRxzszwd+61JFuAo0fxdQfvku12aNJgnla0AQ+hHxFmfc/B7jBVuPr5Rmvu46Jze/iJrFpSOsD7afO8SDw==
version "1.4.2"
resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a"
integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==
dependencies:
append-field "^1.0.0"
busboy "^0.2.11"