2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 10:58:59 +00:00

added ledger

This commit is contained in:
Rushabh Mehta 2018-03-05 22:15:40 +05:30
parent c5b9ae8691
commit b72878110d
16 changed files with 3762 additions and 2657 deletions

View File

@ -0,0 +1,83 @@
const frappe = require('frappejs');
module.exports = class LedgerPosting {
constructor({reference, party, date, description}) {
Object.assign(this, arguments[0]);
this.entries = [];
this.entryMap = {};
}
debit(account, amount) {
const entry = this.getEntry(account);
entry.debit += amount;
}
credit(account, amount) {
const entry = this.getEntry(account);
entry.credit += amount;
}
getEntry(account) {
if (!this.entryMap[account]) {
const entry = {
account: account,
party: this.party,
date: this.date || this.reference.date,
referenceType: this.reference.doctype,
referenceName: this.reference.name,
description: this.description,
debit: 0,
credit: 0
};
this.entries.push(entry);
this.entryMap[account] = entry;
}
return this.entryMap[account];
}
async post() {
this.validateEntries();
await this.insertEntries();
}
async postReverse() {
this.validateEntries();
let temp;
for (let entry of this.entries) {
temp = entry.debit;
entry.debit = entry.credit;
entry.credit = temp;
}
await this.insertEntries();
}
validateEntries() {
let debit = 0, credit = 0;
let debitAccounts = [], creditAccounts = [];
for (let entry of this.entries) {
debit += entry.debit;
credit += entry.credit;
if (debit) {
debitAccounts.push(entry.account);
} else {
creditAccounts.push(entry.account);
}
}
if (debit !== credit) {
throw frappe.errors.ValidationError(frappe._("Debit {0} must be equal to Credit {1}", [debit, credit]));
}
}
async insertEntries() {
for (let entry of this.entries) {
let entryDoc = frappe.newDoc({
doctype: 'AccountingLedgerEntry'
});
Object.assign(entryDoc, entry);
await entryDoc.insert();
}
}
}

67
dist/css/style.css vendored
View File

