2
0
mirror of https://github.com/frappe/books.git synced 2024-11-12 16:36:27 +00:00

Merge branch 'master' into models

This commit is contained in:
Faris Ansari 2018-04-18 13:36:26 +05:30 committed by GitHub
commit 1a59b20b84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 7485 additions and 698 deletions

4
.gitignore vendored
View File

@ -4,4 +4,6 @@ Thumbs.db
*test.db
*.log
.cache
/temp
/temp
dist
Frappe Accounting*

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/start.js",
"runtimeVersion": "9.2.0"
}
]
}

View File

@ -19,7 +19,7 @@ module.exports = {
})
frappe.desk.menu.addItem('ToDo', '#list/ToDo');
frappe.desk.menu.addItem('Accounts', '#list/Account');
frappe.desk.menu.addItem('Chart of Accounts', '#tree/Account');
frappe.desk.menu.addItem('Items', '#list/Item');
frappe.desk.menu.addItem('Customers', '#list/Customer');
frappe.desk.menu.addItem('Quotation', '#list/Quotation');
@ -29,7 +29,7 @@ module.exports = {
frappe.desk.menu.addItem('Contact', "#list/Contact");
frappe.desk.menu.addItem('Settings', () => frappe.desk.showFormModal('SystemSettings'));
frappe.router.default = '#list/ToDo';
frappe.router.default = '#tree/Account';
frappe.router.show(window.location.hash);

91
electron/client.js Normal file
View File

@ -0,0 +1,91 @@
const frappe = require('frappejs');
const path = require('path');
const electron = require('frappejs/client/electron');
const { writeFile } = require('frappejs/server/utils');
const appClient = require('../client');
const SetupWizard = require('../setup');
const { getPDFForElectron } = require('frappejs/server/pdf');
const fs = require('fs');
require.extensions['.html'] = function (module, filename) {
module.exports = fs.readFileSync(filename, 'utf8');
};
(async () => {
const configFilePath = path.join(require('os').homedir(), '.config', 'frappe-accounting', 'settings.json');
let settings, dbPath;
try {
settings = require(configFilePath);
} catch(e) {
settings = {}
}
frappe.electronConfig = settings;
frappe.getPDF = getPDFForElectron;
if (settings.dbPath) {
dbPath = settings.dbPath;
electron.start({
dbPath,
models: require('../models')
}).then(() => {
frappe.syncDoc(require('../fixtures/invoicePrint'));
appClient.start();
});
} else {
const setup = new SetupWizard();
window.setup = setup;
const values = await setup.start();
const {
companyName,
file,
country,
name,
email,
abbreviation,
bankName
} = values;
dbPath = path.join(file[0].path, companyName + '.db');
electron.start({
dbPath,
models: require('../models')
}).then(async () => {
const config = {
directory: path.dirname(dbPath),
dbPath: dbPath
};
await writeFile(configFilePath, JSON.stringify(config));
frappe.electronConfig = config;
const doc = await frappe.getDoc('AccountingSettings');
await doc.set('companyName', companyName);
await doc.set('country', country);
await doc.set('fullname', name);
await doc.set('email', email);
await doc.set('bankName', bankName);
await doc.update();
// bootstrap Chart of Accounts
const importCOA = require('../models/doctype/account/importCOA');
const chart = require('../fixtures/standardCOA');
await importCOA(chart);
frappe.syncDoc(require('../fixtures/invoicePrint'));
appClient.start();
})
}
})();

12
electron/index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="../www/dist/css/style.css" rel="stylesheet">
</head>
<body>
<script src="./client.js"></script>
</body>
</html>

2710
fixtures/countryInfo.json Normal file

File diff suppressed because it is too large Load Diff

173
fixtures/standardCOA.js Normal file
View File

@ -0,0 +1,173 @@
const frappe = require('frappejs');
const _ = frappe._.bind(frappe);
module.exports = {
[_("Application of Funds (Assets)")]: {
[_("Current Assets")]: {
[_("Accounts Receivable")]: {
[_("Debtors")]: {
"accountType": "Receivable"
}
},
[_("Bank Accounts")]: {
"accountType": "Bank",
"isGroup": 1
},
[_("Cash In Hand")]: {
[_("Cash")]: {
"accountType": "Cash"
},
"accountType": "Cash"
},
[_("Loans and Advances (Assets)")]: {
"isGroup": 1
},
[_("Securities and Deposits")]: {
[_("Earnest Money")]: {}
},
[_("Stock Assets")]: {
[_("Stock In Hand")]: {
"accountType": "Stock"
},
"accountType": "Stock",
},
[_("Tax Assets")]: {
"isGroup": 1
}
},
[_("Fixed Assets")]: {
[_("Capital Equipments")]: {
"accountType": "Fixed Asset"
},
[_("Electronic Equipments")]: {
"accountType": "Fixed Asset"
},
[_("Furnitures and Fixtures")]: {
"accountType": "Fixed Asset"
},
[_("Office Equipments")]: {
"accountType": "Fixed Asset"
},
[_("Plants and Machineries")]: {
"accountType": "Fixed Asset"
},
[_("Buildings")]: {
"accountType": "Fixed Asset"
},
[_("Softwares")]: {
"accountType": "Fixed Asset"
},
[_("Accumulated Depreciation")]: {
"accountType": "Accumulated Depreciation"
}
},
[_("Investments")]: {
"isGroup": 1
},
[_("Temporary Accounts")]: {
[_("Temporary Opening")]: {
"accountType": "Temporary"
}
},
"rootType": "Asset"
},
[_("Expenses")]: {
[_("Direct Expenses")]: {
[_("Stock Expenses")]: {
[_("Cost of Goods Sold")]: {
"accountType": "Cost of Goods Sold"
},
[_("Expenses Included In Valuation")]: {
"accountType": "Expenses Included In Valuation"
},
[_("Stock Adjustment")]: {
"accountType": "Stock Adjustment"
}
},
},
[_("Indirect Expenses")]: {
[_("Administrative Expenses")]: {},
[_("Commission on Sales")]: {},
[_("Depreciation")]: {
"accountType": "Depreciation"
},
[_("Entertainment Expenses")]: {},
[_("Freight and Forwarding Charges")]: {
"accountType": "Chargeable"
},
[_("Legal Expenses")]: {},
[_("Marketing Expenses")]: {
"accountType": "Chargeable"
},
[_("Miscellaneous Expenses")]: {
"accountType": "Chargeable"
},
[_("Office Maintenance Expenses")]: {},
[_("Office Rent")]: {},
[_("Postal Expenses")]: {},
[_("Print and Stationery")]: {},
[_("Round Off")]: {
"accountType": "Round Off"
},
[_("Salary")]: {},
[_("Sales Expenses")]: {},
[_("Telephone Expenses")]: {},
[_("Travel Expenses")]: {},
[_("Utility Expenses")]: {},
[_("Write Off")]: {},
[_("Exchange Gain/Loss")]: {},
[_("Gain/Loss on Asset Disposal")]: {}
},
"rootType": "Expense"
},
[_("Income")]: {
[_("Direct Income")]: {
[_("Sales")]: {},
[_("Service")]: {}
},
[_("Indirect Income")]: {
"isGroup": 1
},
"rootType": "Income"
},
[_("Source of Funds (Liabilities)")]: {
[_("Current Liabilities")]: {
[_("Accounts Payable")]: {
[_("Creditors")]: {
"accountType": "Payable"
},
[_("Payroll Payable")]: {},
},
[_("Stock Liabilities")]: {
[_("Stock Received But Not Billed")]: {
"accountType": "Stock Received But Not Billed"
},
},
[_("Duties and Taxes")]: {
"accountType": "Tax",
"isGroup": 1
},
[_("Loans (Liabilities)")]: {
[_("Secured Loans")]: {},
[_("Unsecured Loans")]: {},
[_("Bank Overdraft Account")]: {},
},
},
"rootType": "Liability"
},
[_("Equity")]: {
[_("Capital Stock")]: {
"accountType": "Equity"
},
[_("Dividends Paid")]: {
"accountType": "Equity"
},
[_("Opening Balance Equity")]: {
"accountType": "Equity"
},
[_("Retained Earnings")]: {
"accountType": "Equity"
},
"rootType": "Equity"
}
}

60
main.js Normal file
View File

@ -0,0 +1,60 @@
const electron = require('electron')
// Module to control application life.
const app = electron.app
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
const path = require('path')
const url = require('url')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 1024, height: 768})
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'electron/index.html'),
protocol: 'file:',
slashes: true
}))
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

