mirror of
https://github.com/frappe/books.git
synced 2024-11-14 09:24:04 +00:00
Merge branch 'master' into models
This commit is contained in:
commit
1a59b20b84
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@ Thumbs.db
|
|||||||
*.log
|
*.log
|
||||||
.cache
|
.cache
|
||||||
/temp
|
/temp
|
||||||
|
dist
|
||||||
|
Frappe Accounting*
|
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -19,7 +19,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
|
|
||||||
frappe.desk.menu.addItem('ToDo', '#list/ToDo');
|
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('Items', '#list/Item');
|
||||||
frappe.desk.menu.addItem('Customers', '#list/Customer');
|
frappe.desk.menu.addItem('Customers', '#list/Customer');
|
||||||
frappe.desk.menu.addItem('Quotation', '#list/Quotation');
|
frappe.desk.menu.addItem('Quotation', '#list/Quotation');
|
||||||
@ -29,7 +29,7 @@ module.exports = {
|
|||||||
frappe.desk.menu.addItem('Contact', "#list/Contact");
|
frappe.desk.menu.addItem('Contact', "#list/Contact");
|
||||||
frappe.desk.menu.addItem('Settings', () => frappe.desk.showFormModal('SystemSettings'));
|
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);
|
frappe.router.show(window.location.hash);
|
||||||
|
|
||||||
|
91
electron/client.js
Normal file
91
electron/client.js
Normal 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
12
electron/index.html
Normal 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
2710
fixtures/countryInfo.json
Normal file
File diff suppressed because it is too large
Load Diff
173
fixtures/standardCOA.js
Normal file
173
fixtures/standardCOA.js
Normal 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
60
main.js
Normal 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.
|
@ -5,7 +5,8 @@ module.exports = {
|
|||||||
"isSingle": 0,
|
"isSingle": 0,
|
||||||
"keywordFields": [
|
"keywordFields": [
|
||||||
"name",
|
"name",
|
||||||
"account_type"
|
"rootType",
|
||||||
|
"accountType"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -15,7 +16,7 @@ module.exports = {
|
|||||||
"required": 1
|
"required": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "parent_account",
|
"fieldname": "parentAccount",
|
||||||
"label": "Parent Account",
|
"label": "Parent Account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "Account",
|
"target": "Account",
|
||||||
@ -27,8 +28,8 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "account_type",
|
"fieldname": "rootType",
|
||||||
"label": "Account Type",
|
"label": "Root Type",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"options": [
|
"options": [
|
||||||
"Asset",
|
"Asset",
|
||||||
@ -37,6 +38,37 @@ module.exports = {
|
|||||||
"Income",
|
"Income",
|
||||||
"Expense"
|
"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: {
|
listSettings: {
|
||||||
getFields(list) {
|
getFields(list) {
|
||||||
return ['name', 'account_type'];
|
return ['name', 'accountType', 'rootType'];
|
||||||
},
|
},
|
||||||
getRowHTML(list, data) {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,11 +3,11 @@ const BaseDocument = require('frappejs/model/document');
|
|||||||
|
|
||||||
module.exports = class Account extends BaseDocument {
|
module.exports = class Account extends BaseDocument {
|
||||||
async validate() {
|
async validate() {
|
||||||
if (!this.account_type) {
|
if (!this.accountType) {
|
||||||
if (this.parent_account) {
|
if (this.parentAccount) {
|
||||||
this.account_type = await frappe.db.getValue('Account', this.parent_account, 'account_type');
|
this.accountType = await frappe.db.getValue('Account', this.parentAccount, 'accountType');
|
||||||
} else {
|
} else {
|
||||||
this.account_type = 'Asset';
|
this.accountType = 'Asset';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
const countryList = Object.keys(require('../../../fixtures/countryInfo.json')).sort();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: "AccountingSettings",
|
name: "AccountingSettings",
|
||||||
label: "AccountingSettings",
|
label: "AccountingSettings",
|
||||||
@ -9,15 +11,45 @@ module.exports = {
|
|||||||
keywordFields: [],
|
keywordFields: [],
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldname: "Company Name",
|
label: "Company Name",
|
||||||
label: "companyName",
|
fieldname: "companyName",
|
||||||
fieldtype: "Data",
|
fieldtype: "Data",
|
||||||
required: 1
|
required: 1
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
fieldname: "Writeoff Account",
|
label: "Writeoff Account",
|
||||||
label: "writeOffAccount",
|
fieldname: "writeOffAccount",
|
||||||
fieldtype: "Account"
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
50
models/doctype/account/importCOA.js
Normal file
50
models/doctype/account/importCOA.js
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
models: {
|
models: {
|
||||||
Account: require('./doctype/Account/Account.js'),
|
Account: require('./doctype/Account/Account.js'),
|
||||||
|
AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'),
|
||||||
AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'),
|
AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'),
|
||||||
Party: require('./doctype/Party/Party.js'),
|
Party: require('./doctype/Party/Party.js'),
|
||||||
|
|
||||||
|
22
package.json
22
package.json
@ -1,15 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "frappe-accounting",
|
"name": "frappe-accounting",
|
||||||
|
"description": "Simple Accounting app for everyone",
|
||||||
|
"productName": "Frappe Accounting",
|
||||||
"version": "1.0.0",
|
"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",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha tests",
|
"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": {
|
"dependencies": {
|
||||||
"frappejs": "link:../frappejs"
|
"frappejs": "../frappejs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"electron": "1.8.4",
|
||||||
|
"electron-builder": "^20.6.2",
|
||||||
|
"electron-packager": "^11.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const server = require('frappejs/server');
|
const server = require('frappejs/server');
|
||||||
const frappe = require('frappejs');
|
const frappe = require('frappejs');
|
||||||
const GeneralLedger = require('../reports/generalLedger/GeneralLedger')
|
const GeneralLedger = require('../reports/generalLedger/GeneralLedger');
|
||||||
const naming = require('frappejs/model/naming')
|
const naming = require('frappejs/model/naming');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async start() {
|
async start() {
|
||||||
|
73
setup/config.js
Normal file
73
setup/config.js
Normal 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
135
setup/index.js
Normal 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');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ async function makeFixtures() {
|
|||||||
await frappe.insert({doctype:'Party', name:'Test Customer'})
|
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 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:'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',
|
await frappe.insert({doctype:'Tax', name:'GST',
|
||||||
details: [{account: 'GST', rate:10}]
|
details: [{account: 'GST', rate:10}]
|
||||||
})
|
})
|
||||||
|
39
www/dist/css/style.css
vendored
39
www/dist/css/style.css
vendored
@ -7207,6 +7207,10 @@ span.CodeMirror-selectedtext {
|
|||||||
border-bottom: 1px solid #d1d8dd; }
|
border-bottom: 1px solid #d1d8dd; }
|
||||||
.body-scrollable.row-highlight-all .data-table-row:not(.row-unhighlight) {
|
.body-scrollable.row-highlight-all .data-table-row:not(.row-unhighlight) {
|
||||||
background-color: #f5f7fa; }
|
background-color: #f5f7fa; }
|
||||||
|
.body-scrollable .no-data td {
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px;
|
||||||
|
padding: 0.5rem; }
|
||||||
.data-table-header {
|
.data-table-header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -7486,3 +7490,38 @@ html {
|
|||||||
mark {
|
mark {
|
||||||
padding: none;
|
padding: none;
|
||||||
background: inherit; }
|
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; }
|
||||||
|
3427
www/dist/js/bundle.js
vendored
3427
www/dist/js/bundle.js
vendored
File diff suppressed because it is too large
Load Diff
14
www/index.js
14
www/index.js
@ -1,11 +1,21 @@
|
|||||||
const client = require('frappejs/client');
|
const client = require('frappejs/client');
|
||||||
const appClient = require('../client');
|
const appClient = require('../client');
|
||||||
|
const SetupWizard = require('../setup');
|
||||||
|
|
||||||
// start server
|
// start server
|
||||||
client.start({
|
client.start({
|
||||||
columns: 3,
|
server: 'localhost:8000',
|
||||||
server: 'localhost:8000'
|
makeDesk: 0
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
// new SetupWizard({
|
||||||
|
// postSetup: async (data) => {
|
||||||
|
// client.makeDesk(3);
|
||||||
|
// appClient.start();
|
||||||
|
|
||||||
|
// await frappe.router.setRoute('list', 'ToDo');
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
client.makeDesk(3);
|
||||||
appClient.start();
|
appClient.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user