[MERGE] Forward-port of branch 12.0 to 13.0
This commit is contained in:
@@ -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-anglo-saxon'));
|
||||
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-anglo-saxon');
|
||||
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-continental'));
|
||||
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-continental');
|
||||
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