@ -1,3 +1,4 @@
@charset "UTF-8";
/*!
* Bootstrap v4.0.0 (https://getbootstrap.com)
* Copyright 2011-2018 The Bootstrap Authors
@ -7129,10 +7130,10 @@ div.CodeMirror-dragcursors {
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext {
background: none; }
/* This file is processed by postcss */
/* variables */
.data-table {
/* styling */
width: 100%;
position: relative;
overflow: auto; }
/* resets */
@ -7184,16 +7185,22 @@ span.CodeMirror-selectedtext {
top: 50%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%); }
.data-table .trash-container {
position: absolute;
bottom: 0;
left: 30%;
right: 30%;
height: 70px;
background: palevioletred;
opacity: 0.5; }
.data-table .hide {
display: none; }
.data-table .toast-message {
position: absolute;
bottom: 16px;
bottom: 1rem;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%); }
.data-table .toast-message span {
display: inline-block;
background-color: rgba(0, 0, 0, 0.8);
color: #dfe2e5;
border-radius: 3px;
padding: 8px 16px;
padding: 0.5rem 1rem; }
.body-scrollable {
max-height: 500px;
overflow: auto;
@ -7256,42 +7263,54 @@ span.CodeMirror-selectedtext {
padding: 0.5rem 1rem; }
.data-table-header .data-table-dropdown-list > div:hover {
background-color: #f5f7fa; }
.data-table-header .data-table-col.remove-column {
.data-table-header .data-table-cell.remove-column {
background-color: #FD8B8B;
-webkit-transition: 300ms background-color ease-in-out;
transition: 300ms background-color ease-in-out; }
.data-table-header .data-table-col.sortable-chosen {
.data-table-header .data-table-cell.sortable-chosen {
background-color: #f5f7fa; }
.data-table-col {
.data-table-cell {
position: relative; }
.data-table-col .content {
.data-table-cell .content {
padding: 8px;
padding: 0.5rem;
border: 2px solid transparent; }
.data-table-col .content.ellipsis {
.data-table-cell .content.ellipsis {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden; }
.data-table-col .edit-cell {
.data-table-cell .edit-cell {
display: none;
padding: 8px;
padding: 0.5rem;
background: #fff;
z-index: 1;
height: 100%; }
.data-table-col.selected .content {
.data-table-cell.selected .content {
border: 2px solid #5292f7; }
.data-table-col.editing .content {
.data-table-cell.editing .content {
display: none; }
.data-table-col.editing .edit-cell {
.data-table-cell.editing .edit-cell {
border: 2px solid #5292f7;
display: block; }
.data-table-col.highlight {
.data-table-cell.highlight {
background-color: #f5f7fa; }
.data-table-col:hover .column-resizer {
.data-table-cell:hover .column-resizer {
display: inline-block; }
.data-table-col:hover .data-table-dropdown-toggle {
.data-table-cell:hover .data-table-dropdown-toggle {
display: block; }
.data-table-cell .tree-node {
display: inline-block;
position: relative; }
.data-table-cell .toggle {
display: inline-block;
position: absolute;
padding: 0 4px;
cursor: pointer; }
.data-table-cell .toggle:before {
content: '▼'; }
.data-table-cell.tree-close .toggle:before {
content: '►'; }
.data-table-row.row-highlight {
background-color: #f5f7fa; }
.noselect {
@ -7363,6 +7382,12 @@ html {
padding: 200px 0px; }
.form-body {
padding: 2rem; }
.form-body .form-check {
margin-bottom: 0.5rem; }
.form-body .form-check .form-check-input {
margin-top: 0.25rem; }
.form-body .form-check .form-check-label {
margin-left: 0.25rem; }
.form-body .form-control.font-weight-bold {
background-color: lightyellow; }
.form-body .alert {

5979
dist/js/bundle.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
module.exports = {
name: "AccountingLedgerEntry",
label: "Ledger Entry",
naming: "autoincrement",
doctype: "DocType",
isSingle: 0,
isChild: 0,
keywordFields: [],
fields: [
{
fieldname: "date",
label: "Date",
fieldtype: "Date"
},
{
fieldname: "account",
label: "Account",
fieldtype: "Link",
target: "Account",
required: 1
},
{
fieldname: "description",
label: "Description",
fieldtype: "Text"
},
{
fieldname: "party",
label: "Party",
fieldtype: "Link",
target: "Party",
required: 1
},
{
fieldname: "debit",
label: "Debit",
fieldtype: "Currency",
},
{
fieldname: "credit",
label: "Credit",
fieldtype: "Currency",
},
{
fieldname: "againstAccount",
label: "Against Account",
fieldtype: "Text",
required: 1
},
{
fieldname: "referenceType",
label: "Reference Type",
fieldtype: "Data",
},
{
fieldname: "referenceName",
label: "Reference Name",
fieldtype: "Dynamic Link",
references: "referenceType"
},
{
fieldname: "balance",
label: "Balance",
fieldtype: "Currency",
},
]
}

View File

@ -23,9 +23,15 @@ module.exports = {
"fieldname": "customer",
"label": "Customer",
"fieldtype": "Link",
"target": "Customer",
"target": "Party",
"required": 1
},
{
"fieldname": "account",
"label": "Account",
"fieldtype": "Link",
"target": "Account"
},
{
"fieldname": "items",
"label": "Items",
@ -72,5 +78,24 @@ module.exports = {
"label": "Terms",
"fieldtype": "Text"
}
],
layout: [
// section 1
{
columns: [
{ fields: [ "customer", "account" ] },
{ fields: [ "date" ] }
]
},
// section 2
{ fields: [ "items" ] },
// section 3
{ fields: [ "netTotal", "taxes", "grandTotal" ] },
// section 4
{ fields: [ "terms" ] },
]
}

View File

@ -0,0 +1,31 @@
const Invoice = require('./invoiceDocument');
const frappe = require('frappejs');
const LedgerPosting = require.main.require('./accounting/ledgerPosting');
module.exports = class InvoiceServer extends Invoice {
getPosting() {
let entries = new LedgerPosting({reference: this, party: this.customer});
entries.debit(this.account, this.grandTotal);
for (let item of this.items) {
entries.credit(item.account, item.amount);
}
if (this.taxes) {
for (let tax of this.taxes) {
entries.credit(tax.account, tax.amount);
}
}
return entries;
}
async afterSubmit() {
console.log('here');
await this.getPosting().post();
}
async afterRevert() {
await this.getPosting().postReverse();
}
}

View File

@ -2,10 +2,6 @@ const BaseDocument = require('frappejs/model/document');
const frappe = require('frappejs');
module.exports = class Invoice extends BaseDocument {
async afterSubmit() {
// make ledger entry
}
async getRowTax(row) {
if (row.tax) {
let tax = await this.getTax(row.tax);
@ -62,6 +58,7 @@ module.exports = class Invoice extends BaseDocument {
// clear no taxes
this.taxes = this.taxes.filter(d => d.amount);
}
getGrandTotal() {
this.makeTaxSummary();
let grandTotal = this.netTotal;

View File

@ -1,10 +1,11 @@
module.exports = {
"name": "InvoiceItem",
"doctype": "DocType",
"isSingle": 0,
"isChild": 1,
"keywordFields": [],
"fields": [
name: "InvoiceItem",
doctype: "DocType",
isSingle: 0,
isChild: 1,
keywordFields: [],
layout: 'ratio',
fields: [
{
"fieldname": "item",
"label": "Item",
@ -32,6 +33,14 @@ module.exports = {
"required": 1,
formula: (row, doc) => doc.getFrom('Item', row.item, 'rate')
},
{
fieldname: "account",
label: "Account",
hidden: 1,
fieldtype: "Link",
target: "Account",
formula: (row, doc) => doc.getFrom('Item', row.item, 'incomeAccount')
},
{
"fieldname": "tax",
"label": "Tax",

View File

@ -1,28 +1,29 @@
module.exports = {
"name": "Item",
"doctype": "DocType",
"isSingle": 0,
"keywordFields": [
name: "Item",
doctype: "DocType",
isSingle: 0,
keywordFields: [
"name",
"description"
],
"fields": [
fields: [
{
"fieldname": "name",
"label": "Item Name",
"fieldtype": "Data",
"required": 1
fieldname: "name",
label: "Item Name",
fieldtype: "Data",
required: 1
},
{
"fieldname": "description",
"label": "Description",
"fieldtype": "Text"
fieldname: "description",
label: "Description",
fieldtype: "Text"
},
{
"fieldname": "unit",
"label": "Unit",
"fieldtype": "Select",
"options": [
fieldname: "unit",
label: "Unit",
fieldtype: "Select",
default: "No",
options: [
"No",
"Kg",
"Gram",
@ -31,15 +32,48 @@ module.exports = {
]
},
{
"fieldname": "tax",
"label": "Tax",
"fieldtype": "Link",
"target": "Tax"
fieldname: "incomeAccount",
label: "Income Account",
fieldtype: "Link",
target: "Account"
},
{
"fieldname": "rate",
"label": "Rate",
"fieldtype": "Currency"
fieldname: "expenseAccount",
label: "Expense Account",
fieldtype: "Link",
target: "Account"
},
{
fieldname: "tax",
label: "Tax",
fieldtype: "Link",
target: "Tax"
},
{
fieldname: "rate",
label: "Rate",
fieldtype: "Currency"
}
],
layout: [
// section 1
{
columns: [
{ fields: [ "name", "unit" ] },
{ fields: [ "rate" ] }
]
},
// section 2
{ fields: [ "description" ] },
// section 3
{
title: "Accounting",
columns: [
{ fields: [ "incomeAccount", "expenseAccount" ] },
{ fields: [ "tax" ] }
]
}
]
}

View File

@ -0,0 +1,13 @@
const BaseList = require('frappejs/client/view/list');
const frappe = require('frappejs');
module.exports = class CustomerList extends BaseList {
constructor({doctype, parent, fields, page}) {
super({doctype: 'Party', parent: parent, fields: fields, page: page});
}
getFilters() {
let filters = super.getFilters();
filters.customer = 1;
return filters;
}
}

View File

@ -1,5 +1,5 @@
module.exports = {
"name": "Customer",
"name": "Party",
"doctype": "DocType",
"isSingle": 0,
"istable": 0,
@ -12,6 +12,16 @@ module.exports = {
"label": "Name",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "customer",
"label": "Customer",
"fieldtype": "Check"
},
{
"fieldname": "supplier",
"label": "Supplier",
"fieldtype": "Check"
}
]
}

View File

@ -1,11 +1,14 @@
module.exports = {
Account: require('./doctype/Account/Account.js'),
Customer: require('./doctype/Customer/Customer.js'),
Item: require('./doctype/Item/Item.js'),
Invoice: require('./doctype/Invoice/Invoice.js'),
InvoiceItem: require('./doctype/InvoiceItem/InvoiceItem.js'),
InvoiceSettings: require('./doctype/InvoiceSettings/InvoiceSettings.js'),
Tax: require('./doctype/Tax/Tax.js'),
TaxDetail: require('./doctype/TaxDetail/TaxDetail.js'),
TaxSummary: require('./doctype/TaxSummary/TaxSummary.js')
models: {
Account: require('./doctype/Account/Account.js'),
AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'),
Party: require('./doctype/Party/Party.js'),
Item: require('./doctype/Item/Item.js'),
Invoice: require('./doctype/Invoice/Invoice.js'),
InvoiceItem: require('./doctype/InvoiceItem/InvoiceItem.js'),
InvoiceSettings: require('./doctype/InvoiceSettings/InvoiceSettings.js'),
Tax: require('./doctype/Tax/Tax.js'),
TaxDetail: require('./doctype/TaxDetail/TaxDetail.js'),
TaxSummary: require('./doctype/TaxSummary/TaxSummary.js')
}
}

View File

@ -1,7 +1,7 @@
{
"name": "frappe-accounting",
"version": "1.0.0",
"main": "index.js",
"main": "server.js",
"license": "MIT",
"scripts": {
"test": "mocha tests",

View File

@ -7,5 +7,9 @@ server.start({
static: './',
models: require('./models')
}).then(() => {
// set server-side modules
frappe.models.Invoice.documentClass = require('./models/doctype/Invoice/InvoiceServer.js');
frappe.metaCache = {};
frappe.syncDoc(require('./fixtures/invoicePrint'));
});

View File

@ -7,12 +7,13 @@ client.start({
}).then(() => {
// require modules
frappe.registerModels(require('../models'));
frappe.registerModels(require('../models'), 'client');
frappe.registerView('List', 'ToDo', require('frappejs/models/doctype/ToDo/ToDoList.js'));
frappe.registerView('List', 'Account', require('../models/doctype/Account/AccountList.js'));
frappe.registerView('Form', 'Account', require('../models/doctype/Account/AccountForm.js'));
frappe.registerView('List', 'Invoice', require('../models/doctype/Invoice/InvoiceList.js'));
frappe.registerView('List', 'Customer', require('../models/doctype/Party/CustomerList.js'));
frappe.desk.menu.addItem('ToDo', '#list/ToDo');
frappe.desk.menu.addItem('Accounts', '#list/Account');

View File

@ -4,8 +4,8 @@ const helpers = require('frappejs/tests/helpers');
const models = require('../models');
async function makeFixtures() {
if (!(await frappe.db.exists('Customer', 'Test Customer'))) {
await frappe.insert({doctype:'Customer', name:'Test Customer'})
if (!(await frappe.db.exists('Party', '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 2', description:'Test Item Description 2', unit:'No', rate: 200})
await frappe.insert({doctype:'Account', name:'GST', parent_account: 'Liabilities'});