[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),
|
||||
style: {display: 'block'},
|
||||
className: (operations === state.get('active') && 'highlight-op')
|
||||
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||
},
|
||||
React.DOM.input({
|
||||
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('accounts.js')
|
||||
app.add_javascript('chart-of-accounts.js')
|
||||
app.add_javascript('fiscalyear.js')
|
||||
app.add_javascript('entries.js')
|
||||
|
Loading…
Reference in New Issue
Block a user