[IMP] Modify the inventory valuation memento, for the continental accounting

This commit is contained in:
Yannick Tivisse 2015-12-01 12:37:40 +01:00
parent 4993bf959c
commit a08d151437
8 changed files with 611 additions and 284 deletions

View File

@ -123,6 +123,11 @@ label:hover,
.valuation-chart .highlight-op {
background-color: #030035;
}
.chart-of-accounts .highlight-op,
.valuation-chart-continental .highlight-op {
background-color: #030035;
}
}
.journal-entries .entries-listing p {

View File

@ -0,0 +1,261 @@
(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-continental'));
});
document.addEventListener('DOMContentLoaded', function () {
var chart = document.querySelector('.valuation-chart-continental');
if (!chart) { return; }
var controls = document.createElement('div');
controls.setAttribute('id', 'chart-controls');
chart.parentNode.insertBefore(controls, chart);
data.reset(Immutable.Map({
// last-selected operation
active: null,
// set of all currently enabled operations
operations: Immutable.OrderedSet()
}));
});
var NULL = Immutable.Map({debit: 0, credit: 0});
var ASSETS = {
code: 1,
label: "Assets",
BANK: { code: 11000, label: "Cash" },
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
STOCK: { code: 14000, label: "Inventory" },
RAW_MATERIALS: { code: 14100, label: "Raw Materials Inventory" },
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
};
var LIABILITIES = {
code: 2,
label: "Liabilities",
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
};
var EQUITY = {
code: 3,
label: "Equity",
CAPITAL: { code: 31000, label: "Common Stock" }
};
var REVENUE = {
code: 4,
label: "Revenue",
SALES: { code: 41000, label: "Goods" },
};
var EXPENSES = {
code: 5,
label: "Expenses",
PURCHASED_GOODS: { code: 51000, label: "Purchased Goods" },
PURCHASED_SERVICES: { code: 52000, label: "Purchased Services" },
INVENTORY_VARIATIONS: { code: 58000, label: "Inventory Variations" },
OTHER_OPERATING_EXPENSES: { code: 59000, label: "Other Operating Expenses" },
};
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
return Immutable.Iterable.isIndexed(v)
? v.toList()
: v.toOrderedMap();
});
var accounts = categories.toSeq().flatMap(function (cat) {
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
return k.toUpperCase() === k;
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
return account.set(
'accounts',
Immutable.Seq.of(account.get('code')).concat(
account.toIndexedSeq().map(function (val) {
return Immutable.Map.isMap(val) && val.get('code');
}).filter(function (val) { return !!val; })
)
);
});
var sale = 100,
cor = 50,
cor_tax = cor * 0.09,
tax = sale * 0.09,
total = sale + tax,
purchase = 52,
purchase_tax = 52 * 0.09;
var operations = Immutable.fromJS([{
label: "Vendor Invoice (PO €50, Invoice €50)",
operations: [
{account: EXPENSES.PURCHASED_GOODS.code, debit: constant(50)},
{account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09)},
{account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09)},
]
}, {
label: "Vendor Goods Reception (PO €50, Invoice €50)",
operations: [
{account: EXPENSES.INVENTORY_VARIATIONS.code, credit: constant(50)},
{account: ASSETS.STOCK.code, debit: constant(50)},
]
}, {
label: "Vendor Invoice (PO €48, Invoice €50)",
operations: [
{account: EXPENSES.PURCHASED_GOODS.code, debit: constant(48)},
{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: EXPENSES.PURCHASED_GOODS.code, debit: constant(cor)},
{account: REVENUE.SALES.code, credit: constant(sale)},
{account: EXPENSES.INVENTORY_VARIATIONS.code, credit: constant(cor)},
{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);
}
})();

View File

@ -293,6 +293,7 @@ def setup(app):
app.add_javascript('inventory.js');
app.add_javascript('coa-valuation.js')
app.add_javascript('coa-valuation-continental.js')
app.add_config_value('canonical_root', None, 'env')

View File

@ -6,5 +6,5 @@ Lots and Serial Numbers
:titlesonly:
:glob:
reporting/valuation_methods
reporting/inventory_valuation
reporting/valuation_methods_continental
reporting/valuation_methods_anglo_saxon

View File

@ -1,3 +0,0 @@
=================================
How to do an inventory valuation?
=================================

View File

