[REF][MOV] documentation apocalypse
Prior to this commit, the Odoo documentation was mainly split between
two repositories: odoo/odoo/doc and odoo/documentation-user. Some bits
of documentation were also hosted elsewhere (e.g., wiki, upgrade, ...).
This was causing several problems among which:
- The theme, config, Makefile, and similar technical resources had to
be duplicated. This resulted in inconsistent layout, features, and
build environments from one documentation to another.
- Some pages did not fit either documentation as they were relevant
for both users and developers. Some were relevant to neither of the
two (e.g., DB management).
- Cross-doc references had to be absolute links and they broke often.
- Merging large image files in the developer documentation would bloat
the odoo/odoo repository. Some contributions had to be lightened to
avoid merging too many images (e.g., Odoo development tutorials).
- Long-time contributors to the user documentation were chilly about
going through the merging process of the developer documentation
because of the runbot, mergebot, `odoo-dev` repository, etc.
- Some contributors would look for the developer documentation in the
`odoo/documentation-user` repository.
- Community issues about the user documentation were submitted on the
`odoo/odoo` repository and vice-versa.
Merging all documentations in one repository will allow us to have one
place, one theme, one work process, and one set of tools (build
environment, ...) for all of the Odoo docs.
As this is a good opportunity to revamp the layout of the documentation,
a brand new theme replaces the old one. It features a new way to
navigate the documentation, centered on the idea of always letting the
reader know what is the context (enclosing section, child pages, page
structure ...) of the page they are reading. The previous theme would
quickly confuse readers as they navigated the documentation and followed
cross-application links.
The chance is also taken to get rid of all the technical dangling parts,
performance issues, and left-overs. Except for some page-specific JS
scripts, the Odoo theme Sphinx extension is re-written from scratch
based on the latest Sphinx release to benefit from the improvements and
ease future contributions.
task-2351938
task-2352371
task-2205684
task-2352544
Closes #945
This commit is contained in:
committed by
Antoine Vandevenne (anv)
parent
eac5e9f865
commit
e3fee2cf46
@@ -0,0 +1,175 @@
|
||||
/* global Immutable, React */
|
||||
(function () {
|
||||
// NOTE: used by memento.rst
|
||||
'use strict';
|
||||
|
||||
function highlight(primary, secondary) {
|
||||
return {
|
||||
className: React.addons.classSet({
|
||||
related: primary,
|
||||
secondary: secondary
|
||||
})
|
||||
};
|
||||
}
|
||||
var AccountsTable = React.createClass({
|
||||
render: function () {
|
||||
return React.DOM.div(
|
||||
{ style: { marginTop: '0' } },
|
||||
React.DOM.div(// P&L
|
||||
highlight(this.props.current === 'p-l'),
|
||||
React.DOM.h4(null, "Profit & Loss"),
|
||||
React.DOM.div(
|
||||
null,
|
||||
React.DOM.h5(
|
||||
highlight(null, this.props.current === 'retained'),
|
||||
"Net Profit"),
|
||||
React.DOM.div(
|
||||
highlight(null, this.props.current === 'gross-profit'),
|
||||
React.DOM.h5(
|
||||
highlight(this.props.current === 'gross-profit'),
|
||||
"Gross Profit"),
|
||||
React.DOM.dl(
|
||||
null,
|
||||
React.DOM.dt(null, "Revenue"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Revenue"
|
||||
),
|
||||
React.DOM.dt(null, "Less ", "Costs of Revenue"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Cost of Goods Sold"
|
||||
)
|
||||
)
|
||||
),
|
||||
React.DOM.div(
|
||||
highlight(this.props.current === 'opex'),
|
||||
React.DOM.h5(null, "Operating Income or Loss"),
|
||||
React.DOM.dl(
|
||||
null,
|
||||
React.DOM.dt(
|
||||
null,
|
||||
"Less ", "Operating Expenses"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"R&D", React.DOM.br(),
|
||||
"Sales, General & Administrative"
|
||||
)
|
||||
)
|
||||
),
|
||||
React.DOM.dl(
|
||||
null,
|
||||
React.DOM.dt(null, "Plus ", "Other Income"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Foreign Exchange Gains", React.DOM.br(),
|
||||
"Asset write-downs"
|
||||
),
|
||||
React.DOM.dt(
|
||||
null,
|
||||
"Less ", "Other Expenses"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Interest on debt", React.DOM.br(),
|
||||
"Depreciation"
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
React.DOM.div(//Balance Sheet
|
||||
highlight(this.props.current === 'balance'),
|
||||
React.DOM.h4(null, "Balance Sheet"),
|
||||
React.DOM.div(
|
||||
null,
|
||||
React.DOM.h5(null, "Net Assets"),
|
||||
React.DOM.div(
|
||||
null,
|
||||
React.DOM.h5(highlight(this.props.current === 'assets'), "Total Assets"),
|
||||
React.DOM.dl(
|
||||
highlight(null, this.props.current === 'assets'),
|
||||
React.DOM.dt(null, "Current Assets"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Cash & Bank Accounts", React.DOM.br(),
|
||||
"Accounts Receivable", React.DOM.br(),
|
||||
"Deferred Tax Assets"
|
||||
),
|
||||
React.DOM.dt(null, "Plus ", "Non-current Assets"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Land & buildings", React.DOM.br(),
|
||||
"Intangible Assets"
|
||||
)
|
||||
)
|
||||
),
|
||||
React.DOM.dl(
|
||||
highlight(this.props.current === 'liabilities'),
|
||||
React.DOM.dt(null, "Less ", "Current Liabilities"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Accounts Payable", React.DOM.br(),
|
||||
"Deferred Revenue", React.DOM.br(),
|
||||
"Deferred Tax Liabilities"),
|
||||
React.DOM.dt(null, "Less ", "Non-current liabilities"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Long-term loans")
|
||||
)
|
||||
),
|
||||
React.DOM.div(
|
||||
highlight(null, this.props.current === 'equity'),
|
||||
React.DOM.h5(highlight(this.props.current === 'equity'), "Total Equity"),
|
||||
React.DOM.dl(
|
||||
null,
|
||||
React.DOM.dt(null, "Equity"),
|
||||
React.DOM.dd(
|
||||
null,
|
||||
"Common Stock"
|
||||
),
|
||||
React.DOM.dt(
|
||||
highlight(this.props.current === 'retained'),
|
||||
"Plus ", "Retained Earnings"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var target = document.querySelector('.accounts-table');
|
||||
if (!target) { return; }
|
||||
function render(current) {
|
||||
React.render(
|
||||
React.createElement(AccountsTable, { current: current }),
|
||||
target);
|
||||
}
|
||||
|
||||
var list = document.querySelectorAll('.intro-list p');
|
||||
Array.prototype.forEach.call(list, function (node) {
|
||||
node.addEventListener('mouseover', function (e) {
|
||||
if (!e.currentTarget.contains(e.target)) { return; }
|
||||
|
||||
e.currentTarget.classList.add('secondary');
|
||||
render(e.currentTarget.className.split(/\s+/).reduce(function (acc, cls) {
|
||||
if (acc) { return acc; }
|
||||
var m = /^intro-(.*)$/.exec(cls);
|
||||
return m && m[1];
|
||||
}, null));
|
||||
});
|
||||
node.addEventListener('mouseout', function (e) {
|
||||
// mouseout always precedes mousenter even when going into a
|
||||
// child element. Since re-render should be pretty fast (just
|
||||
// setting or unsetting a pair of classes) don't try to avoid
|
||||
// any thrashing, things should be fast enough either way. If
|
||||
// they're not, batch operations on requestAnimationFrame
|
||||
// instead.
|
||||
e.currentTarget.classList.remove('secondary');
|
||||
render(null);
|
||||
});
|
||||
});
|
||||
|
||||
render(null);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,56 @@
|
||||
function findAncestor(element, name) {
|
||||
name = name.toUpperCase();
|
||||
while(element && element.nodeName.toUpperCase() !== name) {
|
||||
element = element.parentElement;
|
||||
};
|
||||
return element;
|
||||
}
|
||||
|
||||
function createAtom(val, options) {
|
||||
var watchers = {};
|
||||
var validator = options && options.validator || function () { return true; };
|
||||
|
||||
function transition(next) {
|
||||
if (!validator(next)) {
|
||||
var err = new Error(next + " failed validation");
|
||||
err.name = "AssertionError";
|
||||
throw err;
|
||||
}
|
||||
|
||||
var prev = val;
|
||||
val = next;
|
||||
|
||||
Object.keys(watchers).forEach(function (k) {
|
||||
watchers[k](k, atom, prev, next);
|
||||
});
|
||||
}
|
||||
|
||||
var atom = {
|
||||
addWatch: function (key, fn) {
|
||||
watchers[key] = fn;
|
||||
},
|
||||
|
||||
removeWatch: function (key) {
|
||||
delete watchers[key];
|
||||
},
|
||||
|
||||
swap: function (fn) {
|
||||
var args = [val].concat([].slice.call(arguments, 1));
|
||||
transition(fn.apply(null, args));
|
||||
},
|
||||
|
||||
reset: function (v) {
|
||||
transition(v);
|
||||
},
|
||||
|
||||
deref: function () {
|
||||
return val;
|
||||
},
|
||||
|
||||
toString: function () {
|
||||
return "Atom(" + JSON.stringify(val) + ")";
|
||||
}
|
||||
};
|
||||
|
||||
return atom;
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom */
|
||||
(function () {
|
||||
// NOTE: used by memento.rst
|
||||
'use strict';
|
||||
|
||||
var data = createAtom();
|
||||
|
||||
function toKey(s, postfix) {
|
||||
if (postfix) {
|
||||
s += ' ' + postfix;
|
||||
}
|
||||
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
|
||||
|
||||
}
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var state = this.props.p;
|
||||
return React.DOM.div(null, operations.map(function (op) {
|
||||
var label = op.get('label'), operations = op.get('operations');
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: toKey(label),
|
||||
style: { display: 'block' },
|
||||
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'checkbox',
|
||||
checked: state.get('operations').contains(operations),
|
||||
onChange: function (e) {
|
||||
if (e.target.checked) {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', operations)
|
||||
.update('operations', function (ops) {
|
||||
return ops.add(operations)
|
||||
});
|
||||
});
|
||||
} else {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', null) // keep visible in state map
|
||||
.update('operations', function (ops) {
|
||||
return ops.remove(operations);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
" ",
|
||||
label
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
var Chart = React.createClass({
|
||||
render: function () {
|
||||
var lastop = Immutable.Map(
|
||||
(this.props.p.get('active') || Immutable.List()).map(function (op) {
|
||||
return [op.get('account'), op.has('credit') ? 'credit' : 'debit'];
|
||||
})
|
||||
);
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(),
|
||||
React.DOM.th({ className: 'text-right' }, "Debit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Credit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Balance"))
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.accounts().map(function (data) {
|
||||
var highlight = lastop.get(data.get('code'));
|
||||
return React.DOM.tr(
|
||||
{ key: data.get('code') },
|
||||
React.DOM.th(null,
|
||||
data.get('level') ? '\u2001 ' : '',
|
||||
data.get('code'), ' ', data.get('label')),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'debit'
|
||||
})
|
||||
}, format(data.get('debit'))),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'credit'
|
||||
})
|
||||
}, format(data.get('credit'))),
|
||||
React.DOM.td(
|
||||
{ className: 'text-right' },
|
||||
((data.get('debit') || data.get('credit'))
|
||||
? format(data.get('debit') - data.get('credit'), 0)
|
||||
: '')
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
accounts: function () {
|
||||
var data = this.props.p.get('operations');
|
||||
|
||||
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
|
||||
return acc
|
||||
.updateIn([op.get('account'), 'debit'], function (d) {
|
||||
return (d || 0) + op.get('debit', zero)(data);
|
||||
})
|
||||
.updateIn([op.get('account'), 'credit'], function (c) {
|
||||
return (c || 0) + op.get('credit', zero)(data);
|
||||
});
|
||||
}, Immutable.Map());
|
||||
|
||||
return accounts.map(function (account) {
|
||||
// for each account, add sum
|
||||
return account.merge(
|
||||
account.get('accounts').map(function (code) {
|
||||
return totals.get(code, NULL);
|
||||
}).reduce(function (acc, it) {
|
||||
return acc.mergeWith(function (a, b) { return a + b; }, it, NULL);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
data.addWatch('chart', function (k, m, prev, next) {
|
||||
React.render(
|
||||
React.createElement(Controls, { p: next }),
|
||||
document.getElementById('chart-controls'));
|
||||
React.render(
|
||||
React.createElement(Chart, { p: next }),
|
||||
document.querySelector('.chart-of-accounts'));
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var chart = findAncestor(document.querySelector('.chart-of-accounts'), 'section');
|
||||
if (!chart) { return; }
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'chart-controls');
|
||||
chart.insertBefore(controls, chart.lastElementChild);
|
||||
|
||||
data.reset(Immutable.Map({
|
||||
// last-selected operation
|
||||
active: null,
|
||||
// set of all currently enabled operations
|
||||
operations: Immutable.OrderedSet()
|
||||
}));
|
||||
});
|
||||
|
||||
var NULL = Immutable.Map({ debit: 0, credit: 0 });
|
||||
var ASSETS = {
|
||||
code: 1,
|
||||
label: "Assets",
|
||||
BANK: { code: 11000, label: "Cash" },
|
||||
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
|
||||
STOCK: { code: 14000, label: "Inventory" },
|
||||
STOCK_OUT: { code: 14600, label: "Goods Issued Not Invoiced" },
|
||||
BUILDINGS: { code: 17200, label: "Buildings" },
|
||||
DEPRECIATION: { code: 17800, label: "Accumulated Depreciation" },
|
||||
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
|
||||
};
|
||||
var LIABILITIES = {
|
||||
code: 2,
|
||||
label: "Liabilities",
|
||||
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
|
||||
DEFERRED_REVENUE: { code: 22300, label: "Deferred Revenue" },
|
||||
STOCK_IN: { code: 23000, label: "Goods Received Not Purchased" },
|
||||
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
|
||||
};
|
||||
var EQUITY = {
|
||||
code: 3,
|
||||
label: "Equity",
|
||||
CAPITAL: { code: 31000, label: "Common Stock" }
|
||||
};
|
||||
var REVENUE = {
|
||||
code: 4,
|
||||
label: "Revenue",
|
||||
SALES: { code: 41000, label: "Goods" },
|
||||
SALES_SERVICES: { code: 42000, label: "Services" }
|
||||
};
|
||||
var EXPENSES = {
|
||||
code: 5,
|
||||
label: "Expenses",
|
||||
GOODS_SOLD: { code: 51100, label: "Cost of Goods Sold" },
|
||||
DEPRECIATION: { code: 52500, label: "Other Operating Expenses" },
|
||||
PRICE_DIFFERENCE: { code: 53000, label: "Price Difference" }
|
||||
};
|
||||
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
|
||||
return Immutable.Iterable.isIndexed(v)
|
||||
? v.toList()
|
||||
: v.toOrderedMap();
|
||||
});
|
||||
var accounts = categories.toSeq().flatMap(function (cat) {
|
||||
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
|
||||
return k.toUpperCase() === k;
|
||||
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
|
||||
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
|
||||
return account.set(
|
||||
'accounts',
|
||||
Immutable.Seq.of(account.get('code')).concat(
|
||||
account.toIndexedSeq().map(function (val) {
|
||||
return Immutable.Map.isMap(val) && val.get('code');
|
||||
}).filter(function (val) { return !!val; })
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
var sale = 100,
|
||||
cor = 50,
|
||||
cor_tax = cor * 0.09,
|
||||
tax = sale * 0.09,
|
||||
total = sale + tax,
|
||||
refund = sale,
|
||||
refund_tax = refund * 0.09,
|
||||
purchase = 52,
|
||||
purchase_tax = 52 * 0.09;
|
||||
var operations = Immutable.fromJS([{
|
||||
label: "Company Incorporation (Initial Capital $1,000)",
|
||||
operations: [
|
||||
{ account: ASSETS.BANK.code, debit: constant(1000) },
|
||||
{ account: EQUITY.CAPITAL.code, credit: constant(1000) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Invoice ($100 + 9% tax) & Shipping of the Goods",
|
||||
operations: [
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) },
|
||||
{ account: EXPENSES.GOODS_SOLD.code, debit: constant(cor) },
|
||||
{ account: REVENUE.SALES.code, credit: constant(sale) },
|
||||
{ account: ASSETS.STOCK_OUT.code, credit: constant(cor) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Goods Shipment to Customer",
|
||||
operations: [
|
||||
{ account: ASSETS.STOCK_OUT.code, debit: constant(cor) },
|
||||
{ account: ASSETS.STOCK.code, credit: constant(cor) }
|
||||
]
|
||||
}, {
|
||||
id: 'refund',
|
||||
label: "Customer Refund",
|
||||
operations: [
|
||||
{ account: REVENUE.SALES.code, debit: constant(refund) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, debit: constant(refund_tax) },
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, credit: constant(refund + refund_tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Payment",
|
||||
operations: [
|
||||
{
|
||||
account: ASSETS.BANK.code, debit: function (ops) {
|
||||
var refund_op = operations.find(function (op) {
|
||||
return op.get('id') === 'refund';
|
||||
});
|
||||
return ops.contains(refund_op.get('operations'))
|
||||
? total - (refund + refund_tax)
|
||||
: total;
|
||||
}
|
||||
},
|
||||
{
|
||||
account: ASSETS.ACCOUNTS_RECEIVABLE.code, credit: function (ops) {
|
||||
var refund_op = operations.find(function (op) {
|
||||
return op.get('id') === 'refund';
|
||||
});
|
||||
return ops.contains(refund_op.get('operations'))
|
||||
? total - (refund + refund_tax)
|
||||
: total;
|
||||
}
|
||||
}
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Goods Received (Purchase Order: $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, credit: constant(cor) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(cor) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Bill (Invoice: $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, debit: constant(cor) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(cor_tax) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(cor + cor_tax) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Bill (Invoice: $52 but PO $50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.PRICE_DIFFERENCE.code, debit: constant(purchase - cor) },
|
||||
{ account: LIABILITIES.STOCK_IN.code, debit: constant(cor) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(purchase_tax) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(purchase + purchase_tax) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Bill Paid ($52 + 9% tax)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, debit: constant(purchase + purchase_tax) },
|
||||
{ account: ASSETS.BANK.code, credit: constant(purchase + purchase_tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Acquire a building (purchase contract)",
|
||||
operations: [
|
||||
{ account: ASSETS.BUILDINGS.code, debit: constant(3000) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(300) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(3300) }
|
||||
]
|
||||
}, {
|
||||
label: "Pay for building",
|
||||
operations: [
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, debit: constant(3300) },
|
||||
{ account: ASSETS.BANK.code, credit: constant(3300) }
|
||||
]
|
||||
}, {
|
||||
label: "Yearly Asset Depreciation (10% per year)",
|
||||
operations: [
|
||||
{ account: EXPENSES.DEPRECIATION.code, debit: constant(300) },
|
||||
{ account: ASSETS.DEPRECIATION.code, credit: constant(300) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Invoice (3 years service contract, $300)",
|
||||
operations: [
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total * 3) },
|
||||
{ account: LIABILITIES.DEFERRED_REVENUE.code, credit: constant(sale * 3) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax * 3) }
|
||||
]
|
||||
}, {
|
||||
label: "Revenue Recognition (each year, including first)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.DEFERRED_REVENUE.code, debit: constant(sale) },
|
||||
{ account: REVENUE.SALES_SERVICES.code, credit: constant(sale) },
|
||||
]
|
||||
}, {
|
||||
id: 'pay_taxes',
|
||||
label: "Pay Taxes Due",
|
||||
operations: [
|
||||
{
|
||||
account: LIABILITIES.TAXES_PAYABLE.code, debit: function (ops) {
|
||||
var this_ops = operations.find(function (op) {
|
||||
return op.get('id') === 'pay_taxes';
|
||||
}).get('operations');
|
||||
return ops.filter(function (_ops) {
|
||||
return _ops !== this_ops;
|
||||
}).flatten(true).filter(function (op) {
|
||||
return op.get('account') === LIABILITIES.TAXES_PAYABLE.code
|
||||
}).reduce(function (acc, op) {
|
||||
return acc + op.get('credit', zero)(ops) - op.get('debit', zero)(ops);
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
account: ASSETS.BANK.code, credit: function (ops) {
|
||||
return operations.find(function (op) {
|
||||
return op.get('id') === 'pay_taxes';
|
||||
}).getIn(['operations', 0, 'debit'])(ops);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
function constant(val) { return function () { return val; }; }
|
||||
var zero = constant(0);
|
||||
function format(val, def) {
|
||||
if (!val) { return def === undefined ? '' : def; }
|
||||
if (val % 1 === 0) { return val; }
|
||||
return val.toFixed(2);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,276 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var data = createAtom();
|
||||
|
||||
function toKey(s, postfix) {
|
||||
if (postfix) {
|
||||
s += ' ' + postfix;
|
||||
}
|
||||
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
|
||||
|
||||
}
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var state = this.props.p;
|
||||
return React.DOM.div(null, operations.map(function (op) {
|
||||
var label = op.get('label'), operations = op.get('operations');
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: toKey(label),
|
||||
style: { display: 'block' },
|
||||
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'checkbox',
|
||||
checked: state.get('operations').contains(operations),
|
||||
onChange: function (e) {
|
||||
if (e.target.checked) {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', operations)
|
||||
.update('operations', function (ops) {
|
||||
return ops.add(operations);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', null) // keep visible in state map
|
||||
.update('operations', function (ops) {
|
||||
return ops.remove(operations);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
" ",
|
||||
label
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
var Chart = React.createClass({
|
||||
render: function () {
|
||||
var lastop = Immutable.Map(
|
||||
(this.props.p.get('active') || Immutable.List()).map(function (op) {
|
||||
return [op.get('account'), op.has('credit') ? 'credit' : 'debit'];
|
||||
})
|
||||
);
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(),
|
||||
React.DOM.th({ className: 'text-right' }, "Debit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Credit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Balance"))
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.accounts().map(function (data) {
|
||||
var highlight = lastop.get(data.get('code'));
|
||||
return React.DOM.tr(
|
||||
{ key: data.get('code') },
|
||||
React.DOM.th(null,
|
||||
data.get('level') ? '\u2001 ' : '',
|
||||
data.get('code'), ' ', data.get('label')),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'debit'
|
||||
})
|
||||
}, format(data.get('debit'))),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'credit'
|
||||
})
|
||||
}, format(data.get('credit'))),
|
||||
React.DOM.td(
|
||||
{ className: 'text-right' },
|
||||
((data.get('debit') || data.get('credit'))
|
||||
? format(data.get('debit') - data.get('credit'), 0)
|
||||
: '')
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
accounts: function () {
|
||||
var data = this.props.p.get('operations');
|
||||
|
||||
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
|
||||
return acc
|
||||
.updateIn([op.get('account'), 'debit'], function (d) {
|
||||
return (d || 0) + op.get('debit', zero)(data);
|
||||
})
|
||||
.updateIn([op.get('account'), 'credit'], function (c) {
|
||||
return (c || 0) + op.get('credit', zero)(data);
|
||||
});
|
||||
}, Immutable.Map());
|
||||
|
||||
return accounts.map(function (account) {
|
||||
// for each account, add sum
|
||||
return account.merge(
|
||||
account.get('accounts').map(function (code) {
|
||||
return totals.get(code, NULL);
|
||||
}).reduce(function (acc, it) {
|
||||
return acc.mergeWith(function (a, b) { return a + b; }, it, NULL);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
data.addWatch('chart', function (k, m, prev, next) {
|
||||
React.render(
|
||||
React.createElement(Controls, { p: next }),
|
||||
document.getElementById('chart-controls'));
|
||||
React.render(
|
||||
React.createElement(Chart, { p: next }),
|
||||
document.querySelector('.valuation-chart-anglo-saxon'));
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var chart = document.querySelector('.valuation-chart-anglo-saxon');
|
||||
if (!chart) { return; }
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'chart-controls');
|
||||
chart.parentNode.insertBefore(controls, chart);
|
||||
|
||||
data.reset(Immutable.Map({
|
||||
// last-selected operation
|
||||
active: null,
|
||||
// set of all currently enabled operations
|
||||
operations: Immutable.OrderedSet()
|
||||
}));
|
||||
});
|
||||
|
||||
var NULL = Immutable.Map({ debit: 0, credit: 0 });
|
||||
var ASSETS = {
|
||||
code: 1,
|
||||
label: "Assets",
|
||||
BANK: { code: 11000, label: "Cash" },
|
||||
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
|
||||
STOCK: { code: 14000, label: "Inventory" },
|
||||
RAW_MATERIALS: { code: 14100, label: "Raw Materials Inventory" },
|
||||
STOCK_OUT: { code: 14600, label: "Goods Issued Not Invoiced" },
|
||||
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
|
||||
};
|
||||
var LIABILITIES = {
|
||||
code: 2,
|
||||
label: "Liabilities",
|
||||
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
|
||||
STOCK_IN: { code: 23000, label: "Goods Received Not Purchased" },
|
||||
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
|
||||
};
|
||||
var EQUITY = {
|
||||
code: 3,
|
||||
label: "Equity",
|
||||
CAPITAL: { code: 31000, label: "Common Stock" }
|
||||
};
|
||||
var REVENUE = {
|
||||
code: 4,
|
||||
label: "Revenue",
|
||||
SALES: { code: 41000, label: "Goods" },
|
||||
};
|
||||
var EXPENSES = {
|
||||
code: 5,
|
||||
label: "Expenses",
|
||||
GOODS_SOLD: { code: 51100, label: "Cost of Goods Sold" },
|
||||
MANUFACTURING_OVERHEAD: { code: 52000, label: "Manufacturing Overhead" },
|
||||
PRICE_DIFFERENCE: { code: 53000, label: "Price Difference" }
|
||||
};
|
||||
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
|
||||
return Immutable.Iterable.isIndexed(v)
|
||||
? v.toList()
|
||||
: v.toOrderedMap();
|
||||
});
|
||||
var accounts = categories.toSeq().flatMap(function (cat) {
|
||||
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
|
||||
return k.toUpperCase() === k;
|
||||
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
|
||||
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
|
||||
return account.set(
|
||||
'accounts',
|
||||
Immutable.Seq.of(account.get('code')).concat(
|
||||
account.toIndexedSeq().map(function (val) {
|
||||
return Immutable.Map.isMap(val) && val.get('code');
|
||||
}).filter(function (val) { return !!val; })
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
var sale = 100,
|
||||
cor = 50,
|
||||
cor_tax = cor * 0.09,
|
||||
tax = sale * 0.09,
|
||||
total = sale + tax,
|
||||
purchase = 52,
|
||||
purchase_tax = 52 * 0.09;
|
||||
var operations = Immutable.fromJS([{
|
||||
label: "Vendor Bill (PO $50, Invoice $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, debit: constant(50) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
|
||||
]
|
||||
}, {
|
||||
label: "Supplier Goods Reception (PO $50, Invoice $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, credit: constant(50) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(50) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Bill (PO $48, Invoice $50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.PRICE_DIFFERENCE.code, debit: constant(2) },
|
||||
{ account: LIABILITIES.STOCK_IN.code, debit: constant(48) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
|
||||
]
|
||||
}, {
|
||||
label: "Supplier Goods Reception (PO $48, Invoice $50)",
|
||||
operations: [
|
||||
{ account: LIABILITIES.STOCK_IN.code, credit: constant(48) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(48) },
|
||||
]
|
||||
}, {
|
||||
label: "Customer Invoice",
|
||||
operations: [
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) },
|
||||
{ account: EXPENSES.GOODS_SOLD.code, debit: constant(cor) },
|
||||
{ account: REVENUE.SALES.code, credit: constant(sale) },
|
||||
{ account: ASSETS.STOCK_OUT.code, credit: constant(cor) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Shipping",
|
||||
operations: [
|
||||
{ account: ASSETS.STOCK_OUT.code, debit: constant(cor) },
|
||||
{ account: ASSETS.STOCK.code, credit: constant(cor) }
|
||||
]
|
||||
}, {
|
||||
label: "Production Order",
|
||||
operations: [
|
||||
{ account: ASSETS.STOCK.code, debit: constant(50) },
|
||||
{ account: EXPENSES.MANUFACTURING_OVERHEAD.code, debit: constant(2) },
|
||||
{ account: ASSETS.RAW_MATERIALS.code, credit: constant(52) }
|
||||
]
|
||||
}]);
|
||||
function constant(val) { return function () { return val; }; }
|
||||
var zero = constant(0);
|
||||
function format(val, def) {
|
||||
if (!val) { return def === undefined ? '' : def; }
|
||||
if (val % 1 === 0) { return val; }
|
||||
return val.toFixed(2);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,265 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var data = createAtom();
|
||||
|
||||
function toKey(s, postfix) {
|
||||
if (postfix) {
|
||||
s += ' ' + postfix;
|
||||
}
|
||||
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
|
||||
|
||||
}
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var state = this.props.p;
|
||||
return React.DOM.div(null, operations.map(function (op) {
|
||||
var label = op.get('label'), operations = op.get('operations');
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: toKey(label),
|
||||
style: { display: 'block' },
|
||||
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'checkbox',
|
||||
checked: state.get('operations').contains(operations),
|
||||
onChange: function (e) {
|
||||
if (e.target.checked) {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', operations)
|
||||
.update('operations', function (ops) {
|
||||
return ops.add(operations);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', null) // keep visible in state map
|
||||
.update('operations', function (ops) {
|
||||
return ops.remove(operations);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
" ",
|
||||
label
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
var Chart = React.createClass({
|
||||
render: function () {
|
||||
var lastop = Immutable.Map(
|
||||
(this.props.p.get('active') || Immutable.List()).map(function (op) {
|
||||
return [op.get('account'), op.has('credit') ? 'credit' : 'debit'];
|
||||
})
|
||||
);
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(),
|
||||
React.DOM.th({ className: 'text-right' }, "Debit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Credit"),
|
||||
React.DOM.th({ className: 'text-right' }, "Balance"))
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.accounts().map(function (data) {
|
||||
var highlight = lastop.get(data.get('code'));
|
||||
return React.DOM.tr(
|
||||
{ key: data.get('code') },
|
||||
React.DOM.th(null,
|
||||
data.get('level') ? '\u2001 ' : '',
|
||||
data.get('code'), ' ', data.get('label')),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'debit'
|
||||
})
|
||||
}, format(data.get('debit'))),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight === 'credit'
|
||||
})
|
||||
}, format(data.get('credit'))),
|
||||
React.DOM.td(
|
||||
{ className: 'text-right' },
|
||||
((data.get('debit') || data.get('credit'))
|
||||
? format(data.get('debit') - data.get('credit'), 0)
|
||||
: '')
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
accounts: function () {
|
||||
var data = this.props.p.get('operations');
|
||||
|
||||
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
|
||||
return acc
|
||||
.updateIn([op.get('account'), 'debit'], function (d) {
|
||||
return (d || 0) + op.get('debit', zero)(data);
|
||||
})
|
||||
.updateIn([op.get('account'), 'credit'], function (c) {
|
||||
return (c || 0) + op.get('credit', zero)(data);
|
||||
});
|
||||
}, Immutable.Map());
|
||||
|
||||
return accounts.map(function (account) {
|
||||
// for each account, add sum
|
||||
return account.merge(
|
||||
account.get('accounts').map(function (code) {
|
||||
return totals.get(code, NULL);
|
||||
}).reduce(function (acc, it) {
|
||||
return acc.mergeWith(function (a, b) { return a + b; }, it, NULL);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
data.addWatch('chart', function (k, m, prev, next) {
|
||||
React.render(
|
||||
React.createElement(Controls, { p: next }),
|
||||
document.getElementById('chart-controls'));
|
||||
React.render(
|
||||
React.createElement(Chart, { p: next }),
|
||||
document.querySelector('.valuation-chart-continental'));
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var chart = document.querySelector('.valuation-chart-continental');
|
||||
if (!chart) { return; }
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'chart-controls');
|
||||
chart.parentNode.insertBefore(controls, chart);
|
||||
|
||||
data.reset(Immutable.Map({
|
||||
// last-selected operation
|
||||
active: null,
|
||||
// set of all currently enabled operations
|
||||
operations: Immutable.OrderedSet()
|
||||
}));
|
||||
});
|
||||
|
||||
var NULL = Immutable.Map({ debit: 0, credit: 0 });
|
||||
var ASSETS = {
|
||||
code: 1,
|
||||
label: "Assets",
|
||||
BANK: { code: 11000, label: "Cash" },
|
||||
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
|
||||
STOCK: { code: 14000, label: "Inventory" },
|
||||
RAW_MATERIALS: { code: 14100, label: "Raw Materials Inventory" },
|
||||
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
|
||||
};
|
||||
var LIABILITIES = {
|
||||
code: 2,
|
||||
label: "Liabilities",
|
||||
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
|
||||
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
|
||||
};
|
||||
var EQUITY = {
|
||||
code: 3,
|
||||
label: "Equity",
|
||||
CAPITAL: { code: 31000, label: "Common Stock" }
|
||||
};
|
||||
var REVENUE = {
|
||||
code: 4,
|
||||
label: "Revenue",
|
||||
SALES: { code: 41000, label: "Goods" },
|
||||
};
|
||||
var EXPENSES = {
|
||||
code: 5,
|
||||
label: "Expenses",
|
||||
PURCHASED_GOODS: { code: 51000, label: "Purchased Goods" },
|
||||
PURCHASED_SERVICES: { code: 52000, label: "Purchased Services" },
|
||||
INVENTORY_VARIATIONS: { code: 58000, label: "Inventory Variations" },
|
||||
OTHER_OPERATING_EXPENSES: { code: 59000, label: "Other Operating Expenses" },
|
||||
};
|
||||
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
|
||||
return Immutable.Iterable.isIndexed(v)
|
||||
? v.toList()
|
||||
: v.toOrderedMap();
|
||||
});
|
||||
var accounts = categories.toSeq().flatMap(function (cat) {
|
||||
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
|
||||
return k.toUpperCase() === k;
|
||||
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
|
||||
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
|
||||
return account.set(
|
||||
'accounts',
|
||||
Immutable.Seq.of(account.get('code')).concat(
|
||||
account.toIndexedSeq().map(function (val) {
|
||||
return Immutable.Map.isMap(val) && val.get('code');
|
||||
}).filter(function (val) { return !!val; })
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
var sale = 100,
|
||||
cor = 50,
|
||||
cor_tax = cor * 0.09,
|
||||
tax = sale * 0.09,
|
||||
total = sale + tax,
|
||||
purchase = 52,
|
||||
purchase_tax = 52 * 0.09;
|
||||
var operations = Immutable.fromJS([{
|
||||
label: "Vendor Invoice (PO €50, Invoice €50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.PURCHASED_GOODS.code, debit: constant(50) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Goods Reception (PO €50, Invoice €50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.INVENTORY_VARIATIONS.code, credit: constant(50) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(50) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Invoice (PO €48, Invoice €50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.PURCHASED_GOODS.code, debit: constant(50) },
|
||||
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
|
||||
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
|
||||
]
|
||||
}, {
|
||||
label: "Vendor Goods Reception (PO €48, Invoice €50)",
|
||||
operations: [
|
||||
{ account: EXPENSES.INVENTORY_VARIATIONS.code, credit: constant(48) },
|
||||
{ account: ASSETS.STOCK.code, debit: constant(48) },
|
||||
]
|
||||
}, {
|
||||
label: "Customer Invoice (€100 + 9% tax)",
|
||||
operations: [
|
||||
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) },
|
||||
{ account: REVENUE.SALES.code, credit: constant(sale) },
|
||||
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) }
|
||||
]
|
||||
}, {
|
||||
label: "Customer Shipping",
|
||||
operations: [
|
||||
{ account: EXPENSES.INVENTORY_VARIATIONS.code, debit: constant(cor) },
|
||||
{ account: ASSETS.STOCK.code, credit: constant(cor) }
|
||||
]
|
||||
}]);
|
||||
function constant(val) { return function () { return val; }; }
|
||||
var zero = constant(0);
|
||||
function format(val, def) {
|
||||
if (!val) { return def === undefined ? '' : def; }
|
||||
if (val % 1 === 0) { return val; }
|
||||
return val.toFixed(2);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,297 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom, findAncestor */
|
||||
(function () {
|
||||
'use strict';
|
||||
// NOTE: memento.rst
|
||||
|
||||
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_section = findAncestor(document.querySelector('.journal-entries'), 'section');
|
||||
if (!entries_section) { return; }
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'entries-control');
|
||||
entries_section.insertBefore(controls, entries_section.lastElementChild);
|
||||
|
||||
data.reset(entries.first());
|
||||
});
|
||||
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var _this = this;
|
||||
return React.DOM.div(
|
||||
null,
|
||||
entries.map(function (entry, index) {
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: index,
|
||||
style: { display: 'block' },
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'radio',
|
||||
checked: Immutable.is(entry, this.props.entry),
|
||||
onChange: function (e) {
|
||||
data.reset(entry);
|
||||
}
|
||||
}),
|
||||
' ',
|
||||
entry.get('title')
|
||||
);
|
||||
}, this),
|
||||
this.props.entry && React.DOM.p(null, this.props.entry.get('help'))
|
||||
);
|
||||
}
|
||||
});
|
||||
var FormatEntry = React.createClass({
|
||||
render: function () {
|
||||
var entry = this.props.entry;
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm d-c-table' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(),
|
||||
React.DOM.th(null, "Debit"),
|
||||
React.DOM.th(null, "Credit")
|
||||
)
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.render_rows()
|
||||
)
|
||||
),
|
||||
React.createElement(Listing, {
|
||||
heading: "Explanation",
|
||||
items: entry && entry.get('explanation')
|
||||
}),
|
||||
React.createElement(Listing, {
|
||||
heading: "Configuration",
|
||||
items: entry && entry.get('configuration')
|
||||
})
|
||||
);
|
||||
},
|
||||
render_rows: function () {
|
||||
if (!this.props.entry) { return; }
|
||||
return this.props.entry.get('operations').map(this.render_row);
|
||||
},
|
||||
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(null, entry.get('debit')),
|
||||
React.DOM.td(null, entry.get('credit'))
|
||||
);
|
||||
}
|
||||
});
|
||||
var Listing = React.createClass({
|
||||
render: function () {
|
||||
if (!this.props.items || this.props.items.isEmpty()) {
|
||||
return React.DOM.div();
|
||||
}
|
||||
var items = this.props.items, epilog = Immutable.List();
|
||||
var idx = items.indexOf(null);
|
||||
if (idx !== -1) {
|
||||
epilog = items.slice(idx + 1);
|
||||
items = items.take(idx);
|
||||
}
|
||||
return React.DOM.div(
|
||||
{ className: 'entries-listing' },
|
||||
React.DOM.h4(null, this.props.heading, ':'),
|
||||
React.DOM.ul(
|
||||
null,
|
||||
items.map(function (item, index) {
|
||||
return React.DOM.li({ key: index }, item);
|
||||
})
|
||||
),
|
||||
epilog.map(function (item, index) {
|
||||
return React.DOM.p({ key: index }, item);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var entries = Immutable.fromJS([
|
||||
{
|
||||
title: "Company Incorporation",
|
||||
operations: [
|
||||
{ account: 'Assets: Cash', debit: 1000 },
|
||||
{ account: 'Equity: Common Stock', credit: 1000 }
|
||||
],
|
||||
explanation: [
|
||||
"The company receives $1,000 in cash",
|
||||
"Shares worth of $1,000 belong to the founders",
|
||||
null,
|
||||
"The initial capital can be cash, but could also be intellectual property, goodwill from a previous company, licences, know how, etc…",
|
||||
"Sometimes, capital is not released immediately, accounts for \"capital to be released\" may be necessary."
|
||||
],
|
||||
configuration: []
|
||||
}, {
|
||||
title: "Customer Invoice ($100 + 9% tax)",
|
||||
operations: [
|
||||
{ account: 'Revenue: Goods', credit: 100 },
|
||||
{ account: 'Liabilities: Deferred Tax Liabilities', credit: 9 },
|
||||
{ account: 'Assets: Accounts Receivable', debit: 109 },
|
||||
{ account: 'Assets: Inventory', credit: 50 },
|
||||
{ account: 'Expenses: Cost of Goods Sold', debit: 50 }
|
||||
],
|
||||
explanation: [
|
||||
"Revenues increase by $100",
|
||||
"A tax to pay at the end of the month of $9",
|
||||
"The customer owes $109",
|
||||
"The inventory is decreased by $50 (shipping of the goods)",
|
||||
"The cost of goods sold decreases the gross profit by $50"
|
||||
],
|
||||
configuration: [
|
||||
"Revenue: defined on the product, or the product category if not on the product, field Income Account",
|
||||
"Defered Tax Liabilities: defined on the tax used on the invoice line",
|
||||
"Accounts Receivable: defined on the customer (property)",
|
||||
"Inventory: defined on the category of the related product (property)",
|
||||
"Expenses: defined on the product, or the category of product (property)",
|
||||
null,
|
||||
"The fiscal position used on the invoice may have a rule that replaces the Income Account or the tax defined on the product by another one."
|
||||
]
|
||||
}, {
|
||||
title: "Customer payment",
|
||||
operations: [
|
||||
{ account: 'Assets: Cash', debit: 109 },
|
||||
{ account: 'Assets: Accounts Receivable', credit: 109 }
|
||||
],
|
||||
explanation: [
|
||||
"The company receives $109 in cash",
|
||||
"The customer owes $109 less"
|
||||
],
|
||||
configuration: [
|
||||
"Cash: defined on the journal used when registering the payment, fields Default Credit Account and Default Debit Account",
|
||||
"Accounts Receivable: defined on the customer (property)"
|
||||
]
|
||||
}, {
|
||||
title: "Supplier Bill (Purchase Order $50 but Invoice $52)",
|
||||
operations: [
|
||||
{ account: 'Assets: Uninvoiced Inventory', debit: 50 },
|
||||
{ account: 'Assets: Deferred Tax Assets', debit: 4.68 },
|
||||
{ account: 'Expenses: Price Difference', debit: 2 },
|
||||
{ account: 'Liabilities: Accounts Payable', credit: 56.68 }
|
||||
],
|
||||
explanation: [
|
||||
"A temporary account is used to note goods to receive",
|
||||
"The purchase order provides prices of goods, the actual invoice may include extra costs such as shipping",
|
||||
"The company still needs to pay the vendor (traded an asset against a liability)"
|
||||
],
|
||||
configuration: [
|
||||
"Uninvoiced Inventory: defined on the product or the category of related product, field: Stock Input Account",
|
||||
"Deferred Tax Assets: defined on the tax used on the purchase order line",
|
||||
"Accounts Payable: defined on the supplier related to the bill",
|
||||
null,
|
||||
"In this scenario, the purchase order was $50 but the company received an invoice for $52 as there were extra shipping costs"
|
||||
]
|
||||
}, {
|
||||
title: "Supplier Goods Received (Purchase Order: $50)",
|
||||
operations: [
|
||||
{ account: 'Assets: Inventory', debit: 50 },
|
||||
{ account: 'Assets: Uninvoiced Inventory', credit: 50 },
|
||||
],
|
||||
explanation: [
|
||||
"Inventory is increased by $50, the expected amount coming from the purchase order",
|
||||
"A temporary account is used for the counterpart and will be cleared when receiving the invoice"
|
||||
],
|
||||
configuration: [
|
||||
"Uninvoiced Inventory: defined on the product or the category of related product, field: Stock Input Account",
|
||||
"Inventory: defined on the product category, field: Stock Valuation"
|
||||
]
|
||||
}, {
|
||||
title: "Buy an asset ($300,000 - no tax)",
|
||||
operations: [
|
||||
{ account: 'Assets: Buildings', debit: 300000 },
|
||||
{ account: 'Liabilities: Accounts Payable', credit: 300000 }
|
||||
],
|
||||
explanation: [
|
||||
"The company gets an asset worth of $300,000",
|
||||
"The company needs to pay $300,000 to the vendor (traded an asset against a liability)"
|
||||
],
|
||||
configuration: [
|
||||
"Buildings: Defined on the Asset category selected on the supplier bill line",
|
||||
"Accounts Payable: defined on the supplier related to the bill (property)"
|
||||
]
|
||||
}, {
|
||||
title: "Pay supplier invoice",
|
||||
operations: [
|
||||
{ account: 'Liabilities: Accounts Payable', debit: 300000 },
|
||||
{ account: 'Assets: Cash', credit: 300000 }
|
||||
],
|
||||
explanation: [
|
||||
"The company owns $300,000 less to the supplier (liabilities are settled)",
|
||||
"The company's cash is reduced by $300,000 (reduction of asset)"
|
||||
],
|
||||
configuration: [
|
||||
"Accounts Payable: defined on the supplier you pay (property)",
|
||||
"Cash: defined on the journal related to the payment method"
|
||||
]
|
||||
}, {
|
||||
title: "Cash sale (Sales Receipt)",
|
||||
operations: [
|
||||
{ account: 'Assets: Cash', debit: 109 },
|
||||
{ account: 'Revenue: Goods', credit: 100 },
|
||||
{ account: 'Liabilities: Deferred Tax Liabilities', credit: 9 }
|
||||
],
|
||||
explanation: [
|
||||
"Company's cash is increased by $109",
|
||||
"Revenues increase by $100",
|
||||
"A tax of $9 has to be paid"
|
||||
],
|
||||
configuration: [
|
||||
"Cash: Payment method defined on the Sales Receipt",
|
||||
"Sales: Defined on the product used in the sales receipt, or the category of product if empty",
|
||||
"Deferred Tax Liabilities: Defined on the tax used in the sales receipt (coming from the product)"
|
||||
]
|
||||
}, {
|
||||
title: "Customer pays invoice, 5% early payment rebate",
|
||||
operations: [
|
||||
{ account: 'Assets: Cash', debit: 950 },
|
||||
{ account: 'Revenue: Sales Discount', debit: 50 },
|
||||
{ account: 'Assets: Accounts Receivable', credit: 1000 }
|
||||
],
|
||||
explanation: [
|
||||
"Company's cash is increased by $950",
|
||||
"Sales discounts lowering effective revenues by $50",
|
||||
"The customer owns $1000 less to the company"
|
||||
],
|
||||
configuration: [
|
||||
"Cash: is defined on the journal related to the payment / bank statement",
|
||||
"Sales Discount: is selected during the payment matching process",
|
||||
"Accounts Receivable: is defined on the customer associated to the payment"
|
||||
]
|
||||
}, {
|
||||
title: "Fiscal year closing — positive earnings and 50% dividends",
|
||||
operations: [
|
||||
{ account: 'Net Profit', debit: 1000 },
|
||||
{ account: 'Equity: Retained Earnings', credit: 500 },
|
||||
{ account: 'Liabilities: Dividend Payable', credit: 500 }
|
||||
],
|
||||
explanation: [
|
||||
"The P&L is cleared (net profit)",
|
||||
"50% is transferred to retained earnings",
|
||||
"50% will be paid to shareholders as dividends"
|
||||
],
|
||||
configuration: [
|
||||
"This transaction is recorded by the advisor before closing the fiscal year, depending on how the company uses its net profit."
|
||||
]
|
||||
}
|
||||
]);
|
||||
}());
|
||||
@@ -0,0 +1,217 @@
|
||||
/* global Immutable, React */
|
||||
/* global createAtom */
|
||||
(function () {
|
||||
// NOTE: used for double_entry.rst file
|
||||
'use strict';
|
||||
|
||||
var data = createAtom();
|
||||
|
||||
function toKey(s, postfix) {
|
||||
if (postfix) {
|
||||
s += ' ' + postfix;
|
||||
}
|
||||
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
|
||||
}
|
||||
var Controls = React.createClass({
|
||||
render: function () {
|
||||
var state = this.props.p;
|
||||
return React.DOM.div(null, operations.map(function (op) {
|
||||
var label = op.get('label'), operations = op.get('operations');
|
||||
return React.DOM.label(
|
||||
{
|
||||
key: toKey(label),
|
||||
style: { display: 'block' },
|
||||
className: (operations === state.get('active') ? 'highlight-op' : void 0)
|
||||
},
|
||||
React.DOM.input({
|
||||
type: 'checkbox',
|
||||
checked: state.get('operations').contains(operations),
|
||||
onChange: function (e) {
|
||||
if (e.target.checked) {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', operations)
|
||||
.update('operations', function (ops) {
|
||||
return ops.add(operations);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
data.swap(function (d) {
|
||||
return d.set('active', null)
|
||||
.update('operations', function (ops) {
|
||||
return ops.remove(operations);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
' ',
|
||||
label
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
var UNIT_PRICE = 100;
|
||||
function format_qty(val) {
|
||||
if (val == null) { return ''; }
|
||||
if (val < 0) { return val; }
|
||||
return '+' + String(val);
|
||||
}
|
||||
function format_value(val) {
|
||||
if (isNaN(val)) { return ''; }
|
||||
if (val < 0) { return '-$' + String(Math.abs(val)); }
|
||||
return '$' + String(val);
|
||||
}
|
||||
var Chart = React.createClass({
|
||||
render: function () {
|
||||
return React.DOM.div(
|
||||
null,
|
||||
React.DOM.table(
|
||||
{ className: 'table table-sm' },
|
||||
React.DOM.thead(
|
||||
null,
|
||||
React.DOM.tr(
|
||||
null,
|
||||
React.DOM.th(null, "Location"),
|
||||
React.DOM.th({ className: 'text-right' }, "Quantity"),
|
||||
React.DOM.th({ className: 'text-right' }, "Value"))
|
||||
),
|
||||
React.DOM.tbody(
|
||||
null,
|
||||
this.locations().map(function (data) {
|
||||
var highlight = false;
|
||||
return React.DOM.tr(
|
||||
{ key: toKey(data.get('label')) },
|
||||
React.DOM.th(null, data.get('level') ? '\u2001' : '', data.get('label')),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight
|
||||
})
|
||||
}, format_qty(data.get('qty'))),
|
||||
React.DOM.td({
|
||||
className: React.addons.classSet({
|
||||
'text-right': true,
|
||||
'highlight-op': highlight
|
||||
})
|
||||
}, format_value(data.get('qty') * UNIT_PRICE))
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
locations: function () {
|
||||
var data = this.props.p.get('operations');
|
||||
|
||||
// {location: total_qty}
|
||||
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
|
||||
return acc.update(op.get('location'), function (qty) {
|
||||
return (qty || 0) + op.get('qty');
|
||||
});
|
||||
}, Immutable.Map());
|
||||
|
||||
return locations.valueSeq().flatMap(function (loc) {
|
||||
var sub_locations = loc.get('locations').valueSeq().map(function (subloc) {
|
||||
return subloc.set('level', 1).set('qty', totals.get(subloc));
|
||||
});
|
||||
|
||||
return Immutable.Seq.of(loc.set('level', 0)).concat(sub_locations);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
data.addWatch('chart', function (k, m, prev, next) {
|
||||
React.render(
|
||||
React.createElement(Controls, { p: next }),
|
||||
document.getElementById('chart-of-locations-controls'));
|
||||
React.render(
|
||||
React.createElement(Chart, { p: next }),
|
||||
document.getElementById('chart-of-locations'));
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var chart = document.querySelector('.chart-of-locations');
|
||||
if (!chart) { return; }
|
||||
|
||||
chart.setAttribute('id', 'chart-of-locations');
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('id', 'chart-of-locations-controls');
|
||||
chart.parentNode.insertBefore(controls, chart);
|
||||
|
||||
data.reset(Immutable.Map({
|
||||
active: null,
|
||||
operations: Immutable.OrderedSet()
|
||||
}));
|
||||
});
|
||||
var locations = Immutable.fromJS({
|
||||
warehouse: {
|
||||
label: "Warehouse",
|
||||
locations: {
|
||||
zone1: { label: "Zone 1" },
|
||||
zone2: { label: "Zone 2" }
|
||||
}
|
||||
},
|
||||
partners: {
|
||||
label: "Partner Locations",
|
||||
locations: {
|
||||
customers: { label: "Customers" },
|
||||
suppliers: { label: "Suppliers" }
|
||||
}
|
||||
},
|
||||
virtual: {
|
||||
label: "Virtual Locations",
|
||||
locations: {
|
||||
initial: { label: "Initial Inventory" },
|
||||
loss: { label: "Inventory Loss" },
|
||||
scrap: { label: "Scrapped" },
|
||||
manufacturing: { label: "Manufacturing" }
|
||||
}
|
||||
}
|
||||
}, function (k, v) {
|
||||
return Immutable.Iterable.isIndexed(v)
|
||||
? v.toList()
|
||||
: v.toOrderedMap();
|
||||
});
|
||||
var operations = Immutable.fromJS([{
|
||||
label: "Initial Inventory",
|
||||
operations: [
|
||||
{ location: locations.getIn(['virtual', 'locations', 'initial']), qty: -3 },
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: +3 }
|
||||
]
|
||||
}, {
|
||||
label: "Reception",
|
||||
operations: [
|
||||
{ location: locations.getIn(['partners', 'locations', 'suppliers']), qty: -2 },
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: +2 }
|
||||
]
|
||||
}, {
|
||||
label: "Delivery",
|
||||
operations: [
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
|
||||
{ location: locations.getIn(['partners', 'locations', 'customers']), qty: +1 }
|
||||
]
|
||||
}, {
|
||||
label: "Return",
|
||||
operations: [
|
||||
{ location: locations.getIn(['partners', 'locations', 'customers']), qty: -1 },
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: +1 }
|
||||
]
|
||||
}, {
|
||||
label: "1 product broken in Zone 1",
|
||||
operations: [
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
|
||||
{ location: locations.getIn(['virtual', 'locations', 'scrap']), qty: +1 }
|
||||
]
|
||||
}, {
|
||||
label: "Inventory check of Zone 1",
|
||||
operations: [
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
|
||||
{ location: locations.getIn(['virtual', 'locations', 'loss']), qty: +1 }
|
||||
]
|
||||
}, {
|
||||
label: "Move from Zone 1 to Zone 2",
|
||||
operations: [
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
|
||||
{ location: locations.getIn(['warehouse', 'locations', 'zone2']), qty: +1 }
|
||||
]
|
||||
}]);
|
||||
})();
|
||||
@@ -0,0 +1,101 @@
|
||||
(function () {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
alternatives();
|
||||
highlight();
|
||||
checks_handling();
|
||||
});
|
||||
|
||||
function highlight() {
|
||||
// NOTE: used by double-entry.rst
|
||||
$('.highlighter-list').each(function () {
|
||||
var $this = $(this),
|
||||
$target = $($this.data('target'));
|
||||
$this.on('mouseout', 'li', function (e) {
|
||||
$(e.currentTarget).removeClass('secondary');
|
||||
$target.find('.related').removeClass('related');
|
||||
}).on('mouseover', 'li', function (e) {
|
||||
if (!e.currentTarget.contains(e.target)) { return; }
|
||||
|
||||
var $li = $(e.currentTarget);
|
||||
// console.log($li, $li.data('highlight'), $target.find($li.data('highlight')));
|
||||
$li.addClass('secondary');
|
||||
$target.find($li.data('highlight')).addClass('related');
|
||||
});
|
||||
});
|
||||
}
|
||||
/** alternatives display:
|
||||
* - prepend control for each <dt>
|
||||
* - radio input with link to following dd
|
||||
* - label is <dt> content
|
||||
* - hide all first-level dt and dd (CSS)
|
||||
* - on change
|
||||
* - hide all dds
|
||||
* - show dd corresponding to the selected radio
|
||||
* - automatically select first control on startup
|
||||
*/
|
||||
function alternatives() {
|
||||
// NOTE: used by double-entry.rst & valuation_methods pages
|
||||
$('dl.alternatives').each(function (index) {
|
||||
var $list = $(this),
|
||||
$contents = $list.children('dd');
|
||||
var $controls = $('<div class="alternatives-controls">').append(
|
||||
$list.children('dt').map(function () {
|
||||
var label = document.createElement('label'),
|
||||
input = document.createElement('input');
|
||||
input.setAttribute('type', 'radio');
|
||||
input.setAttribute('name', 'alternatives-' + index);
|
||||
|
||||
var sibling = this;
|
||||
while ((sibling = sibling.nextSibling) && sibling.nodeType !== 1);
|
||||
input.content = sibling;
|
||||
|
||||
label.appendChild(input);
|
||||
label.appendChild(document.createTextNode(' '));
|
||||
label.appendChild(document.createTextNode(this.textContent));
|
||||
|
||||
return label;
|
||||
}))
|
||||
.insertBefore($list)
|
||||
.on('change', 'input', function (e) {
|
||||
// change event triggers only on newly selected input, not
|
||||
// on the one being deselected
|
||||
$contents.css('display', '');
|
||||
var content = e.target.content;
|
||||
content && (content.style.display = 'block');
|
||||
})
|
||||
.find('input:first').click();
|
||||
});
|
||||
}
|
||||
function checks_handling() {
|
||||
// NOTE: used by memento.rst
|
||||
var $section = $('.checks-handling');
|
||||
if (!$section.length) { return; }
|
||||
|
||||
var $ul = $section.find('ul')
|
||||
.find('li').each(function () {
|
||||
var txt = this.textContent;
|
||||
while (this.firstChild) {
|
||||
this.removeChild(this.firstChild)
|
||||
}
|
||||
|
||||
$('<label style="display: block;">')
|
||||
.append('<input type="radio" name="checks-handling">')
|
||||
.append(' ')
|
||||
.append(txt)
|
||||
.appendTo(this);
|
||||
}).end()
|
||||
.on('change', 'input', update);
|
||||
update();
|
||||
function update() {
|
||||
var $inputs = $ul.find('input');
|
||||
var idx = 0;
|
||||
$inputs.each(function (index) {
|
||||
if (this.checked) {
|
||||
idx = index;
|
||||
}
|
||||
}).eq(idx).prop('checked', true);
|
||||
$ul.nextAll('div').hide().eq(idx).show();
|
||||
}
|
||||
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,72 @@
|
||||
(function () {
|
||||
// NOTE: memento.rst
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var $rec = $('#reconciliation .reconciliation-example');
|
||||
if (!$rec.length) { return; }
|
||||
|
||||
var state = 0;
|
||||
var operations = [
|
||||
function reconcile2() {
|
||||
$rec.addClass('reconcile2');
|
||||
return 1;
|
||||
},
|
||||
function reconcile1() {
|
||||
$rec.addClass('reconcile1');
|
||||
return 2;
|
||||
},
|
||||
function unreconcile() {
|
||||
$rec.removeClass('reconcile1 reconcile2');
|
||||
$1.add($2).removeClass('reconciled');
|
||||
setTimeout(update_btn, 0);
|
||||
return 0;
|
||||
}
|
||||
];
|
||||
var $buttons = $('<div class="buttons">').on('click', 'button', function () {
|
||||
this.disabled = true;
|
||||
state = operations[state]();
|
||||
}).appendTo($rec);
|
||||
|
||||
var $1 = $rec.find('td:contains("Invoice 1"), td:contains("Payment 1")')
|
||||
.parent()
|
||||
.addClass('invoice1');
|
||||
var $2 = $rec.find('td:contains("Invoice 2"), td:contains("Payment 2")')
|
||||
.parent()
|
||||
.addClass('invoice2');
|
||||
|
||||
// will be called multiple times (each tr + each td), only take trs in
|
||||
// account
|
||||
$rec.on('animationend webkitAnimationEnd MSAnimationEnd', function (e) {
|
||||
switch (e.originalEvent.target) {
|
||||
case $1[0]:
|
||||
$1.addClass('reconciled');
|
||||
break;
|
||||
case $2[0]:
|
||||
$2.addClass('reconciled');
|
||||
break;
|
||||
}
|
||||
update_btn();
|
||||
});
|
||||
update_btn();
|
||||
|
||||
function update_btn() {
|
||||
var $reconcile = $('<button class="btn btn-success" type="button">')
|
||||
.text("Next Reconcile")
|
||||
.appendTo($buttons.empty())
|
||||
switch (state) {
|
||||
case 0:
|
||||
$reconcile.text("Reconcile");
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
$reconcile.prop('disabled', true);
|
||||
$('<button class="btn btn-primary" type="button">')
|
||||
.text("Unreconcile")
|
||||
.appendTo($buttons);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown button state " + state);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user