[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:
parent
2a714115b1
commit
eb20f603b5
210
_static/inventory.js
Normal file
210
_static/inventory.js
Normal 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}
|
||||
]
|
||||
}]);
|
||||
})();
|
2
conf.py
2
conf.py
@ -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')
|
||||
|
||||
|
@ -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
|
||||
==================
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user