2
0
mirror of https://github.com/frappe/books.git synced 2025-01-11 02:36:14 +00:00

* Added Country Currency.

* Invoice reverts linked Payments
 * Transactions affects account balance
 * Chart Of Accounts shows account balance
This commit is contained in:
thefalconx33 2019-07-16 12:34:51 +05:30
parent 5032f0397c
commit 96476f56c3
13 changed files with 523 additions and 345 deletions

View File

@ -5,16 +5,38 @@ module.exports = class LedgerPosting {
Object.assign(this, arguments[0]); Object.assign(this, arguments[0]);
this.entries = []; this.entries = [];
this.entryMap = {}; this.entryMap = {};
// To change balance while entering ledger entries
this.accountEntries = [];
} }
debit(account, amount, referenceType, referenceName) { async debit(account, amount, referenceType, referenceName) {
const entry = this.getEntry(account, referenceType, referenceName); const entry = this.getEntry(account, referenceType, referenceName);
entry.debit += amount; entry.debit += amount;
await this.setAccountBalanceChange(account, 'debit', amount);
} }
credit(account, amount, referenceType, referenceName) { async credit(account, amount, referenceType, referenceName) {
const entry = this.getEntry(account, referenceType, referenceName); const entry = this.getEntry(account, referenceType, referenceName);
entry.credit += amount; entry.credit += amount;
await this.setAccountBalanceChange(account, 'credit', amount);
}
async setAccountBalanceChange(accountName, type, amount) {
const debitAccounts = ['Asset', 'Expense'];
const { rootType } = await frappe.getDoc('Account', accountName);
if (debitAccounts.indexOf(rootType) === -1) {
const change = type == 'credit' ? amount : -1 * amount;
this.accountEntries.push({
name: accountName,
balanceChange: change
});
} else {
const change = type == 'debit' ? amount : -1 * amount;
this.accountEntries.push({
name: accountName,
balanceChange: change
});
}
} }
getEntry(account, referenceType, referenceName) { getEntry(account, referenceType, referenceName) {
@ -50,6 +72,9 @@ module.exports = class LedgerPosting {
entry.debit = entry.credit; entry.debit = entry.credit;
entry.credit = temp; entry.credit = temp;
} }
for (let entry of this.accountEntries) {
entry.balanceChange = -1 * entry.balanceChange;
}
await this.insertEntries(); await this.insertEntries();
} }
@ -70,7 +95,9 @@ module.exports = class LedgerPosting {
} }
if (debit !== credit) { if (debit !== credit) {
throw new frappe.errors.ValidationError(frappe._('Debit {0} must be equal to Credit {1}', [debit, credit])); throw new frappe.errors.ValidationError(
frappe._('Debit {0} must be equal to Credit {1}', [debit, credit])
);
} }
} }
@ -82,5 +109,10 @@ module.exports = class LedgerPosting {
Object.assign(entryDoc, entry); Object.assign(entryDoc, entry);
await entryDoc.insert(); await entryDoc.insert();
} }
for (let entry of this.accountEntries) {
let entryDoc = await frappe.getDoc('Account', entry.name);
entryDoc.balance += entry.balanceChange;
await entryDoc.update();
}
} }
}; };

View File

