[ADD] chart of inventory locations

Quite a bit of duplication with the chart of accounts (controls are just
about identical) but not quite enough that the CoA can trivially be
reused.
This commit is contained in:
Xavier Morel 2015-04-15 17:09:18 +02:00
parent 2a714115b1
commit eb20f603b5
3 changed files with 215 additions and 8 deletions

210
_static/inventory.js Normal file
View File

@ -0,0 +1,210 @@
(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)
.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-condensed'},
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: "Inventaire Initial",
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}
]
}]);
})();

View File

@ -276,6 +276,8 @@ def setup(app):
app.add_javascript('reconciliation.js')
app.add_javascript('misc.js')
app.add_javascript('inventory.js');
app.connect('html-page-context', analytics)
app.add_config_value('google_analytics_key', '', 'env')

View File

@ -7,13 +7,9 @@ Double-Entry Inventory Management
In a double-entry inventory, there is no stock input, output (disparition) or
transformation. Instead, there are only stock moves between locations.
* Inventory: 3 products in Zone 1
* Reception: 2 products in Zone 1
* Delivery: 1 product to client
* Return: 1 product from client
* Scrap: 1 product broken in zone 1
* Inventory Zone 1: loss of 1 product
* Move: 1 product Zone 1 ➔ Zone 2
.. h:div:: force-right chart-of-locations
.. placeholder
Operations
==========
@ -95,4 +91,3 @@ Some example:
Procurement Groups
==================