mirror of
https://github.com/frappe/books.git
synced 2025-02-02 12:08:27 +00:00
commit
e4dd0a9954
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ Thumbs.db
|
||||
*.log
|
||||
.cache
|
||||
/temp
|
||||
dist
|
68
electron/client.js
Normal file
68
electron/client.js
Normal file
@ -0,0 +1,68 @@
|
||||
const path = require('path');
|
||||
const electron = require('frappejs/client/electron');
|
||||
const { writeFile } = require('frappejs/server/utils');
|
||||
const appClient = require('../client');
|
||||
const SetupWizard = require('../setup');
|
||||
|
||||
(async () => {
|
||||
const configFilePath = path.join(require('os').homedir(), '.config', 'frappe-accounting', 'settings.json');
|
||||
|
||||
let settings, dbPath;
|
||||
try {
|
||||
settings = require(configFilePath);
|
||||
} catch(e) {
|
||||
settings = {}
|
||||
}
|
||||
|
||||
if (settings.dbPath) {
|
||||
dbPath = settings.dbPath;
|
||||
electron.start({
|
||||
dbPath,
|
||||
models: require('../models')
|
||||
}).then(() => {
|
||||
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 () => {
|
||||
await writeFile(configFilePath, JSON.stringify({
|
||||
directory: path.dirname(dbPath),
|
||||
dbPath: dbPath
|
||||
}));
|
||||
|
||||
const doc = await frappe.getDoc('AccountingSettings');
|
||||
|
||||
await doc.set('companyName', companyName);
|
||||
await doc.set('file', dbPath);
|
||||
await doc.set('country', country);
|
||||
await doc.set('fullname', name);
|
||||
await doc.set('email', email);
|
||||
await doc.set('abbreviation', abbreviation);
|
||||
await doc.set('bankName', bankName);
|
||||
|
||||
await doc.update();
|
||||
|
||||
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
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: 800, height: 600})
|
||||
|
||||
// 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.
|
@ -1,3 +1,5 @@
|
||||
const countryList = Object.keys(require('../../../fixtures/countryInfo.json')).sort();
|
||||
|
||||
module.exports = {
|
||||
name: "AccountingSettings",
|
||||
label: "AccountingSettings",
|
||||
@ -9,15 +11,60 @@ 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": "file",
|
||||
"label": "File",
|
||||
"fieldtype": "Data",
|
||||
"required": 1,
|
||||
"directory": 1
|
||||
},
|
||||
|
||||
{
|
||||
"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": "abbreviation",
|
||||
"label": "Abbreviation",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "bankName",
|
||||
"label": "Bank Name",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
}
|
||||
|
||||
]
|
||||
|
@ -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'),
|
||||
|
||||
|
22
package.json
22
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -1,48 +1,52 @@
|
||||
module.exports = [
|
||||
{
|
||||
const countryList = Object.keys(require('../fixtures/countryInfo.json')).sort();
|
||||
|
||||
module.exports = {
|
||||
fields: [
|
||||
|
||||
{
|
||||
"fieldname": "country",
|
||||
"label": "Country",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
}
|
||||
],
|
||||
title: 'Select Country'
|
||||
"fieldname": "file",
|
||||
"label": "File",
|
||||
"fieldtype": "File",
|
||||
"required": 1,
|
||||
"directory": 1
|
||||
},
|
||||
|
||||
{
|
||||
fields: [
|
||||
"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
|
||||
}
|
||||
],
|
||||
title: 'Add a Profile'
|
||||
},
|
||||
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
"fieldname": "companyName",
|
||||
"label": "Company Name",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "abbreviation",
|
||||
"label": "Abbreviation",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "bankName",
|
||||
"label": "Bank Name",
|
||||
@ -50,6 +54,33 @@ module.exports = [
|
||||
"required": 1
|
||||
}
|
||||
],
|
||||
title: 'Add your Company'
|
||||
|
||||
layout: [
|
||||
{
|
||||
title: 'Select File location',
|
||||
fields: ['file']
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Select Country',
|
||||
fields: ['country']
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Add a Profile',
|
||||
fields: ['name', 'email']
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Add your Company',
|
||||
columns: [
|
||||
{
|
||||
fields: ['companyName', 'bankName']
|
||||
},
|
||||
{
|
||||
fields: ['abbreviation']
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
127
setup/index.js
127
setup/index.js
@ -3,20 +3,44 @@ const utils = require('frappejs/client/ui/utils');
|
||||
const slideConfigs = require('./config');
|
||||
const Tree = require('frappejs/client/ui/tree');
|
||||
const FormLayout = require('frappejs/client/view/formLayout');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
module.exports = class SetupWizard {
|
||||
constructor({postSetup = () => {}}) {
|
||||
this.slideList = [];
|
||||
constructor() {
|
||||
this.slideCount = slideConfigs.layout.length;
|
||||
this.indicatorList = [];
|
||||
this.footerLinks = {};
|
||||
|
||||
this.currentIndex = 0;
|
||||
this.data = {};
|
||||
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.postSetup = postSetup;
|
||||
this.make();
|
||||
}
|
||||
|
||||
this.showSlide(this.currentIndex);
|
||||
async start() {
|
||||
this.showSlide(0);
|
||||
return this.promise;
|
||||
}
|
||||
|
||||
make() {
|
||||
@ -25,60 +49,39 @@ module.exports = class SetupWizard {
|
||||
this.$indicators = frappe.ui.add('div', 'indicators vertical-margin align-center', this.container);
|
||||
|
||||
this.makeSlides();
|
||||
this.makeLinks();
|
||||
this.makeButtons();
|
||||
}
|
||||
|
||||
makeSlides() {
|
||||
slideConfigs.forEach(config => {
|
||||
this.formLayout = new FormLayout(config);
|
||||
this.slideList.push(this.formLayout);
|
||||
let form = this.formLayout.form;
|
||||
this.container.appendChild(form);
|
||||
|
||||
let title = frappe.ui.create('h3', {
|
||||
className: 'text-extra-muted',
|
||||
innerHTML: config.title
|
||||
})
|
||||
form.insertBefore(title, form.firstChild);
|
||||
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);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
makeLinks() {
|
||||
makeButtons() {
|
||||
this.linkArea = frappe.ui.add('div', 'setup-link-area align-right', this.container);
|
||||
|
||||
// this.formLayout.on('change', () => {
|
||||
// const show = this.doc._dirty && !this.doc.submitted;
|
||||
// this.saveButton.classList.toggle('hide', !show);
|
||||
// });
|
||||
|
||||
this.getFooterLinks().map(link => {
|
||||
let $link = utils.addLink(link.label, this.linkArea, () => {
|
||||
this.buildData();
|
||||
link.action(this.data);
|
||||
});
|
||||
this.footerLinks[link.name] = $link;
|
||||
})
|
||||
}
|
||||
|
||||
buildData() {
|
||||
this.data = {};
|
||||
this.slideList.forEach(slide => {
|
||||
Object.assign(this.data, slide.doc);
|
||||
this.footerButtons.map(link => {
|
||||
link.element = utils.addButton(link.label, this.linkArea, link.action);
|
||||
});
|
||||
}
|
||||
|
||||
showSlide(index) {
|
||||
utils.activate(this.container, this.slideList[index].form, 'form-body', 'active');
|
||||
this.slideList[index].controlList[0].input.blur();
|
||||
this.currentIndex = index;
|
||||
utils.activate(this.container, `.form-section:nth-child(${index + 1})`, '.form-section', 'active');
|
||||
this.activateIndicator(index);
|
||||
this.showFooterLinks(index);
|
||||
this.currentIndex = index;
|
||||
}
|
||||
|
||||
prevSlide() {
|
||||
@ -90,46 +93,22 @@ module.exports = class SetupWizard {
|
||||
}
|
||||
|
||||
activateIndicator(index) {
|
||||
this.indicatorList.forEach(indicator => {indicator.classList.add('gray')});
|
||||
this.indicatorList.forEach(indicator => indicator.classList.add('gray'));
|
||||
let indicator = this.indicatorList[index];
|
||||
utils.activate(this.$indicators, indicator, 'gray', 'blue', index);
|
||||
utils.activate(this.$indicators, indicator, '.gray', 'blue', index);
|
||||
|
||||
frappe.ui.removeClass(indicator, 'gray');
|
||||
indicator.classList.remove('gray');
|
||||
}
|
||||
|
||||
showFooterLinks(index) {
|
||||
let mat = [1, 1, 0]
|
||||
if(index === 0) {
|
||||
mat = [0, 1, 0];
|
||||
} else if (index === this.slideList.length - 1) {
|
||||
mat = [1, 0, 1];
|
||||
this.footerButtons.map(link => {
|
||||
const show = link.condition(this.currentIndex);
|
||||
if (show) {
|
||||
link.element.classList.remove('hide');
|
||||
} else {
|
||||
link.element.classList.add('hide');
|
||||
}
|
||||
this.showHideLinks(mat);
|
||||
}
|
||||
|
||||
showHideLinks(matrix = [1, 1, 0]) {
|
||||
let linkNames = this.getFooterLinks().map(link => link.name);
|
||||
matrix.forEach((value, i) => {
|
||||
const fn = value ? 'remove' : 'add';
|
||||
this.footerLinks[linkNames[i]].classList[fn]('hide');
|
||||
});
|
||||
}
|
||||
|
||||
getFooterLinks() {
|
||||
return [
|
||||
{
|
||||
label: 'Prev', name: 'prev',
|
||||
action: this.prevSlide.bind(this)
|
||||
},
|
||||
{
|
||||
label: 'Next', name: 'next',
|
||||
action: this.nextSlide.bind(this)
|
||||
},
|
||||
{
|
||||
label: 'Complete', name: 'complete',
|
||||
action: this.postSetup.bind(this)
|
||||
}
|
||||
];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
83
www/dist/css/style.css
vendored
83
www/dist/css/style.css
vendored
@ -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;
|
||||
@ -7494,6 +7498,74 @@ mark {
|
||||
margin: 0.25rem; }
|
||||
.vertical-margin {
|
||||
margin: 1rem 0px; }
|
||||
.tree {
|
||||
padding: 15px; }
|
||||
.tree li {
|
||||
list-style: none;
|
||||
margin: 2px 0px; }
|
||||
ul.tree-children {
|
||||
padding-left: 20px; }
|
||||
.tree-link {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 1px; }
|
||||
.tree-link .node-parent {
|
||||
color: #6c757d;
|
||||
font-size: 14px;
|
||||
width: 10px;
|
||||
text-align: center; }
|
||||
.tree-link .node-leaf {
|
||||
color: #ced4da; }
|
||||
.tree-link .node-parent, .tree-link .node-leaf {
|
||||
margin-right: 5px;
|
||||
margin-left: 2px;
|
||||
margin-top: 3px; }
|
||||
.tree-link.active svg {
|
||||
color: #007bff; }
|
||||
.tree-link.active a {
|
||||
color: #6c757d; }
|
||||
.tree-hover {
|
||||
background-color: #e9ecef;
|
||||
min-height: 20px;
|
||||
border: 1px solid #6c757d; }
|
||||
.tree-node-toolbar {
|
||||
display: inline-block;
|
||||
padding: 0px 5px;
|
||||
margin-left: 15px;
|
||||
margin-bottom: -4px;
|
||||
margin-top: -8px; }
|
||||
.tree.with-skeleton, .tree.with-skeleton .tree-node {
|
||||
position: relative; }
|
||||
.tree.with-skeleton.opened::before, .tree.with-skeleton:last-child::after, .tree.with-skeleton .tree-node.opened::before, .tree.with-skeleton .tree-node:last-child::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 7px;
|
||||
height: calc(100% - 23px);
|
||||
width: 1px;
|
||||
background: #ced4da;
|
||||
z-index: -1; }
|
||||
.tree.with-skeleton:last-child::after, .tree.with-skeleton .tree-node:last-child::after {
|
||||
top: 11px;
|
||||
left: -13px;
|
||||
height: calc(100% - 15px);
|
||||
width: 3px;
|
||||
background: #fff; }
|
||||
.tree.with-skeleton.opened > .tree-children > .tree-node > .tree-link::before, .tree.with-skeleton .tree-node.opened > .tree-children > .tree-node > .tree-link::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 1px;
|
||||
top: 10px;
|
||||
left: -12px;
|
||||
z-index: -1;
|
||||
background: #ced4da; }
|
||||
.tree.with-skeleton.opened::before {
|
||||
left: 22px;
|
||||
top: 33px;
|
||||
height: calc(100% - 67px); }
|
||||
.tree-link.active ~ .balance-area {
|
||||
color: #6c757d !important; }
|
||||
.setup-container {
|
||||
margin: 40px auto;
|
||||
padding: 20px 0px;
|
||||
@ -7502,9 +7574,16 @@ mark {
|
||||
border-radius: 4px; }
|
||||
.setup-container h3 {
|
||||
text-align: center; }
|
||||
.setup-container .form-body {
|
||||
.setup-container .form-section {
|
||||
display: none; }
|
||||
.setup-container .form-body.active {
|
||||
.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; }
|
||||
|
1567
www/dist/js/bundle.js
vendored
1567
www/dist/js/bundle.js
vendored
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user