@ -1,70 +1,79 @@
const countryList = Object.keys(require('../../../fixtures/countryInfo.json')).sort(); const countryList = Object.keys(
require('../../../fixtures/countryInfo.json')
).sort();
module.exports = { module.exports = {
name: "AccountingSettings", name: 'AccountingSettings',
label: "Accounting Settings", label: 'Accounting Settings',
naming: "name", // {random|autoincrement} naming: 'name', // {random|autoincrement}
isSingle: 1, isSingle: 1,
isChild: 0, isChild: 0,
isSubmittable: 0, isSubmittable: 0,
settings: null, settings: null,
keywordFields: [], keywordFields: [],
fields: [{ fields: [
label: "Company Name", {
fieldname: "companyName", label: 'Company Name',
fieldtype: "Data", fieldname: 'companyName',
required: 1, fieldtype: 'Data',
disabled: 0 required: 1,
}, disabled: 0
},
{ {
label: "Writeoff Account", label: 'Writeoff Account',
fieldname: "writeOffAccount", fieldname: 'writeOffAccount',
fieldtype: "Account" fieldtype: 'Account'
}, },
{ {
"fieldname": "country", fieldname: 'country',
"label": "Country", label: 'Country',
"fieldtype": "Autocomplete", fieldtype: 'Autocomplete',
"required": 1, required: 1,
getList: () => countryList getList: () => countryList
}, },
{ {
"fieldname": "fullname", fieldname: 'currency',
"label": "Name", label: 'Country Currency',
"fieldtype": "Data", fieldtype: 'Data',
"required": 1 required: 0
}, },
{ {
"fieldname": "email", fieldname: 'fullname',
"label": "Email", label: 'Name',
"fieldtype": "Data", fieldtype: 'Data',
"required": 1 required: 1
}, },
{ {
"fieldname": "bankName", fieldname: 'email',
"label": "Bank Name", label: 'Email',
"fieldtype": "Data", fieldtype: 'Data',
"required": 1 required: 1
}, },
{ {
"fieldname": "fiscalYearStart", fieldname: 'bankName',
"label": "Fiscal Year Start Date", label: 'Bank Name',
"fieldtype": "Date", fieldtype: 'Data',
"required": 1 required: 1
}, },
{ {
"fieldname": "fiscalYearEnd", fieldname: 'fiscalYearStart',
"label": "Fiscal Year End Date", label: 'Fiscal Year Start Date',
"fieldtype": "Date", fieldtype: 'Date',
"required": 1 required: 1
}, },
] {
} fieldname: 'fiscalYearEnd',
label: 'Fiscal Year End Date',
fieldtype: 'Date',
required: 1
}
]
};

View File

@ -23,11 +23,6 @@ export default {
}, },
'grandTotal', 'grandTotal',
'date', 'date',
{ 'outstandingAmount'
label: 'INV #',
getValue(doc) {
return doc.name;
}
}
] ]
} };

View File

@ -2,27 +2,57 @@ const Invoice = require('./InvoiceDocument');
const LedgerPosting = require('../../../accounting/ledgerPosting'); const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class InvoiceServer extends Invoice { module.exports = class InvoiceServer extends Invoice {
getPosting() { async getPosting() {
let entries = new LedgerPosting({ reference: this, party: this.customer }); let entries = new LedgerPosting({ reference: this, party: this.customer });
entries.debit(this.account, this.grandTotal); await entries.debit(this.account, this.grandTotal);
for (let item of this.items) { for (let item of this.items) {
entries.credit(item.account, item.amount); await entries.credit(item.account, item.amount);
} }
if (this.taxes) { if (this.taxes) {
for (let tax of this.taxes) { for (let tax of this.taxes) {
entries.credit(tax.account, tax.amount); await entries.credit(tax.account, tax.amount);
} }
} }
return entries; return entries;
} }
async getPayments() {
let payments = await frappe.db.getAll({
doctype: 'PaymentFor',
fields: ['parent'],
filters: { referenceName: this.name },
orderBy: 'name'
});
if (payments.length != 0) {
return payments;
}
return [];
}
async afterSubmit() { async afterSubmit() {
await this.getPosting().post(); const entries = await this.getPosting();
await entries.post();
await frappe.db.setValue(
'Invoice',
this.name,
'outstandingAmount',
this.grandTotal
);
} }
async afterRevert() { async afterRevert() {
await this.getPosting().postReverse(); let paymentRefList = await this.getPayments();
for (let paymentFor of paymentRefList) {
const paymentReference = paymentFor.parent;
const payment = await frappe.getDoc('Payment', paymentReference);
const paymentEntries = await payment.getPosting();
await paymentEntries.postReverse();
// To set the payment status as unsubmitted.
payment.revert();
}
const entries = await this.getPosting();
await entries.postReverse();
} }
}; };

View File

