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

- Bug Fixes

- PnL & Balance Sheet
- Search Keyboard Navigation
This commit is contained in:
thefalconx33 2019-08-20 14:27:27 +05:30
parent 54e115b030
commit 562b4759e0
27 changed files with 234 additions and 81 deletions

View File

@ -11,12 +11,14 @@ module.exports = class LedgerPosting {
async debit(account, amount, referenceType, referenceName) {
const entry = this.getEntry(account, referenceType, referenceName);
amount = frappe.parseNumber(amount);
entry.debit += amount;
await this.setAccountBalanceChange(account, 'debit', amount);
}
async credit(account, amount, referenceType, referenceName) {
const entry = this.getEntry(account, referenceType, referenceName);
amount = frappe.parseNumber(amount);
entry.credit += amount;
await this.setAccountBalanceChange(account, 'credit', amount);
}
@ -83,7 +85,6 @@ module.exports = class LedgerPosting {
let credit = 0;
let debitAccounts = [];
let creditAccounts = [];
for (let entry of this.entries) {
debit += entry.debit;
credit += entry.credit;
@ -93,11 +94,20 @@ module.exports = class LedgerPosting {
creditAccounts.push(entry.account);
}
}
debit = Math.floor(debit * 100) / 100;
credit = Math.floor(credit * 100) / 100;
if (debit !== credit) {
throw new frappe.errors.ValidationError(
frappe._('Debit {0} must be equal to Credit {1}', [debit, credit])
);
frappe.call({
method: 'show-dialog',
args: {
title: 'Invalid Entry',
message: frappe._('Debit {0} must be equal to Credit {1}', [
debit,
credit
])
}
});
throw new Error();
}
}

View File

@ -2,6 +2,6 @@ import { _ } from 'frappejs/utils';
export default {
doctype: 'Account',
title: _('Accounts'),
title: _('Account'),
columns: ['name', 'parentAccount', 'rootType']
};

View File

@ -118,7 +118,7 @@ module.exports = {
invoice.on('afterInsert', async () => {
form.$formModal.close();
form.$router.push({
path: `/edit/PurchaseInvoice/${invoice.name}`
path: `/edit/SalesInvoice/${invoice.name}`
});
});
await form.$formModal.open(invoice);

View File

@ -17,6 +17,7 @@ module.exports = {
label: 'Entry Type',
fieldtype: 'Select',
options: [
'Select...',
'Journal Entry',
'Bank Entry',
'Cash Entry',

View File

@ -30,13 +30,11 @@ module.exports = {
required: 1,
getFilters: (query, doc) => {
if (doc.paymentType === 'Pay') {
if (doc.paymentMethod === 'Cash')
if (doc.paymentMethod === 'Cash') {
return { accountType: 'Cash', isGroup: 0 };
else
return {
accountType: ['in', ['Bank', 'Cash']],
isGroup: 0
};
} else {
return { accountType: ['in', ['Bank', 'Cash']], isGroup: 0 };
}
}
}
},
@ -96,7 +94,6 @@ module.exports = {
required: 1,
disabled: true,
formula: doc => {
console.log(doc.getSum('for', 'amount'));
return frappe.format(doc.getSum('for', 'amount'), 'Currency');
}
},

View File

@ -23,13 +23,12 @@ module.exports = class PaymentServer extends BaseDocument {
if (outstandingAmount === null) {
outstandingAmount = grandTotal;
}
if (this.amount > outstandingAmount) {
if (0 >= this.amount || this.amount > outstandingAmount) {
frappe.call({
method: 'show-dialog',
args: {
title: 'Invalid Payment Entry',
message: `Payment amount is greater than Outstanding amount by \
${this.amount - outstandingAmount}`
message: `Payment amount is greater than 0 and less than Outstanding amount (${outstandingAmount})`
}
});
throw new Error();
@ -38,7 +37,8 @@ module.exports = class PaymentServer extends BaseDocument {
row.referenceType,
row.referenceName,
'outstandingAmount',
outstandingAmount - this.amount
frappe.parseNumber(outstandingAmount) -
frappe.parseNumber(this.amount)
);
}
}

View File

@ -22,9 +22,8 @@ module.exports = {
fieldname: 'amount',
label: 'Amount',
fieldtype: 'Currency',
formula: (row, doc) => {
doc.getFrom(doc.referenceType, doc.referenceName, 'grandTotal');
},
formula: (row, doc) =>
doc.getFrom(row.referenceType, row.referenceName, 'grandTotal'),
required: 1
}
]

View File

@ -127,9 +127,11 @@ module.exports = {
],
links: [
utils.ledgerLink,
{
label: 'Make Payment',
condition: form => form.doc.submitted,
condition: form =>
form.doc.submitted && form.doc.outstandingAmount != 0.0,
action: async form => {
const payment = await frappe.getNewDoc('Payment');
payment.paymentType = 'Pay';

View File

@ -85,9 +85,9 @@ module.exports = {
<div class='col-6'></div>
<div class='col-6'>
<div class='row' v-for='row in value'>
<div class='col-6'>{{row.account}} ({{row.rate}}%)</div>
<div class='col-6'>{{ row.account }} ({{row.rate}}%)</div>
<div class='col-6 text-right'>
{{frappe.format(row.amount, 'Currency')}}
{{ frappe.format(row.amount, 'Currency')}}
</div>
</div>
</div>
@ -98,7 +98,7 @@ module.exports = {
fieldname: 'grandTotal',
label: 'Grand Total',
fieldtype: 'Currency',
formula: doc => doc.getGrandTotal(),
formula: doc => frappe.format(doc.getGrandTotal(), 'Currency'),
disabled: true,
readOnly: 1
},
@ -142,7 +142,7 @@ module.exports = {
{
label: 'Make Payment',
condition: form =>
form.doc.submitted && form.doc.outstandingAmount !== 0.0,
form.doc.submitted && form.doc.outstandingAmount != 0.0,
action: async form => {
const payment = await frappe.getNewDoc('Payment');
payment.paymentType = 'Receive';

View File

@ -76,6 +76,6 @@ module.exports = class SalesInvoice extends BaseDocument {
}
grandTotal = Math.floor(grandTotal * 100) / 100;
return frappe.format(grandTotal, 'Currency');
return grandTotal;
}
};

View File

@ -20,7 +20,7 @@ module.exports = {
fieldtype: 'Select',
options: ['Basic I', 'Basic II', 'Modern'],
required: 1,
defaultValue: 'Basic I'
default: 'Basic I'
},
{
fieldname: 'font',
@ -28,14 +28,14 @@ module.exports = {
fieldtype: 'Select',
options: ['Montserrat', 'Open Sans', 'Oxygen', 'Merriweather'],
required: 1,
defaultValue: 'Montserrat'
default: 'Montserrat'
},
{
fieldname: 'themeColor',
label: 'Theme Color',
fieldtype: 'Data',
required: 1,
defaultValue: '#000000',
default: '#000000',
hidden: 1
}
]

View File

@ -86,6 +86,12 @@ module.exports = {
return countryList[doc.country].currency;
},
required: 1
},
{
fieldname: 'completed',
label: 'Completed',
fieldtype: 'Check',
readonly: 1
}
],

View File

@ -61,7 +61,7 @@
"cross-env": "^5.2.0",
"file-saver": "^2.0.2",
"frappe-charts": "^1.2.4",
"frappejs": "github:thefalconx33/frappejs#test",
"frappejs": "https://github.com/thefalconx33/frappejs#test",
"nodemailer": "^4.7.0",
"popper.js": "^1.14.4",
"vue-color": "^2.7.0",

View File

@ -0,0 +1,46 @@
module.exports = {
title: 'Balance Sheet',
method: 'balance-sheet',
filterFields: [
{
fieldtype: 'Date',
fieldname: 'toDate',
size: 'small',
placeholder: 'ToDate',
label: 'To Date',
required: 1
},
{
fieldtype: 'Select',
size: 'small',
options: [
'Select Period...',
'Monthly',
'Quarterly',
'Half Yearly',
'Yearly'
],
label: 'Periodicity',
fieldname: 'periodicity',
default: 'Monthly'
}
],
getColumns(data) {
const columns = [
{ label: 'Account', fieldtype: 'Data', fieldname: 'account', width: 340 }
];
if (data && data.columns) {
const currencyColumns = data.columns;
const columnDefs = currencyColumns.map(name => ({
label: name,
fieldname: name,
fieldtype: 'Currency'
}));
columns.push(...columnDefs);
}
return columns;
}
};

View File

@ -2,12 +2,36 @@ const title = 'Profit and Loss';
module.exports = {
title: title,
method: 'profit-and-loss',
treeView: true,
filterFields: [
{ fieldtype: 'Date', fieldname: 'fromDate', label: 'From Date', required: 1 },
{ fieldtype: 'Date', fieldname: 'toDate', label: 'To Date', required: 1 },
{
fieldtype: 'Select', options: ['Monthly', 'Quarterly', 'Half Yearly', 'Yearly'],
label: 'Periodicity', fieldname: 'periodicity', default: 'Monthly'
fieldtype: 'Date',
fieldname: 'fromDate',
size: 'small',
placeholder: 'From Date',
label: 'From Date',
required: 1
},
{
fieldtype: 'Date',
fieldname: 'toDate',
size: 'small',
placeholder: 'To Date',
label: 'To Date',
required: 1
},
{
fieldtype: 'Select',
size: 'small',
options: [
'Select Period...',
'Monthly',
'Quarterly',
'Half Yearly',
'Yearly'
],
label: 'Periodicity',
fieldname: 'periodicity'
}
],
getColumns(data) {

View File

@ -2,6 +2,7 @@ module.exports = {
'general-ledger': require('./GeneralLedger/viewConfig'),
'sales-register': require('./SalesRegister/viewConfig'),
'purchase-register': require('./PurchaseRegister/viewConfig'),
'balance-sheet': require('./BalanceSheet/viewConfig'),
'profit-and-loss': require('./ProfitAndLoss/viewConfig'),
'trial-balance': require('./TrialBalance/viewConfig'),
'bank-reconciliation': require('./BankReconciliation/viewConfig'),

View File

@ -6,7 +6,7 @@ let winURL;
if (process.env.NODE_ENV !== 'development') {
global.__static = require('path')
.join(__dirname, '/static')
.join(__dirname, '../static')
.replace(/\\/g, '\\\\');
}

View File

@ -19,12 +19,12 @@ export default {
showDatabaseSelector: false,
showDesk: false,
showSetupWizard: false
}
};
},
components: {
Desk,
SetupWizard,
DatabaseSelector,
DatabaseSelector
},
mounted() {
if (!localStorage.dbPath) {
@ -43,15 +43,15 @@ export default {
this.showDesk = true;
this.showSetupWizard = false;
this.showDatabaseSelector = false;
})
});
},
methods: {
connectToDBFile(filePath) {
frappe.events.trigger('DatabaseSelector:file-selected', filePath);
}
}
}
};
</script>
<style lang="scss">
@import "styles/index.scss";
@import 'styles/index.scss';
</style>

View File

@ -9,19 +9,23 @@
<input
type="search"
class="form-control"
@click="focus(0)"
@keydown.down="navigate('down')"
@keydown.up="navigate('up')"
placeholder="Search..."
autocomplete="off"
spellcheck="false"
v-model="inputValue"
@keyup.enter="search"
aria-label="Recipient's username"
@keydown.enter="searchOrSelect"
/>
</div>
<div v-if="inputValue" class="suggestion-list position-absolute shadow-sm" style="width: 98%">
<div v-if="inputValue" class="search-list position-absolute shadow-sm" style="width: 98%">
<list-row
v-for="doc in suggestion"
:key="doc.name"
:class="doc.seperator ? 'seperator': ''"
v-for="(doc, i) in suggestion"
:key="i+1"
:ref="i+1"
:route="doc.route"
:class="{ 'seperator': doc.seperator, 'item-active': isFocused(i+1) && !doc.seperator }"
class="d-flex align-items-center"
@click.native="routeTo(doc.route)"
>
@ -40,7 +44,8 @@ export default {
data() {
return {
inputValue: '',
suggestion: []
suggestion: [],
currentlyFocused: 0
};
},
components: {
@ -53,7 +58,31 @@ export default {
}
},
methods: {
async search() {
focus(key) {
this.currentlyFocused = key % (this.suggestion.length + 1);
},
navigate(dir) {
const seperatorIndexes = this.suggestion.map((item, i) => {
if (item.seperator) return i;
});
let nextItem = this.currentlyFocused + (dir === 'up' ? -1 : 1);
if (seperatorIndexes.includes(this.currentlyFocused)) {
nextItem = this.currentlyFocused + (dir === 'up' ? -2 : 2);
}
this.focus(nextItem);
},
isFocused(i) {
if (i === this.currentlyFocused) {
return true;
}
return false;
},
async searchOrSelect() {
if (this.currentlyFocused != 0) {
this.routeTo(this.$refs[this.currentlyFocused][0].$attrs.route);
return;
}
const searchableDoctypes = frappe.getDoctypeList({
isSingle: 0,
isChild: 0
@ -73,7 +102,10 @@ export default {
const promises = searchableDoctypes.map(doctype => {
return frappe.db.getAll({
doctype,
filters: { name: ['includes', this.inputValue] },
filters: {
name: ['includes', this.inputValue],
keywords: ['like', this.inputValue]
},
fields: ['name']
});
});
@ -166,10 +198,14 @@ input:focus {
outline: none !important;
box-shadow: none !important;
}
.suggestion-list {
.search-list {
z-index: 2;
max-height: 90vh;
overflow: auto;
}
.item-active {
background-color: var(--light);
}
</style>

View File

@ -70,19 +70,20 @@ export default {
setActive() {
let currentRoute = this.$route.fullPath;
// check each group items
this.groups.forEach(title => {
this.groups.forEach(groupTitle => {
// filter items which contains the current route
sidebarConfig.getItems(title).filter(item => {
sidebarConfig.getItems(groupTitle).some(item => {
// check for substring 'list' 'SalesInvoice' etc.
let found = true;
currentRoute.split('/').forEach(str => {
let found = false;
currentRoute.split('/').some(str => {
if (str.length) {
item.route.indexOf(str) != -1 ? '' : (found = false);
item.route.indexOf(str) > -1 ? (found = true) : (found = false);
}
if (found) {
this.toggleGroup(groupTitle);
return found;
}
});
if (found) {
this.toggleGroup(title);
}
return found;
});
});
@ -122,6 +123,7 @@ export default {
.page-sidebar {
height: 100vh;
min-width: 208px;
max-width: 208px;
}
.sidebar-item {

View File

@ -26,6 +26,13 @@ import Toasted from 'vue-toasted';
frappe.events.on('connect-database', async filepath => {
await connectToLocalDatabase(filepath);
const { completed } = await frappe.getSingle('SetupWizard');
if (!completed) {
frappe.events.trigger('show-setup-wizard');
return;
}
const { country } = await frappe.getSingle('AccountingSettings');
if (country === 'India') {
@ -84,8 +91,12 @@ import Toasted from 'vue-toasted';
await setupAccountsAndDashboard(bankName);
await setupRegionalChanges(country);
await setupWizardValues.set({ completed: 1 });
await setupWizardValues.update();
frappe.events.trigger('show-desk');
});
async function setupAccountsAndDashboard(bankName) {
await frappe.call({
method: 'import-coa'
@ -98,7 +109,8 @@ import Toasted from 'vue-toasted';
name: bankName,
rootType: 'Asset',
parentAccount: 'Bank Accounts',
accountType: 'Bank'
accountType: 'Bank',
isGroup: 0
});
accountDoc.insert();

View File

@ -42,8 +42,6 @@
</div>
</template>
<script>
import { setTimeout } from 'timers';
const Branch = {
props: ['label', 'parentValue', 'doctype', 'balance', 'currency', 'rootType'],
data() {
@ -64,9 +62,9 @@ const Branch = {
},
creditOrDebit() {
if (['Asset', 'Expense'].includes(this.rootType))
return this.nodeBalance > 0 ? 'Dr' : 'Cr';
return this.nodeBalance >= 0 ? 'Dr' : 'Cr';
return this.nodeBalance > 0 ? 'Cr' : 'Dr';
return this.nodeBalance >= 0 ? 'Cr' : 'Dr';
}
},
components: {

View File

@ -18,12 +18,13 @@ export default {
<style>
.page-container {
height: 100vh;
max-height: 100vh;
width: 100%;
padding-left: 208px;
overflow-x: hidden;
}
.sidebar {
position: fixed;
z-index: 1;
z-index: 20;
}
</style>

View File

@ -95,10 +95,10 @@ export default {
this.doc.set('name', '');
}
if (this.defaults) {
if (this.doc._notInserted && this.defaults) {
for (let fieldname in this.defaults) {
const value = this.defaults[fieldname];
this.doc.set(fieldname, value);
await this.doc.set(fieldname, value);
}
}
@ -106,11 +106,15 @@ export default {
this.doc.on('change', this.setLinks);
} catch (e) {
this.notFound = true;
this.$router.push({
path: `/`
});
}
},
getFormTitle() {
try {
// For different list/form based on same doctype since they will have different title
// For different list/form based on same doctype
// Since they will have different title
return (
this.meta.getFormTitle(this.doc) || this.meta.label || this.doctype
);
@ -121,7 +125,7 @@ export default {
getListTitle() {
try {
// For different list/form based on same doctype
// Since they will have different list route
// Since they will have different route to their list
return this.meta.getListTitle(this.doc);
} catch (e) {
return this.doctype;
@ -132,12 +136,11 @@ export default {
if (this.isFormInvalid) return;
try {
if (this.doc._notInserted) {
if (this.doc.isNew()) {
await this.doc.insert();
} else {
await this.doc.update();
}
this.$emit('save', this.doc);
} catch (e) {
console.error(e);
@ -146,12 +149,12 @@ export default {
},
async submit() {
this.doc.set('submitted', 1);
await this.doc.set('submitted', 1);
try {
await this.save();
} catch (e) {
this.doc.set('submitted', 0);
this.doc.set('_dirty', false);
await this.doc.set('submitted', 0);
await this.doc.set('_dirty', false);
}
},
@ -160,8 +163,8 @@ export default {
try {
await this.save();
} catch (e) {
this.doc.set('submitted', 1);
this.doc._dirty = false;
await this.doc.set('submitted', 1);
await this.doc.set('_dirty', false);
}
},

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="report-view">
<div>
<div class="pb-4 d-flex">
<page-header :breadcrumbs="breadcrumbs" style="flex-grow: 1;" />
@ -145,4 +145,10 @@ export default {
.datatable {
font-size: 12px;
}
.dt-scrollable {
height: 77vh;
}
.report-view {
overflow: hidden;
}
</style>

View File

@ -97,6 +97,14 @@ const config = {
label: _('General Ledger'),
route: '/report/general-ledger'
},
{
label: _('Profit And Loss'),
route: '/report/profit-and-loss'
},
{
label: _('Balance Sheet'),
route: '/report/balance-sheet'
},
{
label: _('Trial Balance'),
route: '/report/trial-balance'

View File

@ -1,7 +1,8 @@
@import "variables.scss";
@import "~bootstrap/scss/bootstrap";
@import "~frappe-datatable/dist/frappe-datatable.css";
@import 'variables.scss';
@import '~bootstrap/scss/bootstrap';
@import '~frappe-datatable/dist/frappe-datatable.css';
html {
font-size: 14px;
}
overflow: hidden;
}