[ADD] examples of journal entries
* interaction is terrible, select is ugly but radio buttons take way too much vertical space, no matter whether they're on the left or the right * may be possible to unify operations lists between chart of accounts and entries? Not currently selected set, just the list of possible ops
This commit is contained in:
parent
1570f58835
commit
e79e32721c
@ -27,7 +27,7 @@
|
|||||||
{
|
{
|
||||||
key: toKey(label),
|
key: toKey(label),
|
||||||
style: {display: 'block'},
|
style: {display: 'block'},
|
||||||
className: (operations === state.get('active') && 'highlight-op')
|
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||||
},
|
},
|
||||||
React.DOM.input({
|
React.DOM.input({
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
|
172
_static/entries.js
Normal file
172
_static/entries.js
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var data = createAtom();
|
||||||
|
data.addWatch('chart', function (k, m, prev, next) {
|
||||||
|
React.render(
|
||||||
|
React.createElement(Controls, {entry: next}),
|
||||||
|
document.getElementById('entries-control'));
|
||||||
|
React.render(
|
||||||
|
React.createElement(FormatEntry, {entry: next}),
|
||||||
|
document.querySelector('.journal-entries'));
|
||||||
|
});
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var entries_node = document.getElementById('journal-entries'),
|
||||||
|
controls = document.createElement('div');
|
||||||
|
controls.setAttribute('id', 'entries-control');
|
||||||
|
entries_node.insertBefore(controls, entries_node.lastElementChild);
|
||||||
|
|
||||||
|
data.reset(entries.first());
|
||||||
|
});
|
||||||
|
|
||||||
|
var Controls = React.createClass({
|
||||||
|
render: function () {
|
||||||
|
var _this = this;
|
||||||
|
return React.DOM.div(
|
||||||
|
null,
|
||||||
|
"Example journal entries: ",
|
||||||
|
React.DOM.select(
|
||||||
|
{
|
||||||
|
value: entries.indexOf(this.props.entry),
|
||||||
|
onChange: function (e) {
|
||||||
|
data.reset(entries.get(e.target.value));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entries.map(function (entry, index) {
|
||||||
|
return React.DOM.option(
|
||||||
|
{key: index, value: index},
|
||||||
|
entry.get('title')
|
||||||
|
);
|
||||||
|
}).toArray()),
|
||||||
|
this.props.entry && React.DOM.p(null, this.props.entry.get('help'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var FormatEntry = React.createClass({
|
||||||
|
render: function () {
|
||||||
|
return React.DOM.table(
|
||||||
|
{className: 'table'},
|
||||||
|
React.DOM.thead(
|
||||||
|
null,
|
||||||
|
React.DOM.tr(
|
||||||
|
null,
|
||||||
|
React.DOM.th(),
|
||||||
|
React.DOM.th({width: '25%', className: 'text-right'}, "Debit"),
|
||||||
|
React.DOM.th({width: '25%', className: 'text-right'}, "Credit")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
React.DOM.tbody(
|
||||||
|
null,
|
||||||
|
this.render_rows()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
render_rows: function () {
|
||||||
|
if (!this.props.entry) { return; }
|
||||||
|
return this.props.entry.get('operations').map(this.render_row).toArray();
|
||||||
|
},
|
||||||
|
render_row: function (entry, index) {
|
||||||
|
if (!entry) {
|
||||||
|
return React.DOM.tr(
|
||||||
|
{key: 'spacer-' + index},
|
||||||
|
React.DOM.td({colSpan: 3}, "\u00A0")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return React.DOM.tr(
|
||||||
|
{key: index},
|
||||||
|
React.DOM.td(null, entry.get('account')),
|
||||||
|
React.DOM.td({className: 'text-right'}, entry.get('debit')),
|
||||||
|
React.DOM.td({className: 'text-right'}, entry.get('credit'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: help explaining what the operation is about?
|
||||||
|
// TODO: link to relevant Odoo operation?
|
||||||
|
var entries = Immutable.fromJS([
|
||||||
|
{
|
||||||
|
title: "Company Founding",
|
||||||
|
operations: [
|
||||||
|
{account: 'Cash', debit: 10000},
|
||||||
|
{account: 'Common Stock', credit: 10000}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Buy work tooling",
|
||||||
|
operations: [
|
||||||
|
{account: 'Tooling', debit: 3000},
|
||||||
|
{account: 'Cash', credit: 3000}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Buy work tooling (invoiced)",
|
||||||
|
operations: [
|
||||||
|
{account: 'Tooling', debit: 3000},
|
||||||
|
{account: 'Accounts Payable', credit: 3000}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Pay supplier invoice",
|
||||||
|
operations: [
|
||||||
|
{account: 'Accounts Payable', debit: 3000},
|
||||||
|
{account: 'Cash', credit: 3000}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Sale paid immediately",
|
||||||
|
operations: [
|
||||||
|
{account: 'Cash', debit: 100},
|
||||||
|
{account: 'Sales', credit: 100}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Delayed payment (trade credit)",
|
||||||
|
operations: [
|
||||||
|
{account: 'Accounts Receivable', debit: 1000},
|
||||||
|
{account: 'Sales', credit: 1000}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Customer invoice paid",
|
||||||
|
operations: [
|
||||||
|
{account: 'Cash', debit: 1000},
|
||||||
|
{account: 'Accounts Receivable', credit: 1000}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Customer invoice, 10% early payment rebate",
|
||||||
|
operations: [
|
||||||
|
{account: 'Cash', debit: 900},
|
||||||
|
{account: 'Sales Discount', debit: 100},
|
||||||
|
{account: 'Accounts Receivable', credit: 1000}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Sale with tax",
|
||||||
|
operations: [
|
||||||
|
{account: 'Cash', debit: 109},
|
||||||
|
{account: 'Sales', credit: 100},
|
||||||
|
{account: 'Taxes Payable', credit: 9}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Fiscal year cloture — positive earnings and 50% dividends",
|
||||||
|
operations: [
|
||||||
|
{account: 'Revenue', debit: 5000},
|
||||||
|
{account: 'Income Summary', credit: 5000},
|
||||||
|
null,
|
||||||
|
{account: 'Income Summary', debit: 4000},
|
||||||
|
{account: 'Expenses', credit: 4000},
|
||||||
|
null,
|
||||||
|
{account: 'Income Summary', debit: 1000},
|
||||||
|
{account: 'Retained Earnings', credit: 1000},
|
||||||
|
null,
|
||||||
|
{account: 'Retained Earnings', debit: 500},
|
||||||
|
{account: 'Dividend Payable', credit: 500}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
title: "Fiscal year cloture — negative earnings and dividend irrelevant",
|
||||||
|
operations: [
|
||||||
|
{account: 'Revenue', debit: 5000},
|
||||||
|
{account: 'Income Summary', credit: 5000},
|
||||||
|
null,
|
||||||
|
{account: 'Income Summary', debit: 6000},
|
||||||
|
{account: 'Expenses', credit: 6000},
|
||||||
|
null,
|
||||||
|
{account: 'Retained Earnings', debit: 1000},
|
||||||
|
{account: 'Income Summary', credit: 1000}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}());
|
@ -1,247 +0,0 @@
|
|||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function item(it) {
|
|
||||||
if (it.credit && it.debit) {
|
|
||||||
throw new Error("A journal item can't have both credit and debit, got " + JSON.stringify(it));
|
|
||||||
}
|
|
||||||
return React.DOM.tr(
|
|
||||||
{key: it.label.toLowerCase().split(' ').concat(
|
|
||||||
['debit', it.debit, 'credit', it.credit]
|
|
||||||
).join('-')
|
|
||||||
},
|
|
||||||
React.DOM.td(null, (it.credit ? '\u2001' : '') + it.label),
|
|
||||||
React.DOM.td(null, it.debit),
|
|
||||||
React.DOM.td(null, it.credit)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function spacer(key) {
|
|
||||||
return React.DOM.tr({key: 'spacer-' + key}, React.DOM.td({colSpan: 3}, "\u00A0"));
|
|
||||||
}
|
|
||||||
|
|
||||||
var ClotureTable = React.createClass({
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
revenues: {
|
|
||||||
cash: 800,
|
|
||||||
receivable: 200,
|
|
||||||
},
|
|
||||||
expenses: {
|
|
||||||
cash: 100,
|
|
||||||
payable: 500,
|
|
||||||
},
|
|
||||||
// don't ignore/break invalid values
|
|
||||||
dividends: "0.5",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
income: function () {
|
|
||||||
return (
|
|
||||||
(this.state.revenues.cash + this.state.revenues.receivable)
|
|
||||||
-
|
|
||||||
(this.state.expenses.cash + this.state.expenses.payable)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function () {
|
|
||||||
return React.DOM.div(
|
|
||||||
null,
|
|
||||||
this.controls(),
|
|
||||||
React.DOM.table(
|
|
||||||
{className: 'table'},
|
|
||||||
this.makeHeader(),
|
|
||||||
React.DOM.tbody(
|
|
||||||
null,
|
|
||||||
this.revenues(this.state.revenues),
|
|
||||||
spacer('table-1'),
|
|
||||||
this.expenses(this.state.expenses),
|
|
||||||
spacer('table-2'),
|
|
||||||
this.closure({
|
|
||||||
dividends: this.state.dividends,
|
|
||||||
income: this.income(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
makeHeader: function () {
|
|
||||||
return React.DOM.thead(
|
|
||||||
null,
|
|
||||||
React.DOM.tr(
|
|
||||||
null,
|
|
||||||
React.DOM.th(),
|
|
||||||
React.DOM.th(null, "Debit"),
|
|
||||||
React.DOM.th(null, "Credit")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
controls: function () {
|
|
||||||
var _this = this;
|
|
||||||
return [
|
|
||||||
React.DOM.fieldset(
|
|
||||||
{key: 'income'},
|
|
||||||
React.DOM.legend(null, "Income"),
|
|
||||||
React.DOM.label(
|
|
||||||
null, "Cash ",
|
|
||||||
React.DOM.input({
|
|
||||||
type: 'number',
|
|
||||||
step: 1,
|
|
||||||
value: this.state.revenues.cash,
|
|
||||||
onChange: function (e) {
|
|
||||||
var val = e.target.value;
|
|
||||||
_this.setState({
|
|
||||||
revenues: {
|
|
||||||
cash: val ? parseInt(val, 10) : 0,
|
|
||||||
receivable: _this.state.revenues.receivable
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
),
|
|
||||||
' ',
|
|
||||||
React.DOM.label(
|
|
||||||
null, " Accounts Receivable ",
|
|
||||||
React.DOM.input({
|
|
||||||
type: 'number',
|
|
||||||
step: 1,
|
|
||||||
value: this.state.revenues.receivable,
|
|
||||||
onChange: function (e) {
|
|
||||||
var val = e.target.value;
|
|
||||||
_this.setState({
|
|
||||||
revenues: {
|
|
||||||
cash: _this.state.revenues.cash,
|
|
||||||
receivable: val ? parseInt(val, 10) : 0,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
React.DOM.fieldset(
|
|
||||||
{key: 'expenses'},
|
|
||||||
React.DOM.legend(null, "Expenses"),
|
|
||||||
React.DOM.label(
|
|
||||||
null, "Cash ",
|
|
||||||
React.DOM.input({
|
|
||||||
type: 'number',
|
|
||||||
step: 1,
|
|
||||||
value: this.state.expenses.cash,
|
|
||||||
onChange: function (e) {
|
|
||||||
var val = e.target.value;
|
|
||||||
_this.setState({
|
|
||||||
expenses: {
|
|
||||||
cash: val ? parseInt(val, 10): 0,
|
|
||||||
payable: _this.state.expenses.payable
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
),
|
|
||||||
' ',
|
|
||||||
React.DOM.label(
|
|
||||||
null, " Accounts Payable ",
|
|
||||||
React.DOM.input({
|
|
||||||
type: 'number',
|
|
||||||
step: 1,
|
|
||||||
value: this.state.expenses.payable,
|
|
||||||
onChange: function (e) {
|
|
||||||
var val = e.target.value;
|
|
||||||
_this.setState({
|
|
||||||
expenses: {
|
|
||||||
cash: _this.state.expenses.cash,
|
|
||||||
payable: val ? parseInt(val, 10) : 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
React.DOM.fieldset(
|
|
||||||
{key: 'dividends'},
|
|
||||||
React.DOM.legend(null, "Dividends"),
|
|
||||||
React.DOM.label(
|
|
||||||
null,
|
|
||||||
"Ratio (from retained earnings) ",
|
|
||||||
React.DOM.input({
|
|
||||||
type: 'range',
|
|
||||||
min: 0,
|
|
||||||
max: 1,
|
|
||||||
step: 0.01,
|
|
||||||
value: this.state.dividends,
|
|
||||||
style: { display: 'inline-block' },
|
|
||||||
onChange: function (e) {
|
|
||||||
_this.setState({dividends: e.target.value});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
];
|
|
||||||
},
|
|
||||||
// components must return a single root which isn't practical here
|
|
||||||
closure: function (props) {
|
|
||||||
var result, income = Math.abs(props.income), dividends = 0;
|
|
||||||
if (props.income > 0) {
|
|
||||||
// credit retained earnings from income, then credit dividends
|
|
||||||
// from retained
|
|
||||||
var dividends = parseInt(income * Math.max(0, Math.min(1, parseFloat(props.dividends))));
|
|
||||||
result = [
|
|
||||||
item({label: "Income Summary", debit: income}),
|
|
||||||
item({label: "Retained Earnings", credit: income}),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
// debit retained earnings, no dividends
|
|
||||||
result = [
|
|
||||||
item({label: "Retained Earnings", debit: income}),
|
|
||||||
item({label: "Income Summary", credit: income}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (dividends) {
|
|
||||||
result = result.concat([
|
|
||||||
spacer('closure'),
|
|
||||||
item({label: "Retained Earnings", debit: dividends}),
|
|
||||||
item({label: "Dividends Payable", credit: dividends})
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
revenues: function (props) {
|
|
||||||
var total = props.cash + props.receivable;
|
|
||||||
return [
|
|
||||||
item({label: "Cash", debit: props.cash}),
|
|
||||||
item({label: "Accounts Receivable", debit: props.receivable}),
|
|
||||||
item({label: "Revenue", credit: total}),
|
|
||||||
React.DOM.tr({key: 'revenue-notes'}, React.DOM.td(
|
|
||||||
{colSpan: 3},
|
|
||||||
"\u2001\u2001Consolidation of revenues")),
|
|
||||||
spacer('revenues'),
|
|
||||||
item({label: "Revenue", debit: total}),
|
|
||||||
item({label: "Income Summary", credit: total})
|
|
||||||
];
|
|
||||||
},
|
|
||||||
expenses: function (props) {
|
|
||||||
var total = props.cash + props.payable;
|
|
||||||
return [
|
|
||||||
item({label: "Expenses", debit: total}),
|
|
||||||
item({label: "Cash", credit: props.cash}),
|
|
||||||
item({label: "Accounts Payable", credit: props.payable}),
|
|
||||||
React.DOM.tr(
|
|
||||||
{key: 'expenses-note'}, React.DOM.td(
|
|
||||||
{colSpan: 3},
|
|
||||||
"\u2001\u2001Consolidation of expenses"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
spacer('expenses'),
|
|
||||||
item({label: "Income Summary", debit: total}),
|
|
||||||
item({label: "Expenses", credit: total})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
React.render(
|
|
||||||
React.createElement(ClotureTable),
|
|
||||||
document.querySelector('.fiscal-year-closing'));
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
2
conf.py
2
conf.py
@ -272,4 +272,4 @@ def setup(app):
|
|||||||
app.add_javascript('react.js')
|
app.add_javascript('react.js')
|
||||||
app.add_javascript('accounts.js')
|
app.add_javascript('accounts.js')
|
||||||
app.add_javascript('chart-of-accounts.js')
|
app.add_javascript('chart-of-accounts.js')
|
||||||
app.add_javascript('fiscalyear.js')
|
app.add_javascript('entries.js')
|
||||||
|
@ -123,7 +123,7 @@ context. Common journals are:
|
|||||||
|
|
||||||
.. h:div:: force-right journal-entries
|
.. h:div:: force-right journal-entries
|
||||||
|
|
||||||
.. todo:: insert examples of accounting entries for various transactions
|
examples of accounting entries for various transactions
|
||||||
|
|
||||||
Reconciliation
|
Reconciliation
|
||||||
==============
|
==============
|
||||||
|
Loading…
Reference in New Issue
Block a user