@ -2,11 +2,9 @@ module.exports = {
name: 'Item', name: 'Item',
doctype: 'DocType', doctype: 'DocType',
isSingle: 0, isSingle: 0,
keywordFields: [ keywordFields: ['name', 'description'],
'name', fields: [
'description' {
],
fields: [{
fieldname: 'name', fieldname: 'name',
label: 'Item Name', label: 'Item Name',
fieldtype: 'Data', fieldtype: 'Data',
@ -22,20 +20,20 @@ module.exports = {
label: 'Unit', label: 'Unit',
fieldtype: 'Select', fieldtype: 'Select',
default: 'No', default: 'No',
options: [ options: ['No', 'Kg', 'Gram', 'Hour', 'Day']
'No',
'Kg',
'Gram',
'Hour',
'Day'
]
}, },
{ {
fieldname: 'incomeAccount', fieldname: 'incomeAccount',
label: 'Income Account', label: 'Income Account',
fieldtype: 'Link', fieldtype: 'Link',
target: 'Account', target: 'Account',
required: 1 required: 1,
getFilters: (query, control) => {
return {
isGroup: 0,
accountType: 'Income Account'
};
}
}, },
{ {
fieldname: 'expenseAccount', fieldname: 'expenseAccount',
@ -58,7 +56,8 @@ module.exports = {
layout: [ layout: [
// section 1 // section 1
{ {
columns: [{ columns: [
{
fields: ['name', 'unit'] fields: ['name', 'unit']
}, },
{ {
@ -69,15 +68,18 @@ module.exports = {
// section 2 // section 2
{ {
columns: [{ columns: [
fields: ['description'] {
}] fields: ['description']
}
]
}, },
// section 3 // section 3
{ {
title: 'Accounting', title: 'Accounting',
columns: [{ columns: [
{
fields: ['incomeAccount', 'expenseAccount'] fields: ['incomeAccount', 'expenseAccount']
}, },
{ {

View File

@ -3,28 +3,48 @@ const frappe = require('frappejs');
const LedgerPosting = require('../../../accounting/ledgerPosting'); const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class PaymentServer extends BaseDocument { module.exports = class PaymentServer extends BaseDocument {
getPosting() { async getPosting() {
let entries = new LedgerPosting({reference: this, party: this.party}); let entries = new LedgerPosting({ reference: this, party: this.party });
entries.debit(this.paymentAccount, this.amount); await entries.debit(this.paymentAccount, this.amount);
for (let row of this.for) { for (let row of this.for) {
entries.credit(this.account, row.amount, row.referenceType, row.referenceName); await entries.credit(
this.account,
row.amount,
row.referenceType,
row.referenceName
);
}
return entries;
}
async afterSubmit() {
for (let row of this.for) {
if (row.referenceType === 'Invoice') {
const { outstandingAmount } = await frappe.getDoc(
'Invoice',
row.referenceName
);
if (this.amount > outstandingAmount) {
console.log('Over Payment');
} else {
await frappe.db.setValue(
'Invoice',
row.referenceName,
'outstandingAmount',
outstandingAmount - this.amount
);
const entries = await this.getPosting();
await entries.post();
} }
}
return entries;
} }
}
async afterSubmit() { async afterRevert() {
await this.getPosting().post(); const entries = await this.getPosting();
for (let row of this.for) { await entries.postReverse();
if (row.referenceType === 'Invoice') {
await frappe.db.setValue('Invoice', row.referenceName, 'outstandingAmount', 0.0);
}
}
}
async afterRevert() { // Maybe revert outstanding amount of invoice too?
await this.getPosting().postReverse(); }
} };
}

View File

@ -1,102 +1,128 @@
const countryList = require('../../../fixtures/countryInfo.json'); const countryList = require('../../../fixtures/countryInfo.json');
module.exports = { module.exports = {
name: "SetupWizard", name: 'SetupWizard',
label: "Setup Wizard", label: 'Setup Wizard',
naming: "name", naming: 'name',
isSingle: 1, isSingle: 1,
isChild: 0, isChild: 0,
isSubmittable: 0, isSubmittable: 0,
settings: null, settings: null,
keywordFields: [], keywordFields: [],
fields: [{ fields: [
fieldname: 'country', {
label: 'Country', fieldname: 'country',
fieldtype: 'Autocomplete', label: 'Country',
required: 1, fieldtype: 'Autocomplete',
getList: () => Object.keys(countryList).sort() required: 1,
}, getList: () => Object.keys(countryList).sort()
},
{ {
fieldname: 'fullname', fieldname: 'fullname',
label: 'Name', label: 'Name',
fieldtype: 'Data', fieldtype: 'Data',
required: 1 required: 1
}, },
{ {
fieldname: 'email', fieldname: 'email',
label: 'Email', label: 'Email',
fieldtype: 'Data', fieldtype: 'Data',
required: 1, required: 1,
inputType: 'email' inputType: 'email'
}, },
{ {
fieldname: 'companyName', fieldname: 'companyName',
label: 'Company Name', label: 'Company Name',
fieldtype: 'Data', fieldtype: 'Data',
required: 1 required: 1
}, },
{ {
fieldname: 'bankName', fieldname: 'bankName',
label: 'Bank Name', label: 'Bank Name',
fieldtype: 'Data', fieldtype: 'Data',
required: 1 required: 1
}, },
{ {
fieldname: 'fiscalYearStart', fieldname: 'fiscalYearStart',
label: 'Fiscal Year Start Date', label: 'Fiscal Year Start Date',
fieldtype: 'Date', fieldtype: 'Date',
formula: (doc) => { formula: doc => {
let date = countryList[doc.country]["fiscal_year_start"].split("-"); let date = countryList[doc.country]['fiscal_year_start'].split('-');
var currentYear = (new Date).getFullYear(); var currentYear = new Date().getFullYear();
let currentMonth = date[0] - 1 ; let currentMonth = date[0] - 1;
let currentDate = date[1]; let currentDate = date[1];
return new Date(currentYear,currentMonth,currentDate).toISOString().substr(0, 10);; return new Date(currentYear, currentMonth, currentDate)
}, .toISOString()
required: 1 .substr(0, 10);
}, },
required: 1
},
{ {
fieldname: 'fiscalYearEnd', fieldname: 'fiscalYearEnd',
label: 'Fiscal Year End Date', label: 'Fiscal Year End Date',
fieldtype: 'Date', fieldtype: 'Date',
formula: (doc) => { formula: doc => {
let date = countryList[doc.country]["fiscal_year_end"].split("-"); let date = countryList[doc.country]['fiscal_year_end'].split('-');
var currentYear = (new Date).getFullYear() + 1 ; var currentYear = new Date().getFullYear() + 1;
let currentMonth = date[0] - 1 ; let currentMonth = date[0] - 1;
let currentDate = date[1]; let currentDate = date[1];
return new Date(currentYear,currentMonth,currentDate).toISOString().substr(0, 10);; return new Date(currentYear, currentMonth, currentDate)
}, .toISOString()
required: 1 .substr(0, 10);
} },
], required: 1
},
layout: { {
paginated: true, fieldname: 'currency',
sections: [{ label: 'Currency',
title: 'Select Country', fieldtype: 'Currency',
columns: [{ formula: doc => {
fields: ['country'] return countryList[doc.country]['currency'];
}] },
}, required: 1
{
title: 'Add a Profile',
columns: [{
fields: ['fullname', 'email']
}]
},
{
title: 'Add your Company',
columns: [{
fields: ['companyName', 'bankName', 'fiscalYearStart', 'fiscalYearEnd']
}]
}
].filter(Boolean)
} }
} ],
layout: {
paginated: true,
sections: [
{
title: 'Select Country',
columns: [
{
fields: ['country']
}
]
},
{
title: 'Add a Profile',
columns: [
{
fields: ['fullname', 'email']
}
]
},
{
title: 'Add your Company',
columns: [
{
fields: [
'companyName',
'bankName',
'fiscalYearStart',
'fiscalYearEnd'
]
}
]
}
].filter(Boolean)
}
};

View File

@ -3,67 +3,75 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const countries = require('../../../fixtures/countryInfo.json'); const countries = require('../../../fixtures/countryInfo.json');
const standardCOA = require('../../../fixtures/verified/standardCOA.json'); const standardCOA = require('../../../fixtures/verified/standardCOA.json');
const accountFields = ['accountType', 'rootType', 'isGroup', 'account_type', 'root_type', 'is_group']; const accountFields = [
'accountType',
'rootType',
'isGroup',
'account_type',
'root_type',
'is_group'
];
async function importAccounts(children, parent, rootType, rootAccount) { async function importAccounts(children, parent, rootType, rootAccount) {
for (let accountName in children) { for (let accountName in children) {
const child = children[accountName]; const child = children[accountName];
if (rootAccount) { if (rootAccount) {
rootType = child.rootType || child.root_type; rootType = child.rootType || child.root_type;
}
if (!accountFields.includes(accountName)) {
let isGroup = identifyIsGroup(child);
const doc = frappe.newDoc({
doctype: 'Account',
name: accountName,
parentAccount: parent,
isGroup,
rootType,
balance: 0,
accountType: child.accountType
})
await doc.insert()
await importAccounts(child, accountName, rootType)
}
} }
if (!accountFields.includes(accountName)) {
let isGroup = identifyIsGroup(child);
const doc = frappe.newDoc({
doctype: 'Account',
name: accountName,
parentAccount: parent,
isGroup,
rootType,
balance: 0,
accountType: child.accountType || child.account_type
});
await doc.insert();
await importAccounts(child, accountName, rootType);
}
}
} }
function identifyIsGroup(child) { function identifyIsGroup(child) {
if (child.isGroup || child.is_group) { if (child.isGroup || child.is_group) {
return child.isGroup || child.is_group; return child.isGroup || child.is_group;
} }
const keys = Object.keys(child); const keys = Object.keys(child);
const children = keys.filter(key => !accountFields.includes(key)) const children = keys.filter(key => !accountFields.includes(key));
if (children.length) { if (children.length) {
return 1; return 1;
} }
return 0; return 0;
} }
async function getCountryCOA(){ async function getCountryCOA() {
const doc = await frappe.getDoc('AccountingSettings'); const doc = await frappe.getDoc('AccountingSettings');
const conCode = countries[doc.country].code; const conCode = countries[doc.country].code;
const countryCOA = path.resolve(path.join('./fixtures/verified/', conCode + '.json')); const countryCOA = path.resolve(
path.join('./fixtures/verified/', conCode + '.json')
);
if(fs.existsSync(countryCOA)){ if (fs.existsSync(countryCOA)) {
const jsonText = fs.readFileSync(countryCOA, 'utf-8'); const jsonText = fs.readFileSync(countryCOA, 'utf-8');
const json = JSON.parse(jsonText); const json = JSON.parse(jsonText);
return json.tree; return json.tree;
} else { } else {
return standardCOA; return standardCOA;
} }
} }
module.exports = async function importCharts() { module.exports = async function importCharts() {
const chart = await getCountryCOA(); const chart = await getCountryCOA();
await importAccounts(chart, '', '', true) await importAccounts(chart, '', '', true);
} };

View File

@ -6,10 +6,7 @@ import common from 'frappejs/common';
import coreModels from 'frappejs/models'; import coreModels from 'frappejs/models';
import models from '../models'; import models from '../models';
import postStart from '../server/postStart'; import postStart from '../server/postStart';
import { import { getSettings, saveSettings } from '../electron/settings';
getSettings,
saveSettings
} from '../electron/settings';
// vue imports // vue imports
import Vue from 'vue'; import Vue from 'vue';
@ -26,35 +23,34 @@ import Toasted from 'vue-toasted';
frappe.registerModels(coreModels); frappe.registerModels(coreModels);
frappe.registerModels(models); frappe.registerModels(models);
frappe.fetch = window.fetch.bind(); frappe.fetch = window.fetch.bind();
frappe.events.on('connect-database', async (filepath) => { frappe.events.on('connect-database', async filepath => {
await connectToLocalDatabase(filepath); await connectToLocalDatabase(filepath);
const accountingSettings = await frappe.getSingle('AccountingSettings'); const { country } = await frappe.getSingle('AccountingSettings');
const country = accountingSettings.country;
if (country === "India") { if (country === 'India') {
frappe.models.Party = require('../models/doctype/Party/RegionalChanges.js') frappe.models.Party = require('../models/doctype/Party/RegionalChanges.js');
} else { } else {
frappe.models.Party = require('../models/doctype/Party/Party.js') frappe.models.Party = require('../models/doctype/Party/Party.js');
} }
frappe.events.trigger('show-desk'); frappe.events.trigger('show-desk');
}); });
frappe.events.on('DatabaseSelector:file-selected', async (filepath) => { frappe.events.on('DatabaseSelector:file-selected', async filepath => {
await connectToLocalDatabase(filepath); await connectToLocalDatabase(filepath);
localStorage.dbPath = filepath; localStorage.dbPath = filepath;
const accountingSettings = await frappe.getSingle('AccountingSettings'); const { companyName } = await frappe.getSingle('AccountingSettings');
if (!accountingSettings.companyName) { if (!companyName) {
frappe.events.trigger('show-setup-wizard'); frappe.events.trigger('show-setup-wizard');
} else { } else {
frappe.events.trigger('show-desk'); frappe.events.trigger('show-desk');
} }
}); });
frappe.events.on('SetupWizard:setup-complete', async (setupWizardValues) => { frappe.events.on('SetupWizard:setup-complete', async setupWizardValues => {
const countryList = require('../fixtures/countryInfo.json');
const { const {
companyName, companyName,
country, country,
@ -73,7 +69,8 @@ import Toasted from 'vue-toasted';
email, email,
bankName, bankName,
fiscalYearStart, fiscalYearStart,
fiscalYearEnd fiscalYearEnd,
currency: countryList[country]['currency']
}); });
await doc.update(); await doc.update();
@ -81,9 +78,9 @@ import Toasted from 'vue-toasted';
method: 'import-coa' method: 'import-coa'
}); });
if (country === "India") { if (country === 'India') {
frappe.models.Party = require('../models/doctype/Party/RegionalChanges.js') frappe.models.Party = require('../models/doctype/Party/RegionalChanges.js');
await frappe.db.migrate() await frappe.db.migrate();
await generateGstTaxes(); await generateGstTaxes();
} }
frappe.events.trigger('show-desk'); frappe.events.trigger('show-desk');
@ -98,25 +95,28 @@ import Toasted from 'vue-toasted';
case 'Out of State': case 'Out of State':
await newTax.set({ await newTax.set({
name: `${type}-${percent}`, name: `${type}-${percent}`,
details: [{ details: [
account: "IGST", {
rate: percent account: 'IGST',
}] rate: percent
}) }
]
});
break; break;
case 'In State': case 'In State':
await newTax.set({ await newTax.set({
name: `${type}-${percent}`, name: `${type}-${percent}`,
details: [{ details: [
account: "CGST", {
account: 'CGST',
rate: percent / 2 rate: percent / 2
}, },
{ {
account: "SGST", account: 'SGST',
rate: percent / 2 rate: percent / 2
} }
] ]
}) });
break; break;
} }
await newTax.insert(); await newTax.insert();
@ -124,11 +124,13 @@ import Toasted from 'vue-toasted';
} }
await newTax.set({ await newTax.set({
name: `Exempt-0`, name: `Exempt-0`,
details: [{ details: [
account: "Exempt", {
rate: 0 account: 'Exempt',
}] rate: 0
}) }
]
});
await newTax.insert(); await newTax.insert();
} }
@ -147,7 +149,6 @@ import Toasted from 'vue-toasted';
} }
} }
window.frappe = frappe; window.frappe = frappe;
Vue.config.productionTip = false; Vue.config.productionTip = false;
@ -166,4 +167,4 @@ import Toasted from 'vue-toasted';
}, },
template: '<App/>' template: '<App/>'
}); });
})() })();