@ -1,279 +0,0 @@
:code-column:
===================================================
What are the different inventory valuation methods
===================================================
Costing Method
==============
International accounting standards define several ways to compute product
costs:
.. rst-class:: alternatives doc-aside
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
* -
- $10
- 0
-
- $0
* - Receive 8 Products at $10
- $10
- 8
- +8*$10
- $80
* - Receive 4 Products at $16
- $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
* -
- $0
- 0
-
- $0
* - 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
* -
- $0
- 0
-
- $0
* - 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
* -
- $0
- 0
-
- $0
* - 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 doc-aside
Supplier Invoice
.. rst-class:: values-table
============================= ===== ======
\ Debit Credit
============================= ===== ======
Assets: Inventory 50
Assets: Deferred Tax Assets 4.68
Liabilities: Accounts Payable 54.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:
* 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
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:: doc-aside
If the real value of the inventory is $4800 but the *14000 Inventory*
account has a balance of $4200, the following journal entry is created
manually:
.. 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 doc-aside
.. placeholder
.. [#average-removal] products leaving the stock have no impact on the average
price.

View File

@ -0,0 +1,3 @@
==========================================================
How to do an inventory valuation? (Anglo-Saxon Accounting)
==========================================================

View File

@ -0,0 +1,339 @@
:code-column:
==========================================================
How to do an inventory valuation? (Continental Accounting)
==========================================================
Costing Method
==============
Every year your inventory valuation has to be recorded in your
balance sheet. This implies two main choices:
- the way you compute the cost of your stored items
(Standard vs. Average vs. Real Price);
- the way you record the inventory value into your books
(periodic vs. Perpetual).
.. rst-class:: alternatives doc-aside
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
* -
- €10
- 0
-
- €0
* - Receive 8 Products at €10
- €10
- 8
- +8*€10
- €80
* - Receive 4 Products at €16
- €10
- 12
- +4*€10
- €120
* - Deliver 10 Products
- €10
- 2
- | -10*€10
|
- €20
* - Receive 2 Products at €9
- €10
- 4
- +2*€10
- €40
**Standard Price** means you estimate the cost price based
on direct materials, direct labor and manufacturing overhead
at the end of a specific period (usually once a year). You
enter this cost price in the product form.
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
* -
- €0
- 0
-
- €0
* - 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
The **Average Price** method recomputes the cost price as a
receipt order has been processed, based on prices defined in
tied purchase orders. This method is mainly justified in case
of huge purchase price variations and is quite unusual due to
its operational complexity. Your actually need a software like
Odoo to keep this cost up-to-date.
This method is dedicated to advanced users. It requires well
established business processes because the order in which you
process receipt orders matters in the cost computation. Moreover
if you mistakenly process such an order, there is no way to
reset the cost price at its initial value.
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
* -
- €0
- 0
-
- €0
* - 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
For **Real Price** (FIFO, LIFO, FEFO, etc), the costing is
further refined by the removal strategy set on the warehouse
location or product's internal category. The default strategy
is FIFO. With such method, your inventory value is computed
from the real cost of your stored products (cfr. Quantitative
Valuation) and not from the cost price shown in the product
form. Whenever you ship items, the cost price is reset to the
cost of the last item(s) shipped. This cost price is used to
value any product not received from a purchase order (e.g.
inventory adjustments).
Such a method is advised if you manage all your workflow into
Odoo (Sales, Purchases, Inventory). It suits any kind of users.
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
* -
- €0
- 0
-
- €0
* - 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
Odoo allows any method. The default one is **Standard Price**.
To change it, check **Use a 'Fixed', 'Real' or 'Average' price
costing method** in Purchase settings. Then set the costing
method from products' internal categories. Categories show up
in the Inventory tab of the product form.
Whatever the method is, Odoo provides a full inventory valuation
in :menuselection:`Inventory --> Reports --> Inventory Valuation`
(i.e. current quantity in stock * cost price).
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 posts one
journal entry representing the value of the physical inventory.
This is the default configuration in Odoo and it works
out-of-the-box. Check following operations and find out how
Odoo is managing the accounting postings.
.. rst-class:: alternatives doc-aside
Vendor Bill
.. rst-class:: values-table
============================= ===== ======
\ Debit Credit
============================= ===== ======
Assets: Inventory 50
Assets: Deferred Tax Assets 4.68
Liabilities: Accounts Payable 54.68
============================= ===== ======
Configuration:
* Purchased Goods: defined on the product or on the internal category of related product (Expense Account field)
* Deferred Tax Assets: defined on the tax used on the purchase order line
* Accounts Payable: defined on the vendor related to the bill
Goods Receptions
No Journal Entry
Customer Invoice
.. rst-class:: values-table
===================================== ===== ======
\ Debit Credit
===================================== ===== ======
Revenues: Sold Goods 100
Liabilities: Deferred Tax Liabilities 9
Assets: Accounts Receivable 109
===================================== ===== ======
Configuration:
* Revenues: defined on the product or on the internal category of related product (Income Account field)
* Deferred Tax Liabilities: defined on the tax used on the invoice line
* Accounts Receivable: defined on the customer (Receivable Account)
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, your company does a physical inventory
or just relies on the inventory in Odoo to value the stock into your books.
Create a journal entry to move the stock variation value from your
Profit&Loss section to your assets.
.. h:div:: doc-aside
.. rst-class:: values-table
===================================== ===== ======
\ Debit Credit
===================================== ===== ======
Assets: Inventory X
Expenses: Inventory Variations X
===================================== ===== ======
If the stock value decreased, he makes it upside down.
.. raw:: html
<hr style="float: none; visibility: hidden; margin: 0;">
Perpetual Inventory Valuation
=============================
In a perpetual inventory valuation, goods receptions and
outgoing shipments are posted in your books in real time.
The books are therefore always up-to-date. This mode is
dedicated to expert accountants and advanced users only.
As opposed to periodic valuation, it requires some extra
configuration & testing.
Let's take the case of a reseller.
.. h:div:: valuation-chart-continental doc-aside
.. placeholder
.. [#average-removal] products leaving the stock have no impact on the average price.
.. raw:: html
<hr style="float: none; visibility: hidden; margin: 0;">
.. h:div:: doc-aside
**Configuration:**
- Accounts Receivable/Payable: defined on the partner (Accounting tab)
- Deferred Tax Assets/Liabilities: defined on the tax used on the invoice line
- Revenues/Expenses: defined by default on product's internal category; can be
also set in product form (Accounting tab) as a replacement value.
- Inventory Variations: to set as Stock Input/Output Account in product's internal
category
- Inventory: to set as Stock Valuation Account in product's internal category