Merge pull request #2 from odoo/inventory
Inventory mementoes (wip-ish)
This commit is contained in:
commit
08cd20a121
@ -13,8 +13,9 @@
|
|||||||
background-color: hsl(219, 67%, 94%);
|
background-color: hsl(219, 67%, 94%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#chart-controls label:hover,
|
label:hover,
|
||||||
#entries-control label:hover {
|
label:hover,
|
||||||
|
.highlighter-list li:hover {
|
||||||
background-color: hsl(0, 0%, 94%);
|
background-color: hsl(0, 0%, 94%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -77,6 +78,13 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.values-table tr > * {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.values-table tr > :first-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
/* 3-column (thing, debit, credit) tables */
|
/* 3-column (thing, debit, credit) tables */
|
||||||
/* 2nd and 3rd th & td of each row right-aligned and 1/4th width */
|
/* 2nd and 3rd th & td of each row right-aligned and 1/4th width */
|
||||||
.d-c-table tr > :nth-child(2),
|
.d-c-table tr > :nth-child(2),
|
||||||
@ -86,20 +94,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 992px) {
|
@media (min-width: 992px) {
|
||||||
.accounts-table {
|
.accounts-table, .force-right .highlighter-target {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
color: #888 !important;
|
color: #888 !important;
|
||||||
}
|
}
|
||||||
.accounts-table .related {
|
.force-right .highlighter-target th {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
.accounts-table .related, .force-right .highlighter-target .related {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: #eee !important;
|
color: #eee !important;
|
||||||
}
|
}
|
||||||
.accounts-table .secondary {
|
.accounts-table .secondary, .force-right .highlighter-target .secondary {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: #aaa !important;
|
color: #aaa !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-of-accounts .highlight-op {
|
.chart-of-accounts .highlight-op,
|
||||||
|
.valuation-chart .highlight-op {
|
||||||
background-color: #030035;
|
background-color: #030035;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,3 +166,17 @@ blockquote.highlights {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
lists of alternatives
|
||||||
|
*/
|
||||||
|
.alternatives-controls label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
dl.alternatives > dt,
|
||||||
|
dl.alternatives > dd {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
dl.alternatives > dd {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
@ -99,10 +99,9 @@
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
accounts: function() {
|
accounts: function() {
|
||||||
var _this = this;
|
|
||||||
var data = this.props.p.get('operations');
|
var data = this.props.p.get('operations');
|
||||||
|
|
||||||
var totals = data.flatten(true).reduce(function (acc, op) {
|
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
|
||||||
return acc
|
return acc
|
||||||
.updateIn([op.get('account'), 'debit'], function (d) {
|
.updateIn([op.get('account'), 'debit'], function (d) {
|
||||||
return (d || 0) + op.get('debit', zero)(data);
|
return (d || 0) + op.get('debit', zero)(data);
|
||||||
|
270
_static/coa-valuation.js
Normal file
270
_static/coa-valuation.js
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
(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-condensed'},
|
||||||
|
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'));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var chart = document.querySelector('.valuation-chart');
|
||||||
|
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: "Supplier Invoice (PO $50, Invoice $40)",
|
||||||
|
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: "Supplier Invoice (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: "Manufacturing Order",
|
||||||
|
operations: [
|
||||||
|
{account: ASSETS.STOCK.code, debit: constant(50)},
|
||||||
|
{account: EXPENSES.MANUFACTURING_OVERHEAD.code, debit: constant(2)},
|
||||||
|
{account: ASSETS.RAW_MATERIALS.code, debit: 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);
|
||||||
|
}
|
||||||
|
})();
|
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: "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}
|
||||||
|
]
|
||||||
|
}]);
|
||||||
|
})();
|
@ -1,5 +1,70 @@
|
|||||||
(function () {
|
(function () {
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
alternatives();
|
||||||
|
highlight();
|
||||||
|
checks_handling();
|
||||||
|
});
|
||||||
|
|
||||||
|
function highlight() {
|
||||||
|
$('.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() {
|
||||||
|
$('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() {
|
||||||
var $section = $('.checks-handling');
|
var $section = $('.checks-handling');
|
||||||
if (!$section.length) { return; }
|
if (!$section.length) { return; }
|
||||||
|
|
||||||
@ -9,7 +74,7 @@
|
|||||||
while (this.firstChild) {
|
while (this.firstChild) {
|
||||||
this.removeChild(this.firstChild)
|
this.removeChild(this.firstChild)
|
||||||
}
|
}
|
||||||
|
|
||||||
$('<label style="display: block;">')
|
$('<label style="display: block;">')
|
||||||
.append('<input type="radio" name="checks-handling">')
|
.append('<input type="radio" name="checks-handling">')
|
||||||
.append(' ')
|
.append(' ')
|
||||||
@ -28,5 +93,6 @@
|
|||||||
}).eq(idx).prop('checked', true);
|
}).eq(idx).prop('checked', true);
|
||||||
$ul.nextAll('div').hide().eq(idx).show();
|
$ul.nextAll('div').hide().eq(idx).show();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -6621,6 +6621,7 @@ div.section > h1 {
|
|||||||
div.section > h2 {
|
div.section > h2 {
|
||||||
padding-bottom: 9px;
|
padding-bottom: 9px;
|
||||||
margin: 40px 0 20px;
|
margin: 40px 0 20px;
|
||||||
|
border-bottom: 1px solid #eeeeee;
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
3
conf.py
3
conf.py
@ -276,6 +276,9 @@ def setup(app):
|
|||||||
app.add_javascript('reconciliation.js')
|
app.add_javascript('reconciliation.js')
|
||||||
app.add_javascript('misc.js')
|
app.add_javascript('misc.js')
|
||||||
|
|
||||||
|
app.add_javascript('inventory.js');
|
||||||
|
app.add_javascript('coa-valuation.js')
|
||||||
|
|
||||||
app.connect('html-page-context', analytics)
|
app.connect('html-page-context', analytics)
|
||||||
app.add_config_value('google_analytics_key', '', 'env')
|
app.add_config_value('google_analytics_key', '', 'env')
|
||||||
|
|
||||||
|
247
double-entry.rst
Normal file
247
double-entry.rst
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
:classes: stripe
|
||||||
|
|
||||||
|
=================================
|
||||||
|
Double-Entry Inventory Management
|
||||||
|
=================================
|
||||||
|
|
||||||
|
A double-entry inventory has no stock input, output (disparition of products)
|
||||||
|
or transformation. Instead, all operations are stock moves between locations
|
||||||
|
(possibly virtual).
|
||||||
|
|
||||||
|
.. h:div:: force-right chart-of-locations
|
||||||
|
|
||||||
|
.. placeholder
|
||||||
|
|
||||||
|
Operations
|
||||||
|
==========
|
||||||
|
|
||||||
|
Stock moves represent the transit of goods and materials between locations.
|
||||||
|
|
||||||
|
.. rst-class:: alternatives force-right
|
||||||
|
|
||||||
|
Manufacturing Order
|
||||||
|
Consume:
|
||||||
|
| 2 Wheels: Stock → Production
|
||||||
|
| 1 Bike Frame: Stock → Production
|
||||||
|
Produce:
|
||||||
|
1 Bicycle: Production → Stock
|
||||||
|
Configuration:
|
||||||
|
| Stock: the location the Manufacturing Order is initiated from
|
||||||
|
| Production: on the product form, field "Production Location"
|
||||||
|
|
||||||
|
Drop-shipping
|
||||||
|
1 Bicycle: Supplier → Customer
|
||||||
|
Client Delivery
|
||||||
|
Pick
|
||||||
|
1 Bicycle: Stock → Packing Zone
|
||||||
|
Pack
|
||||||
|
1 Bicycle: Packing Zone → Output
|
||||||
|
Shipping
|
||||||
|
1 Bicycle: Output → Customer
|
||||||
|
Inter-Warehouse transfer
|
||||||
|
Transfer:
|
||||||
|
| 1 Bicycle: Warehouse 1 → Transit
|
||||||
|
| 1 Bicycle: Transit → Warehouse 2
|
||||||
|
Configuration:
|
||||||
|
| Warehouse 2: the location the transfer is initiated from
|
||||||
|
| Warehouse 1: on the transit route
|
||||||
|
Broken Product (scrapped)
|
||||||
|
1 Bicycle: Warehouse → Scrap
|
||||||
|
Inventory
|
||||||
|
Missing products in inventory
|
||||||
|
1 Bicycle: Warehouse → Inventory Loss
|
||||||
|
Extra products in inventory
|
||||||
|
1 Bicycle: Inventory Loss → Warehouse
|
||||||
|
Reception
|
||||||
|
| 1 Bicycle: Supplier → Input
|
||||||
|
| 1 Bicycle: Input → Stock
|
||||||
|
|
||||||
|
Analysis
|
||||||
|
========
|
||||||
|
|
||||||
|
Inventory analysis can use products count or products value (= number of
|
||||||
|
products * product cost).
|
||||||
|
|
||||||
|
For each inventory location, multiple data points can be analysed:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<ul class="highlighter-list" data-target=".analysis-table">
|
||||||
|
<li data-highlight=".analysis-valuation">inventory valuation</li>
|
||||||
|
<li data-highlight=".analysis-creation">
|
||||||
|
value creation (difference between the value of manufactured products
|
||||||
|
and the cost of raw materials used during manufacturing) (negative)
|
||||||
|
</li>
|
||||||
|
<li data-highlight=".analysis-lost">value of lost/stolen products</li>
|
||||||
|
<li data-highlight=".analysis-scrapped">value of scrapped products</li>
|
||||||
|
<li data-highlight=".analysis-delivered">value of products delivered to clients over a period</li>
|
||||||
|
<li data-highlight=".analysis-transit">value of products in transit between locations</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
.. h:div:: force-right analysis-table
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<table class="table table-condensed highlighter-target">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Location</th> <th class="text-right">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="analysis-valuation">
|
||||||
|
<th>Physical Locations</th> <td class="text-right">$1,000</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th> Warehouse 1</th> <td class="text-right">$600</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th> Warehouse 2</th> <td class="text-right">$400</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Partner Locations</th> <td class="text-right">- $1,500</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="analysis-delivered">
|
||||||
|
<th> Customers</th> <td class="text-right">$2,000</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th> Suppliers</th> <td class="text-right">- $3,500</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Virtual Locations</th> <td class="text-right">$500</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="analysis-transit">
|
||||||
|
<th> Transit Location</th> <td class="text-right">$600</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th> Initial Inventory</th> <td class="text-right">$0</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="analysis-lost">
|
||||||
|
<th> Inventory Loss</th> <td class="text-right">$350</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="analysis-scrapped">
|
||||||
|
<th> Scraped</th> <td class="text-right">$550</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="analysis-creation">
|
||||||
|
<th> Manufacturing</th> <td class="text-right">- $1,000</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Procurements & Procurement Rules
|
||||||
|
================================
|
||||||
|
|
||||||
|
A procurement is a request for a specific quantity of products to a specific
|
||||||
|
location. They can be created manually or automatically triggered by:
|
||||||
|
|
||||||
|
.. rst-class:: alternatives force-right
|
||||||
|
|
||||||
|
New sale orders
|
||||||
|
Effect
|
||||||
|
A procurement is created at the customer location for every product
|
||||||
|
ordered by the customer (you have to deliver the customer)
|
||||||
|
Configuration
|
||||||
|
Procurement Location: on the customer, field “Customer Location” (property)
|
||||||
|
Minimum Stock Rules
|
||||||
|
Effect
|
||||||
|
A procurement is created at the rule's location.
|
||||||
|
Configuration
|
||||||
|
Procurement location: on the rule, field "Location"
|
||||||
|
Procurement rules
|
||||||
|
Effect
|
||||||
|
A new procurement is created on the rule's source location
|
||||||
|
|
||||||
|
*Procurement rules* describe how procurements on specific locations should be
|
||||||
|
fulfilled e.g.:
|
||||||
|
|
||||||
|
* where the product should come from (source location)
|
||||||
|
* whether the procurement is :abbr:`MTO (Made To Order)` or :abbr:`MTS (Made
|
||||||
|
To Stock)`
|
||||||
|
|
||||||
|
.. h:div:: force-right
|
||||||
|
|
||||||
|
.. todo:: needs schema thing from FP
|
||||||
|
|
||||||
|
Routes
|
||||||
|
======
|
||||||
|
|
||||||
|
Procurement rules are grouped in routes. Routes define paths the product must
|
||||||
|
follow. Routes may be applicable or not, depending on the products, sales
|
||||||
|
order lines, warehouse,...
|
||||||
|
|
||||||
|
To fulfill a procurement, the system will search for rules belonging to routes
|
||||||
|
that are defined in (by order of priority):
|
||||||
|
|
||||||
|
.. rst-class:: alternatives force-right
|
||||||
|
|
||||||
|
Warehouses
|
||||||
|
Warehouse Route Example: Pick → Pack → Ship
|
||||||
|
|
||||||
|
Picking List:
|
||||||
|
Pick Zone → Pack Zone
|
||||||
|
Pack List:
|
||||||
|
Pack Zone → Gate A
|
||||||
|
Delivery Order:
|
||||||
|
Gate A → Customer
|
||||||
|
|
||||||
|
Routes that describe how you organize your warehouse should be defined on the warehouse.
|
||||||
|
A Product
|
||||||
|
Product Route Example: Quality Control
|
||||||
|
|
||||||
|
Reception:
|
||||||
|
Supplier → Input
|
||||||
|
Confirmation:
|
||||||
|
Input → Quality Control
|
||||||
|
Storage:
|
||||||
|
Quality Control → Stock
|
||||||
|
|
||||||
|
Product Category
|
||||||
|
Product Category Route Example: cross-dock
|
||||||
|
|
||||||
|
Reception:
|
||||||
|
Supplier → Input
|
||||||
|
Cross-Docks:
|
||||||
|
Input → Output
|
||||||
|
Delivery:
|
||||||
|
Output → Customer
|
||||||
|
Sale Order Line
|
||||||
|
Sale Order Line Example: Drop-shipping
|
||||||
|
|
||||||
|
Order:
|
||||||
|
Supplier → Customer
|
||||||
|
|
||||||
|
Push Rules
|
||||||
|
==========
|
||||||
|
|
||||||
|
Push rules trigger when products enter a specific location. They automatically
|
||||||
|
move the product to a new location. Whether a push rule can be used depends on
|
||||||
|
applicable routes.
|
||||||
|
|
||||||
|
.. rst-class:: alternatives force-right
|
||||||
|
|
||||||
|
Quality Control
|
||||||
|
* Product lands in Input
|
||||||
|
* Push 1: Input → Quality Control
|
||||||
|
* Push 2: Quality Control → Stock
|
||||||
|
Warehouse Transit
|
||||||
|
* Product lands in Transit
|
||||||
|
* Push: Transit → Warehouse 2
|
||||||
|
|
||||||
|
Procurement Groups
|
||||||
|
==================
|
||||||
|
|
||||||
|
Routes and rules define inventory moves. For every rule, a document type is
|
||||||
|
provided:
|
||||||
|
|
||||||
|
* Picking
|
||||||
|
* Packing
|
||||||
|
* Delivery Order
|
||||||
|
* Purchase Order
|
||||||
|
* ...
|
||||||
|
|
||||||
|
Moves are grouped within the same document type if their procurement group and
|
||||||
|
locations are the same.
|
||||||
|
|
||||||
|
A sale order creates a procurement group so that pickings and delivery orders
|
||||||
|
of the same order are grouped. But you can define specific groups on
|
||||||
|
reordering rules too. (e.g. to group purchases of specific products together)
|
@ -5,6 +5,7 @@ Odoo Business Mementoes
|
|||||||
.. rst-class:: index-tree
|
.. rst-class:: index-tree
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:titlesonly:
|
||||||
|
|
||||||
accounting
|
accounting
|
||||||
|
inventory
|
||||||
|
9
inventory.rst
Normal file
9
inventory.rst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
=========
|
||||||
|
Warehouse
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:titlesonly:
|
||||||
|
|
||||||
|
double-entry
|
||||||
|
valuation
|
274
valuation.rst
Normal file
274
valuation.rst
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
:classes: stripe
|
||||||
|
|
||||||
|
====================
|
||||||
|
Inventory Valuations
|
||||||
|
====================
|
||||||
|
|
||||||
|
Costing Method
|
||||||
|
==============
|
||||||
|
|
||||||
|
International accounting standards define several ways to compute product
|
||||||
|
costs:
|
||||||
|
|
||||||
|
.. rst-class:: alternatives force-right
|
||||||
|
|
||||||
|
Standard Price
|
||||||
|
.. rst-class:: values-table
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:widths: 28 18 18 18 18
|
||||||
|
:header-rows: 1
|
||||||
|
:stub-columns: 1
|
||||||
|
|
||||||
|
* - Operation
|
||||||
|
- Unit Cost
|
||||||
|
- Qty On Hand
|
||||||
|
- Delta Value
|
||||||
|
- Inventory Value
|
||||||
|
* - Receive 8 Products at $10
|
||||||
|
- $10
|
||||||
|
- 10
|
||||||
|
- +8*$10
|
||||||
|
- $80
|
||||||
|
* - Receive 4 Products at $12
|
||||||
|
- $10
|
||||||
|
- 12
|
||||||
|
- +4*$10
|
||||||
|
- $120
|
||||||
|
* - Deliver 10 Products
|
||||||
|
- $10
|
||||||
|
- 2
|
||||||
|
- | -10*$10
|
||||||
|
|
|
||||||
|
- $20
|
||||||
|
* - Receive 2 Products at $9
|
||||||
|
- $10
|
||||||
|
- 4
|
||||||
|
- +2*$10
|
||||||
|
- $40
|
||||||
|
Average Price
|
||||||
|
.. rst-class:: values-table
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:widths: 28 18 18 18 18
|
||||||
|
:header-rows: 1
|
||||||
|
:stub-columns: 1
|
||||||
|
|
||||||
|
* - Operation
|
||||||
|
- Unit Cost
|
||||||
|
- Qty On Hand
|
||||||
|
- Delta Value
|
||||||
|
- Inventory Value
|
||||||
|
* - Receive 8 Products at $10
|
||||||
|
- $10
|
||||||
|
- 8
|
||||||
|
- +8*$10
|
||||||
|
- $80
|
||||||
|
* - Receive 4 Products at $16
|
||||||
|
- $12
|
||||||
|
- 12
|
||||||
|
- +4*$16
|
||||||
|
- $144
|
||||||
|
* - Deliver 10 Products [#average-removal]_
|
||||||
|
- $12
|
||||||
|
- 2
|
||||||
|
- | -10*$12
|
||||||
|
|
|
||||||
|
- $24
|
||||||
|
* - Receive 2 Products at $6
|
||||||
|
- $9
|
||||||
|
- 4
|
||||||
|
- +2*$6
|
||||||
|
- $36
|
||||||
|
FIFO
|
||||||
|
.. rst-class:: values-table
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:widths: 28 18 18 18 18
|
||||||
|
:header-rows: 1
|
||||||
|
:stub-columns: 1
|
||||||
|
|
||||||
|
* - Operation
|
||||||
|
- Unit Cost
|
||||||
|
- Qty On Hand
|
||||||
|
- Delta Value
|
||||||
|
- Inventory Value
|
||||||
|
* - Receive 8 Products at $10
|
||||||
|
- $10
|
||||||
|
- 8
|
||||||
|
- +8*$10
|
||||||
|
- $80
|
||||||
|
* - Receive 4 Products at $16
|
||||||
|
- $12
|
||||||
|
- 12
|
||||||
|
- +4*$16
|
||||||
|
- $144
|
||||||
|
* - Deliver 10 Products
|
||||||
|
- $16
|
||||||
|
- 2
|
||||||
|
- | -8*$10
|
||||||
|
| -2*$16
|
||||||
|
- $32
|
||||||
|
* - Receive 2 Products at $6
|
||||||
|
- $11
|
||||||
|
- 4
|
||||||
|
- +2*$6
|
||||||
|
- $44
|
||||||
|
LIFO (not accepted in IFRS)
|
||||||
|
.. rst-class:: values-table
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:widths: 28 18 18 18 18
|
||||||
|
:header-rows: 1
|
||||||
|
:stub-columns: 1
|
||||||
|
|
||||||
|
* - Operation
|
||||||
|
- Unit Cost
|
||||||
|
- Qty On Hand
|
||||||
|
- Delta Value
|
||||||
|
- Inventory Value
|
||||||
|
* - Receive 8 Products at $10
|
||||||
|
- $10
|
||||||
|
- 8
|
||||||
|
- +8*$10
|
||||||
|
- $80
|
||||||
|
* - Receive 4 Products at $16
|
||||||
|
- $12
|
||||||
|
- 12
|
||||||
|
- +4*$16
|
||||||
|
- $144
|
||||||
|
* - Deliver 10 Products
|
||||||
|
- $10
|
||||||
|
- 2
|
||||||
|
- | -4*$16
|
||||||
|
| -6*$10
|
||||||
|
- $20
|
||||||
|
* - Receive 2 Products at $6
|
||||||
|
- $8
|
||||||
|
- 4
|
||||||
|
- +2*$6
|
||||||
|
- $32
|
||||||
|
|
||||||
|
The costing method is defined on the product form: standard, average or real
|
||||||
|
price.
|
||||||
|
|
||||||
|
For "real price", the costing is further refined by the removal strategy (on
|
||||||
|
the warehouse location or product category), FIFO by default.
|
||||||
|
|
||||||
|
Periodic Inventory Valuation
|
||||||
|
============================
|
||||||
|
|
||||||
|
In a periodic inventory valuation, goods reception and outgoing shipments have
|
||||||
|
no direct impact in the accounting. At the end of the month or year, the
|
||||||
|
accountant post one journal entry representing the value of the physical
|
||||||
|
inventory.
|
||||||
|
|
||||||
|
.. rst-class:: alternatives force-right
|
||||||
|
|
||||||
|
Supplier Invoice
|
||||||
|
.. rst-class:: values-table
|
||||||
|
|
||||||
|
============================= ===== ======
|
||||||
|
\ Debit Credit
|
||||||
|
============================= ===== ======
|
||||||
|
Assets: Uninvoiced Inventory 50
|
||||||
|
Assets: Deferred Tax Assets 4.68
|
||||||
|
Expenses: Price Difference 2
|
||||||
|
Liabilities: Accounts Payable 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
|
||||||
|
|
||||||
|
In this scenario, the purchase order was $50 but the company received an
|
||||||
|
invoice for $52 as there were extra shipping costs.
|
||||||
|
Goods Receptions
|
||||||
|
No Journal Entry
|
||||||
|
Customer Invoice
|
||||||
|
.. rst-class:: values-table
|
||||||
|
|
||||||
|
===================================== ===== ======
|
||||||
|
\ Debit Credit
|
||||||
|
===================================== ===== ======
|
||||||
|
Revenue: Goods 100
|
||||||
|
Liabilities: Deferred Tax Liabilities 9
|
||||||
|
Assets: Accounts Receivable 109
|
||||||
|
Assets: Inventory 50
|
||||||
|
Expenses: Cost of Goods Sold 50
|
||||||
|
===================================== ===== ======
|
||||||
|
|
||||||
|
Explanation:
|
||||||
|
* Revenues increase by $100
|
||||||
|
* A tax to pay at the end of the month of $9
|
||||||
|
* The customer owns you $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
|
||||||
|
* Deferred 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)
|
||||||
|
|
||||||
|
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.
|
||||||
|
Customer Shipping
|
||||||
|
No Journal Entry
|
||||||
|
Manufacturing Orders
|
||||||
|
No Journal Entry
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<hr style="float: none; visibility: hidden; margin: 0;">
|
||||||
|
|
||||||
|
At the end of the month/year, the company do a physical inventory (or just
|
||||||
|
rely on the inventory in Odoo). They multiply the quantity of each product by
|
||||||
|
its cost to know the inventory value of the company.
|
||||||
|
|
||||||
|
.. h:div:: force-right
|
||||||
|
|
||||||
|
Current Values in Accounting:
|
||||||
|
|
||||||
|
.. rst-class:: values-table
|
||||||
|
|
||||||
|
=============== ====== ====== =======
|
||||||
|
Account Debit Credit Balance
|
||||||
|
=============== ====== ====== =======
|
||||||
|
14000 Inventory $5,000 $800 $4,200
|
||||||
|
=============== ====== ====== =======
|
||||||
|
|
||||||
|
Real Inventory Valuation: $4,800
|
||||||
|
|
||||||
|
Journal Entry to create:
|
||||||
|
|
||||||
|
.. rst-class:: values-table
|
||||||
|
|
||||||
|
========================== ==== ====
|
||||||
|
14000 Inventory $600
|
||||||
|
14700 Inventory Variations $600
|
||||||
|
========================== ==== ====
|
||||||
|
|
||||||
|
|
||||||
|
Perpetual Inventory Valuation
|
||||||
|
=============================
|
||||||
|
|
||||||
|
In a perpetual inventory valuation, goods reception and outgoing shipments are
|
||||||
|
directly posted in the accounting. The inventory valuation is always
|
||||||
|
up-to-date.
|
||||||
|
|
||||||
|
.. h:div:: valuation-chart force-right
|
||||||
|
|
||||||
|
.. placeholder
|
||||||
|
|
||||||
|
.. [#average-removal] products leaving the stock have no impact on the average
|
||||||
|
price.
|
Loading…
Reference in New Issue
Block a user