View File

@ -26,46 +26,50 @@ frappe.db.bindSocketClient(socket);
frappe.getSingle('SystemSettings'); frappe.getSingle('SystemSettings');
registerReportMethods(); registerReportMethods();
frappe.getSingle('AccountingSettings') frappe.getSingle('AccountingSettings').then(accountingSettings => {
.then(accountingSettings => { if (router.currentRoute.fullPath !== '/') return;
if (router.currentRoute.fullPath !== '/') return;
if (accountingSettings.companyName) { if (accountingSettings.companyName) {
frappe.events.trigger('show-desk'); frappe.events.trigger('show-desk');
} else { } else {
frappe.events.trigger('show-setup-wizard'); frappe.events.trigger('show-setup-wizard');
} }
});
frappe.events.on('SetupWizard:setup-complete', async ({ setupWizardValues }) => {
const {
companyName,
country,
name,
email,
abbreviation,
bankName,
fiscalYearStart,
fiscalYearEnd
} = setupWizardValues;
const doc = await frappe.getSingle('AccountingSettings');
await doc.set({
companyName,
country,
fullname: name,
email,
bankName,
fiscalYearStart,
fiscalYearEnd
});
await doc.update();
await frappe.call({ method: 'import-coa' });
frappe.events.trigger('show-desk');
}); });
frappe.events.on(
'SetupWizard:setup-complete',
async ({ setupWizardValues }) => {
const countryList = require('../fixtures/countryInfo.json');
const {
companyName,
country,
name,
email,
abbreviation,
bankName,
fiscalYearStart,
fiscalYearEnd
} = setupWizardValues;
const doc = await frappe.getSingle('AccountingSettings');
await doc.set({
companyName,
country,
fullname: name,
email,
bankName,
fiscalYearStart,
fiscalYearEnd,
currency: countryList[country]['currency']
});
await doc.update();
await frappe.call({ method: 'import-coa' });
frappe.events.trigger('show-desk');
}
);
window.frappe = frappe; window.frappe = frappe;
Vue.config.productionTip = false; Vue.config.productionTip = false;