View File

@ -5,7 +5,8 @@ module.exports = {
"isSingle": 0,
"keywordFields": [
"name",
"account_type"
"rootType",
"accountType"
],
"fields": [
{
@ -15,7 +16,7 @@ module.exports = {
"required": 1
},
{
"fieldname": "parent_account",
"fieldname": "parentAccount",
"label": "Parent Account",
"fieldtype": "Link",
"target": "Account",
@ -27,8 +28,8 @@ module.exports = {
}
},
{
"fieldname": "account_type",
"label": "Account Type",
"fieldname": "rootType",
"label": "Root Type",
"fieldtype": "Select",
"options": [
"Asset",
@ -37,6 +38,37 @@ module.exports = {
"Income",
"Expense"
]
},
{
"fieldname": "accountType",
"label": "Account Type",
"fieldtype": "Select",
"options": [
"Accumulated Depreciation",
"Bank",
"Cash",
"Chargeable",
"Cost of Goods Sold",
"Depreciation",
"Equity",
"Expense Account",
"Expenses Included In Valuation",
"Fixed Asset",
"Income Account",
"Payable",
"Receivable",
"Round Off",
"Stock",
"Stock Adjustment",
"Stock Received But Not Billed",
"Tax",
"Temporary"
]
},
{
"fieldname": "isGroup",
"label": "Is Group",
"fieldtype": "Check"
}
],
@ -48,10 +80,18 @@ module.exports = {
listSettings: {
getFields(list) {
return ['name', 'account_type'];
return ['name', 'accountType', 'rootType'];
},
getRowHTML(list, data) {
return `<div class="col-11">${list.getNameHTML(data)} (${data.account_type})</div>`;
return `<div class="col-11">${list.getNameHTML(data)} (${data.rootType})</div>`;
}
},
treeSettings: {
parentField: 'parentAccount',
async getRootLabel() {
let accountingSettings = await frappe.getSingle('AccountingSettings');
return accountingSettings.companyName;
}
}
}
}

View File

@ -3,11 +3,11 @@ const BaseDocument = require('frappejs/model/document');
module.exports = class Account extends BaseDocument {
async validate() {
if (!this.account_type) {
if (this.parent_account) {
this.account_type = await frappe.db.getValue('Account', this.parent_account, 'account_type');
if (!this.accountType) {
if (this.parentAccount) {
this.accountType = await frappe.db.getValue('Account', this.parentAccount, 'accountType');
} else {
this.account_type = 'Asset';
this.accountType = 'Asset';
}
}
}

View File

@ -1,3 +1,5 @@
const countryList = Object.keys(require('../../../fixtures/countryInfo.json')).sort();
module.exports = {
name: "AccountingSettings",
label: "AccountingSettings",
@ -9,15 +11,45 @@ module.exports = {
keywordFields: [],
fields: [
{
fieldname: "Company Name",
label: "companyName",
label: "Company Name",
fieldname: "companyName",
fieldtype: "Data",
required: 1
},
{
fieldname: "Writeoff Account",
label: "writeOffAccount",
label: "Writeoff Account",
fieldname: "writeOffAccount",
fieldtype: "Account"
},
{
"fieldname": "country",
"label": "Country",
"fieldtype": "Autocomplete",
"required": 1,
getList: () => countryList
},
{
"fieldname": "fullname",
"label": "Name",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "email",
"label": "Email",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "bankName",
"label": "Bank Name",
"fieldtype": "Data",
"required": 1
}
]

View File

@ -0,0 +1,50 @@
const frappe = require('frappejs');
const accountFields = ['accountType', 'rootType', 'isGroup'];
async function importAccounts(children, parent, rootType, rootAccount) {
for (let accountName in children) {
const child = children[accountName];
if (rootAccount) {
rootType = child.rootType;
}
if (!accountFields.includes(accountName)) {
let isGroup = identifyIsGroup(child);
const doc = frappe.newDoc({
doctype: 'Account',
name: accountName,
parentAccount: parent,
isGroup,
rootType,
accountType: child.accountType
})
await doc.insert()
await importAccounts(child, accountName, rootType)
}
}
}
function identifyIsGroup(child) {
if (child.isGroup) {
return child.isGroup;
}
const keys = Object.keys(child);
const children = keys.filter(key => !accountFields.includes(key))
if (children.length) {
return 1;
}
return 0;
}
module.exports = async function importCharts(chart) {
if (chart) {
await importAccounts(chart, '', '', true)
}
}

View File

@ -1,6 +1,7 @@
module.exports = {
models: {
Account: require('./doctype/Account/Account.js'),
AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'),
AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'),
Party: require('./doctype/Party/Party.js'),

View File

@ -1,15 +1,31 @@
{
"name": "frappe-accounting",
"description": "Simple Accounting app for everyone",
"productName": "Frappe Accounting",
"version": "1.0.0",
"main": "start.js",
"author": {
"name": "Frappe Technologies Pvt. Ltd.",
"email": "hello@frappe.io"
},
"build": {
"appId": "io.frappe.accounting"
},
"main": "main.js",
"license": "MIT",
"scripts": {
"test": "mocha tests",
"start": "nodemon start.js"
"start": "nodemon server.js",
"watch": "rollup -c --watch",
"electron": "electron main.js",
"electron-pack": "electron-packager . --overwrite",
"postinstall": "electron-builder install-app-deps"
},
"dependencies": {
"frappejs": "link:../frappejs"
"frappejs": "../frappejs"
},
"devDependencies": {
"electron": "1.8.4",
"electron-builder": "^20.6.2",
"electron-packager": "^11.2.0"
}
}

View File

@ -1,7 +1,7 @@
const server = require('frappejs/server');
const frappe = require('frappejs');
const GeneralLedger = require('../reports/generalLedger/GeneralLedger')
const naming = require('frappejs/model/naming')
const GeneralLedger = require('../reports/generalLedger/GeneralLedger');
const naming = require('frappejs/model/naming');
module.exports = {
async start() {

73
setup/config.js Normal file
View File

@ -0,0 +1,73 @@
const countryList = Object.keys(require('../fixtures/countryInfo.json')).sort();
module.exports = {
fields: [
{
"fieldname": "file",
"label": "File",
"fieldtype": "File",
"required": 1,
"directory": 1
},
{
"fieldname": "country",
"label": "Country",
"fieldtype": "Autocomplete",
"required": 1,
getList: () => countryList
},
{
"fieldname": "name",
"label": "Name",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "email",
"label": "Email",
"fieldtype": "Data",
"required": 1,
"inputType": "email"
},
{
"fieldname": "companyName",
"label": "Company Name",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "bankName",
"label": "Bank Name",
"fieldtype": "Data",
"required": 1
}
],
layout: [
{
title: 'Select File location',
fields: ['file']
},
{
title: 'Select Country',
fields: ['country']
},
{
title: 'Add a Profile',
fields: ['name', 'email']
},
{
title: 'Add your Company',
fields: ['companyName', 'bankName']
}
]
}

135
setup/index.js Normal file
View File

@ -0,0 +1,135 @@
const frappe = require('frappejs');
const utils = require('frappejs/client/ui/utils');
const slideConfigs = require('./config');
const FormLayout = require('frappejs/client/view/formLayout');
const Observable = require('frappejs/utils/observable');
module.exports = class SetupWizard {
constructor() {
this.slideCount = slideConfigs.layout.length;
this.indicatorList = [];
this.currentIndex = 0;
this.doc = new Observable();
this.promise = new Promise(resolve => {
this.onComplete = resolve;
});
this.footerButtons = [
{
label: 'Prev', name: 'prev',
action: this.prevSlide.bind(this),
condition: index => index !== 0
},
{
label: 'Next', name: 'next',
action: this.nextSlide.bind(this),
condition: index => index !== this.slideCount - 1
},
{
label: 'Complete', name: 'complete',
action: this.onComplete.bind(this, this.doc),
condition: index => index === this.slideCount - 1
}
];
this.make();
}
async start() {
this.showSlide(0);
return this.promise;
}
make() {
let body = document.querySelector('body');
this.container = frappe.ui.add('form', 'setup-container container', body);
this.$indicators = frappe.ui.add('div', 'indicators vertical-margin align-center', this.container);
this.makeSlides();
this.makeButtons();
}
makeSlides() {
this.formLayout = new FormLayout(Object.assign(slideConfigs, {
doc: this.doc
}));
this.container.appendChild(this.formLayout.form);
slideConfigs.layout.forEach(() => {
// indicator for each section
let indicator = frappe.ui.create('span', {
inside: this.$indicators,
className: 'indicator gray'
});
this.indicatorList.push(indicator);
});
}
makeButtons() {
this.linkArea = frappe.ui.add('div', 'setup-link-area align-right', this.container);
this.footerButtons.map(link => {
link.element = utils.addButton(link.label, this.linkArea, link.action);
});
}
showSlide(index) {
this.currentIndex = index;
utils.activate(this.container, `.form-section:nth-child(${index + 1})`, '.form-section', 'active');
this.activateIndicator(index);
this.showFooterLinks(index);
}
prevSlide() {
this.showSlide(this.currentIndex - 1);
}
nextSlide() {
const isValid = this.validateCurrentSlide();
frappe.ui.toggleClass(this.formLayout.sections[this.currentIndex], 'was-validated', !isValid);
if (isValid) {
this.showSlide(this.currentIndex + 1);
}
}
validateCurrentSlide() {
const fields = this.getFieldsInSlide(this.currentIndex);
const inputValidityMap = fields.map(field => this.formLayout.controls[field].input.checkValidity());
const isValid = !inputValidityMap.includes(false);
return isValid;
}
getFieldsInSlide(index) {
const visibleSection = slideConfigs.layout[index];
const fieldsInSlide = visibleSection.fields ||
visibleSection.columns.reduce(
(col, fields) => fields.concat(col.fields), []
);
return fieldsInSlide;
}
activateIndicator(index) {
this.indicatorList.forEach(indicator => indicator.classList.add('gray'));
let indicator = this.indicatorList[index];
utils.activate(this.$indicators, indicator, '.gray', 'blue', index);
frappe.ui.removeClass(indicator, 'gray');
indicator.classList.remove('gray');
}
showFooterLinks(index) {
this.footerButtons.map(link => {
const show = link.condition(this.currentIndex);
if (show) {
link.element.classList.remove('hide');
} else {
link.element.classList.add('hide');
}
})
}
}

View File

@ -8,7 +8,7 @@ async function makeFixtures() {
await frappe.insert({doctype:'Party', name:'Test Customer'})
await frappe.insert({doctype:'Item', name:'Test Item 1', description:'Test Item Description 1', unit:'No', rate: 100})
await frappe.insert({doctype:'Item', name:'Test Item 2', description:'Test Item Description 2', unit:'No', rate: 200})
await frappe.insert({doctype:'Account', name:'GST', parent_account: 'Liabilities'});
await frappe.insert({doctype:'Account', name:'GST', parentAccount: 'Liabilities'});
await frappe.insert({doctype:'Tax', name:'GST',
details: [{account: 'GST', rate:10}]
})

View File

@ -7207,6 +7207,10 @@ span.CodeMirror-selectedtext {
border-bottom: 1px solid #d1d8dd; }
.body-scrollable.row-highlight-all .data-table-row:not(.row-unhighlight) {
background-color: #f5f7fa; }
.body-scrollable .no-data td {
text-align: center;
padding: 8px;
padding: 0.5rem; }
.data-table-header {
position: absolute;
top: 0;
@ -7486,3 +7490,38 @@ html {
mark {
padding: none;
background: inherit; }
.align-right {
text-align: right; }
.align-center {
text-align: center; }
.btn-sm, .btn-group-sm > .btn {
margin: 0.25rem; }
.vertical-margin {
margin: 1rem 0px; }
.tree-body {
padding: 1rem 2rem; }
f-tree-node {
--tree-node-hover: #f8f9fa; }
.setup-container {
margin: 40px auto;
padding: 20px 0px;
width: 450px;
border: 1px solid #dee2e6;
border-radius: 4px; }
.setup-container h3 {
text-align: center; }
.setup-container .form-section {
display: none; }
.setup-container .form-section.active {
display: block; }
.setup-container .setup-link-area {
margin: 0.25rem 2rem; }
input[type=file] {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1; }
.was-validated input[type=file]:invalid + button {
border-color: #dc3545; }

3431
www/dist/js/bundle.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,21 @@
const client = require('frappejs/client');
const appClient = require('../client');
const SetupWizard = require('../setup');
// start server
client.start({
columns: 3,
server: 'localhost:8000'
server: 'localhost:8000',
makeDesk: 0
}).then(() => {
// new SetupWizard({
// postSetup: async (data) => {
// client.makeDesk(3);
// appClient.start();
// await frappe.router.setRoute('list', 'ToDo');
// }
// });
client.makeDesk(3);
appClient.start();
});

1241
yarn.lock

File diff suppressed because it is too large Load Diff