View File

@ -5,47 +5,69 @@
<feather-icon class="mr-1" :name="iconName" v-show="iconName" /> <feather-icon class="mr-1" :name="iconName" v-show="iconName" />
<span>{{ label }}</span> <span>{{ label }}</span>
<div class="ml-auto d-flex align-items-center"> <div class="ml-auto d-flex align-items-center">
<feather-icon v-if="balance !== ''" style="width:15px; height:15px" name="dollar-sign"/> <span>
<span>{{ balance }}</span> {{ currency }}
<span style="font-weight: 800">{{ computedBalance }}</span>
</span>
</div> </div>
</div> </div>
</div> </div>
<div :class="['branch-children', expanded ? '' : 'd-none']"> <div :class="['branch-children', expanded ? '' : 'd-none']">
<branch v-for="child in children" :key="child.label" <branch
v-for="child in children"
:key="child.label"
:label="child.label" :label="child.label"
:balance="child.balance" :balance="child.balance"
:parentValue="child.name" :parentValue="child.name"
:doctype="doctype" :doctype="doctype"
:currency="currency"
@updateBalance="updateBalance"
/> />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
const Branch = { const Branch = {
props: ['label', 'parentValue', 'doctype', 'balance'], props: ['label', 'parentValue', 'doctype', 'balance', 'currency'],
data() { data() {
return { return {
expanded: false, expanded: false,
children: null children: null,
} nodeBalance: this.balance
};
}, },
computed: { computed: {
iconName() { iconName() {
if (this.children && this.children.length ==0) return 'chevron-right'; if (this.children && this.children.length == 0) return 'chevron-right';
return this.expanded ? 'chevron-down' : 'chevron-right'; return this.expanded ? 'chevron-down' : 'chevron-right';
},
computedBalance() {
return this.nodeBalance;
} }
}, },
components: { components: {
Branch: () => Promise.resolve(Branch) Branch: () => Promise.resolve(Branch)
}, },
mounted() { async mounted() {
this.settings = frappe.getMeta(this.doctype).treeSettings; this.settings = frappe.getMeta(this.doctype).treeSettings;
if (this.nodeBalance > 0) {
this.$emit('updateBalance', this.nodeBalance);
}
await this.toggleChildren();
this.expanded = !this.expanded;
if (this.label === (await this.settings.getRootLabel())) {
await this.toggleChildren();
}
}, },
methods: { methods: {
async toggleChildren() { async toggleChildren() {
await this.getChildren(); await this.getChildren();
this.expanded = !this.expanded; this.expanded = !this.expanded;
}, },
updateBalance(balance) {
this.nodeBalance += balance;
this.$emit('updateBalance', this.nodeBalance);
},
async getChildren() { async getChildren() {
if (this.children) return; if (this.children) return;
@ -65,7 +87,7 @@ const Branch = {
this.children = children.map(c => { this.children = children.map(c => {
c.label = c.name; c.label = c.name;
c.balance = c.balance c.balance = c.balance;
return c; return c;
}); });
} }
@ -75,7 +97,7 @@ const Branch = {
export default Branch; export default Branch;
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../../styles/variables"; @import '../../styles/variables';
.branch { .branch {
font-size: 1rem; font-size: 1rem;

View File

@ -1,28 +1,42 @@
<template> <template>
<div class="p-3" v-if="root"> <div class="p-3" v-if="root">
<branch :label="root.label" :balance="root.balance" :parentValue="''" :doctype="doctype" ref="root"/> <branch
:label="root.label"
:balance="root.balance"
:parentValue="''"
:doctype="doctype"
ref="root"
:currency="root.currency"
@updateBalance="updateBalance"
/>
</div> </div>
</template> </template>
<script> <script>
import frappe from 'frappejs'; import frappe from 'frappejs';
import Branch from './Branch'; import Branch from './Branch';
import { setTimeout } from 'timers';
export default { export default {
components: { components: {
Branch, Branch
}, },
data() { data() {
return { return {
root: null, root: null,
doctype: "Account" doctype: 'Account'
};
},
computed: {
rootBalance() {
return this.root.balance;
} }
}, },
async mounted() { async mounted() {
this.settings = frappe.getMeta(this.doctype).treeSettings; this.settings = frappe.getMeta(this.doctype).treeSettings;
const { currency } = await frappe.getSingle('AccountingSettings');
this.root = { this.root = {
label: await this.settings.getRootLabel(), label: await this.settings.getRootLabel(),
balance: 'Net Worth' balance: 0,
currency
}; };
}, },
methods: { methods: {
@ -44,7 +58,10 @@ export default {
c.balance = c.balance; c.balance = c.balance;
return c; return c;
}); });
},
updateBalance(balance) {
this.root.balance += balance;
} }
} }
} };
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="bg-light"> <div class="bg-light">
<div class="form-container col-8 bg-white mt-4 ml-auto mr-auto border p-5"> <div class="form-container col-10 bg-white mt-4 ml-auto mr-auto border p-5">
<form-actions <form-actions
v-if="shouldRenderForm" v-if="shouldRenderForm"
:doc="doc" :doc="doc"
@ -10,7 +10,7 @@
@print="print" @print="print"
:links="links" :links="links"
/> />
<hr class="mb-3"> <hr class="mb-3" />
<form-layout <form-layout
v-if="shouldRenderForm" v-if="shouldRenderForm"
:doc="doc" :doc="doc"
@ -87,10 +87,9 @@ export default {
this.setLinks(); this.setLinks();
this.doc.on('change', this.setLinks); this.doc.on('change', this.setLinks);
} catch (e) { } catch (e) {
this.notFound = true; this.notFound = true;
this.$router.push(`/list/${this.doctype}`)//if reloaded while insert new Item,Invoice etc form. this.$router.push(`/list/${this.doctype}`); //if reloaded while insert new Item,Invoice etc form.
} }
}, },
async save() { async save() {
@ -148,3 +147,16 @@ export default {
} }
}; };
</script> </script>
<style>
/* FIX: For table cell expanding when active */
.table-cell {
min-height: 58px;
}
.table-cell > div {
margin-top: 6px;
}
.table th,
.table td {
vertical-align: middle;
}
</style>