[ADD] Inventory: add stock valuation cheat sheet

An adaptation of the venerable business memento from 8.0 to the new
inventory valuation mechanics of Odoo 19.0 by scavenging and cobbling
together of the scripts `entries.js` and `coa-valuation.js`. The shared
data is kept in a separate file.

Additionally, we remove the old inventory valuation documentation.

Task ID: 5107300

closes odoo/documentation#14906

Signed-off-by: Felicia Kuan (feku) <feku@odoo.com>
This commit is contained in:
Lulu Grimalkin (lugr)
2025-10-06 11:20:16 +02:00
committed by “Dallas”
parent c97a8e6bad
commit 912dde6d26
63 changed files with 2387 additions and 773 deletions

View File

@@ -282,7 +282,7 @@ available methods are standard price, average price, :abbr:`LIFO (Last-In, First
:abbr:`FIFO (First-In, First-Out).`
.. seealso::
:doc:`../inventory_and_mrp/inventory/product_management/inventory_valuation/inventory_valuation_config`
:doc:`../inventory_and_mrp/inventory/inventory_valuation/cheat_sheet`
.. _accounting/retained-earnings:

View File

@@ -25,14 +25,8 @@ valuation <inventory/avg_price/leaving_inventory>`.
.. note::
This document addresses a specific use case for theoretical purposes. For instructions on how to
set up and use |AVCO|, refer to the :doc:`inventory valuation configuration
<../../../inventory_and_mrp/inventory/product_management/inventory_valuation/inventory_valuation_config>`
doc.
.. seealso::
- :doc:`Using inventory valuation
<../../../inventory_and_mrp/inventory/product_management/inventory_valuation/using_inventory_valuation>`
- :ref:`Other inventory valuation methods <inventory/warehouses_storage/costing_methods>`
set up and use |AVCO|, refer to the :doc:`inventory valuation cheat sheet
<../../../inventory_and_mrp/inventory/inventory_valuation/cheat_sheet>`.
Configuration
=============
@@ -43,8 +37,8 @@ product category page, set :guilabel:`Costing Method` to `Average Cost (AVCO)` a
:guilabel:`Inventory Valuation` to `Automated`.
.. seealso::
:doc:`Inventory valuation configuration
<../../../inventory_and_mrp/inventory/product_management/inventory_valuation/inventory_valuation_config>`
:doc:`Inventory valuation cheat sheet
<../../../inventory_and_mrp/inventory/inventory_valuation/cheat_sheet>`
Using average cost valuation
============================
@@ -70,7 +64,7 @@ When new products arrive, the new average cost for each product is recomputed us
- **Purchase Price**: estimated price of products at the reception of products (since vendor bills
may arrive later). The amount includes not only the price for the products, but also added costs,
such as shipping, taxes, and :doc:`landed costs
<../../../inventory_and_mrp/inventory/product_management/inventory_valuation/landed_costs>`. At
<../../../inventory_and_mrp/inventory/inventory_valuation/landed_costs>`. At
reception of the vendor bill, this price is adjusted;
- **Final Qty**: quantity of on-hand stock after the stock move.
@@ -263,9 +257,6 @@ account that tracks the amount to be paid to vendors. Once a vendor delivers an
value** increases based on the vendor price of the products that have entered the stock. The holding
account (called **stock input**) is credited and only reconciled once the vendor bill is received.
.. seealso::
- :ref:`Anglo-Saxon vs. Continental <inventory/warehouses_storage/accounting-types>`
The table below reflects journal entries and accounts. The *stock input* account stores the money
intended to pay vendors when the vendor bill has not yet been received. To balance accounts when
returning products that have a price difference between the price the product is **valued at** and

View File

@@ -1553,7 +1553,7 @@ Configuration
~~~~~~~~~~~~~
In order to track the correct customs number for a specific invoice, Odoo uses :doc:`landed costs
<../../inventory_and_mrp/inventory/product_management/inventory_valuation/landed_costs>`. Go to
<../../inventory_and_mrp/inventory/inventory_valuation/landed_costs>`. Go to
:menuselection:`Inventory --> Configuration --> Settings`, and in the :guilabel:`Valuation` section,
make sure that :guilabel:`Landed Costs` is activated.
@@ -1569,8 +1569,7 @@ and complete these three requirements:
Number` but **not** :guilabel:`By Quantity`.
- :guilabel:`Invoicing Policy` **must** be set to :guilabel:`Delivered quantities`.
- :doc:`Valuation by lots/serial numbers
<../../inventory_and_mrp/inventory/product_management/inventory_valuation/valuation_by_lots>`
**must** be enabled.
<../../inventory_and_mrp/inventory/inventory_valuation/valuation_by_lots>` **must** be enabled.
This will make the field :guilabel:`Customs invoicing` available on the :guilabel:`Accounting` tab.
Enable the field to use customs numbers with this product.
@@ -1583,8 +1582,8 @@ configuration:
.. note::
The feature works regardless of whether the :doc:`inventory valuation
<../../inventory_and_mrp/inventory/product_management/inventory_valuation/using_inventory_valuation>`
is set to either :guilabel:`Periodic (at closing)` or :guilabel:`Perpetual (at invoicing)`.
<../../inventory_and_mrp/inventory/inventory_valuation/cheat_sheet>` is set to either
:guilabel:`Periodic (at closing)` or :guilabel:`Perpetual (at invoicing)`.
.. image:: mexico/mx-landing-configuration.png
:alt: Storable products general configuration.
@@ -1610,7 +1609,7 @@ number`.
While it is possible to add costs related to the customs number at this stage of the process, it
is highly recommended to create a landed cost from a vendor bill from your customs agent. Learn
more about :doc:`Landed Costs here
<../../inventory_and_mrp/inventory/product_management/inventory_valuation/landed_costs>`.
<../../inventory_and_mrp/inventory/inventory_valuation/landed_costs>`.
.. warning::
The :guilabel:`Customs number` field is not editable once it is set, and cannot be repeated,

View File

@@ -1003,12 +1003,11 @@ Several configurations related to the product or product category are necessary
- **Automatic inventory valuation**: For storable goods (:dfn:`products with tracked inventory`),
use :doc:`automatic inventory valuation
<../../inventory_and_mrp/inventory/product_management/inventory_valuation/inventory_valuation_config>`.
Once automatic inventory valuation is enabled, this valuation method can be enabled for
a product's :ref:`product category <inventory/warehouses_storage/valuation-on-product-category>`.
<../../inventory_and_mrp/inventory/inventory_valuation/cheat_sheet>`. Once automatic inventory
valuation is enabled, this valuation method can be enabled for a product's product category.
- **Costing method:** Storable goods must use a :doc:`costing method
<../../inventory_and_mrp/inventory/product_management/inventory_valuation/inventory_valuation_config>`
<../../inventory_and_mrp/inventory/inventory_valuation/cheat_sheet>`
**other** than :guilabel:`Standard Price`, as the journal entries generated from stock moves are
used to populate the |PLE| reports.
@@ -1041,7 +1040,7 @@ Generate a .txt file for permanent inventory Kardex reports
|PLE| 12.1 and 13.1 come as two separate books. The books need to be downloaded in `.txt` file
format from Odoo, and then they should be submitted to the |SUNAT| |PLE| software.
On the :ref:`Inventory Valuation Report <inventory/management/reporting/valuation-report>`, click
On the :ref:`Inventory Valuation Report <inventory/product_management/valuation-report>`, click
the :guilabel:`PLE Reports` button. Then, select the :guilabel:`Period` and choose a report to
export: either the :guilabel:`PLE 12.1` or :guilabel:`PLE 13.1`. Odoo generates a `.txt` file
for the chosen report.

View File

@@ -19,3 +19,4 @@ users to easily manage lead times, automate replenishment, configure advanced ro
inventory/product_management
inventory/warehouses_storage
inventory/shipping_receiving
inventory/inventory_valuation

View File

@@ -7,8 +7,7 @@ Inventory valuation
.. toctree::
:titlesonly:
inventory_valuation/inventory_valuation_config
inventory_valuation/using_inventory_valuation
inventory_valuation/cheat_sheet
inventory_valuation/landed_costs
inventory_valuation/valuation_by_lots

View File

@@ -0,0 +1,408 @@
:code-column:
:custom-css: valuation.css
:custom-js: misc.js,valuation-data.js,valuation-journal.js,valuation-accounting.js
=====================
Valuation cheat sheet
=====================
.. rst-class:: full-width
Costing Methods
===============
Odoo supports 3 costing methods configured in accounting's settings and, optionally,
the product's category.
.. rst-class:: alternatives doc-aside
Standard Cost: fixed unit cost, updated manually
.. list-table::
:widths: 28 18 18 18 18
:header-rows: 1
:stub-columns: 1
:class: values-table
* - Operation
- Unit Cost
- Qty On Hand
- Delta Value
- Inventory Value
* -
- $10
- 0
-
- $0
* - Receive 8 @$10
- $10
- 8
- +8×$10
- $80
* - Receive 4 @$16
- $10
- 12
- +4×$10
- $120
* - Deliver 10
- $10
- 2
- | -10×$10
|
- $20
* - Receive 2 @$9
- $10
- 4
- +2×$10
- $40
Average Cost: weighted average of all units
.. list-table::
:widths: 28 18 18 18 18
:header-rows: 1
:stub-columns: 1
:class: values-table
* - Operation
- Unit Cost
- Qty On Hand
- Delta Value
- Inventory Value
* -
- $0
- 0
-
- $0
* - Receive 8 @$10
- $10
- 8
- +8×$10
- $80
* - Receive 4 @$16
- $12
- 12
- +4×$16
- $144
* - Deliver 10
- $12
- 2
- | -10×$12
|
- $24
* - Receive 2 @$6
- $9
- 4
- +2×$6
- $36
FIFO: first in, first out
.. list-table::
:widths: 28 18 18 18 18
:header-rows: 1
:stub-columns: 1
:class: values-table
* - Operation
- Unit Cost
- Qty On Hand
- Delta Value
- Inventory Value
* -
- $0
- 0
-
- $0
* - Receive 8 @$10
- $10
- 8
- +8×$10
- $80
* - Receive 4 @$16
- $12
- 12
- +4×$16
- $144
* - Deliver 10
- $16
- 2
- | -8×$10
| -2×$16
- $32
* - Receive 2 @$6
- $11
- 4
- +2×$6
- $44
.. rst-class:: alternatives-note
.. note:: Removal strategies also support :abbr:`LIFO (last in, first out)` and :abbr:`FEFO
(first expiry, first out)`, but they only impact which product is first picked, not the
valuation method. For example, you can pick using LIFO, but using average cost for valuation,
as LIFO is not allowed by :abbr:`IFRS (International Financial Reporting Standards)`.
Inventory vs Accounting
=======================
.. rst-class:: inventory-app-paragraph
The :doc:`Inventory app </applications/inventory_and_mrp/inventory>` keeps track of the inventory
value in real time as you **receive and deliver goods**. The reporting menu lets you analyze
inventory quantities and values by company, location, product, and more.
.. rst-class:: accounting-app-paragraph
The :doc:`Accounting app </applications/finance/accounting>` updates accounts when you receive
**invoices or bills**. Even though receipts and invoices differ, its not practical for
accountants to post journal entries for every inventory movement. So, they post a closing entry
to account for the difference between what has been invoiced and received/delivered. This closing
process happens usually once a year for SMEs, or once a month for larger companies.
.. role:: good
.. role:: meh
.. role:: bad
.. h:div:: feature-table doc-aside
+------------------+------------+-----------+
| | Accounting | Inventory |
+==================+============+===========+
| Purchase Order | :meh:`/` | :meh:`/` |
+------------------+------------+-----------+
| Receipt | :meh:`/` | :good:`✓` |
+------------------+------------+-----------+
| Vendor Bill | :good:`✓` | :meh:`/` |
+------------------+------------+-----------+
| Sales Order | :meh:`/` | :meh:`/` |
+------------------+------------+-----------+
| Customer Invoice | :good:`✓` | :meh:`/` |
+------------------+------------+-----------+
| Delivery | :meh:`/` | :good:`✓` |
+------------------+------------+-----------+
| Closing Entry | :good:`✓` | :meh:`/` |
+------------------+------------+-----------+
Accounting Methods
==================
There are two accounting practices on how to maintain your accounts:
**Periodic:** Post vendor bills as expenses by nature, and update stock valuation in the closing
entry by reducing expenses (stock variation). This is the best practice in Europe.
**Perpetual:** Post vendor bills as assets (stock valuation), report expenses when goods are sold
(cost of goods sold). This is the best practice in countries that follow Anglo-Saxon accounting,
like the USA and India.
.. role:: yellow
.. role:: green
.. role:: blue
.. role:: darkblue
.. role:: purple
.. role:: washed
.. role:: washed-green
:class: washed green
.. role:: washed-darkblue
:class: washed darkblue
.. role:: washed-purple
:class: washed purple
* :purple:`Stock Account` on the product's category
* :yellow:`Stock Variation` on the stock account
* :blue:`Expense/Cost of Goods Sold` on the product/category
* :green:`Inventory Adjustment` on the Inventory Loss location
(optional, recommended for Anglo-Saxon accounting)
* :darkblue:`Expense` on the stock account
(for perpetual Continental accounting only)
.. h:div:: doc-aside
.. list-table::
:stub-columns: 1
:header-rows: 1
:class: config-table
* -
- EU Periodic
- EU Perpetual
- US Periodic
- US Perpetual
* - ADJUSTMENT
-
- :purple:`Stock`
-
- :purple:`Stock`
* -
-
- :green:`LOSS`
-
- :green:`Shrinkage`
* -
-
-
-
-
* - BILL
- :blue:`Expense`
- :purple:`Stock`
- :blue:`COGS`
- :purple:`Stock`
* -
- :washed:`Payable`
- :washed:`Payable`
- :washed:`Payable`
- :washed:`Payable`
* -
-
-
-
-
* - INVOICE
-
- :blue:`Expense`
-
- :blue:`COGS`
* -
-
- :purple:`Stock`
-
- :purple:`Stock`
* -
- :washed:`Income`
- :washed:`Income`
- :washed:`Income`
- :washed:`Income`
* -
- :washed:`Receivable`
- :washed:`Receivable`
- :washed:`Receivable`
- :washed:`Receivable`
* -
-
-
-
-
* - Closing
- :purple:`Stock`
- :washed-purple:`Stock`
- :purple:`Stock`
- :washed-purple:`Stock`
* - [1]
- :yellow:`Variation`
- :washed-darkblue:`Expense`
- :yellow:`Variation`
- :yellow:`Variation`
* - [2]
- :washed-green:`LOSS`
-
- :washed-green:`Shrinkage`
-
* - [3]
-
- :yellow:`Variation`
-
-
* -
-
- :darkblue:`Expense`
-
-
1. Inventory valuation - Accounting valuation
2. Inventory valuation lost,
only if an account is set on the loss location
3. Accounting valuation end of period -
Valuation beginning of period
.. _accounting-entries:
Accounting Entries
==================
.. h:div:: accounting-entries doc-aside
.. placeholder
.. _journal-entries:
Journal Entries Configuration
=============================
.. h:div:: journal-entries doc-aside
.. placeholder
Reporting
=========
In Inventory
------------
Open :menuselection:`Inventory -- > Reporting --> Stock` to view your current inventory level and
valuation for each product, or to review historical data as of a previous date.
.. h:div:: doc-aside
.. image:: cheat_sheet/valuation-stock.png
Unit cost
~~~~~~~~~
To check a product's existing unit price updates and their origins, click on the product's
:guilabel:`Unit Cost`. In :abbr:`AVCO (average cost)` this allows you to understand how the
currently used value was calculated.
.. h:div:: doc-aside
.. image:: cheat_sheet/unit-cost.png
Total value
~~~~~~~~~~~
To see all incoming quantities for which you still have a remaining quantity and the value used for
their valuation, click on a product's :guilabel:`Total Value`.
- In AVCO or standard cost, the used value is always the current average unit cost.
- In FIFO, remaining units from each previous incoming move retain their own individual valuation.
In FIFO or AVCO, remaining quantities from a previous incoming move can have their value adjusted if
necessary: Select the incoming moves to be adjusted, click :icon:`fa-cog` :guilabel:`Actions`, and
then click :guilabel:`Adjust Valuation`. Enter the new :guilabel:`Value` and, optionally, a
:guilabel:`Description`.
.. h:div:: doc-aside
.. image:: cheat_sheet/total-value.png
In Accounting
-------------
To view the difference between the accounting stock value and the current inventory value recorded
thanks to the incoming moves with a remaining quantity, go to :menuselection:`Accounting --> Review
--> Inventory Valuation`.
To generate a new accounting entry to review and post, click :guilabel:`Generate Entry`.
To view a list of sales and purchase orders for which accrual entries should be encoded, go to
:menuselection:`Accounting --> Review` and select the relevant menu item (:guilabel:`Invoices not
received`, :guilabel:`Invoices to be issued`, :guilabel:`Prepaid expenses`, or :guilabel:`Deferred
Revenues`).
With Anglo-Saxon perpetual accounting, this will also help to distribute recorded inventory
variations to accounts such as Bills to Receive/:abbr:`GRNI (goods received not invoiced)` or
:abbr:`COGS (cost of goods sold)` as shown in the :ref:`Accounting Entries <accounting-entries>`
and :ref:`Journal Entries Configuration <journal-entries>` sections.
.. h:div:: doc-aside
.. image:: cheat_sheet/valuation-accounting.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -69,9 +69,7 @@ When creating new vendor bills, this product can be added as an invoice line as
.. important::
To apply a landed cost on a vendor bill, products in the original |PO| **must** belong to a
*Product Category* with a *Costing Method* of either |AVCO| or |FIFO|, and the valuation method
can be :doc:`manual <using_inventory_valuation>` or :doc:`automatic
<inventory_valuation_config>`.
*Product Category* with a *Costing Method* of either |AVCO| or |FIFO|.
Create purchase order
=====================

View File

@@ -2,19 +2,19 @@
Valuation by lots/serial numbers
================================
Track :doc:`inventory valuation <using_inventory_valuation>` by :doc:`lots or serial numbers
<../../product_management/product_tracking>` to:
Track :doc:`inventory valuation <cheat_sheet>` by :doc:`lots or serial numbers
<../product_management/product_tracking>` to:
#. :ref:`Compare and differentiate purchasing cost <inventory/product_management/view-valuation>`,
based on lot or serial numbers.
#. Track the actual cost of manufactured products, based on the real cost of each tracked component
used.
#. Depreciate specific lot or serial numbers when they :doc:`sit in stock for too long
<../../warehouses_storage/reporting/aging>`.
<../warehouses_storage/reporting/aging>`.
.. important::
Please read this :doc:`introduction to inventory valuation <inventory_valuation_config>` before
setting up valuation by lot/serial numbers.
Please read this :doc:`introduction to inventory valuation <cheat_sheet>` before setting up
valuation by lot/serial numbers.
Configuration
=============
@@ -25,16 +25,13 @@ feature <inventory/product_management/enable-lot-serial>`. After that, go to
a new product, by clicking :guilabel:`New`.
On the product form, in the :guilabel:`Category` field, choose a product category. Ensure the
product category's :ref:`Costing Method <inventory/warehouses_storage/costing_methods>` is set to
*First In First Out (FIFO)* or *Average Cost (AVCO)*.
product category's :guilabel:`Costing Method` is set to *First In First Out (FIFO)* or
*Average Cost (AVCO)*.
.. tip::
To check the costing method set on the product category, hover over the :guilabel:`Category`
field, and click the :icon:`oi-arrow-right` :guilabel:`(Internal Link)` icon.
.. seealso::
:ref:`Costing methods <inventory/warehouses_storage/costing_methods>`
Next, activate the product to be tracked by lots or serial numbers by ticking the :guilabel:`Track
Inventory` checkbox. Then, click the adjacent field that appears, and choose either :guilabel:`By
Lots` or :guilabel:`By Unique Serial Number` from the resulting drop-down menu.
@@ -109,7 +106,7 @@ Create new lot/serial number
----------------------------
Creating a new lot/serial number through an :doc:`inventory adjustment
<../../warehouses_storage/inventory_management/count_products>` assigns the same value as the cost
<../warehouses_storage/inventory_management/count_products>` assigns the same value as the cost
on the product form.
To make an inventory adjustment, and assign a lot number, go to :menuselection:`Inventory app -->
@@ -187,7 +184,7 @@ On the resulting :guilabel:`Stock Valuation` report, click the search bar, and i
.. tip::
Click the :icon:`fa-plus` :guilabel:`(plus)` icon to the right of a collapsed lot number line to
:ref:`manually modify the cost <inventory/product_management/update-unit-price>`.
manually modify the cost.
This is useful for adjusting individual lot prices when a purchase order or bill includes
multiple lots/serial numbers, as initial prices are identical upon reception.

View File

@@ -9,4 +9,3 @@ Product management
product_management/configure
product_management/product_tracking
product_management/inventory_valuation

View File

@@ -164,7 +164,7 @@ documents.
* - :doc:`Use inventory adjustments <../../warehouses_storage/inventory_management/count_products>`
- Yes
- No
* - :doc:`Use inventory valuation <../inventory_valuation/using_inventory_valuation>`
* - :doc:`Use inventory valuation <../../inventory_valuation/cheat_sheet>`
- Yes
- No
* - :ref:`Create transfer <inventory/product_management/transfer-store>`
@@ -344,6 +344,6 @@ Inventory reports
on that product's specific moves history.
- :guilabel:`Moves Analysis`: This report provides a pivot table view of inventory transfers by
operation type.
- :ref:`Stock Valuation report <inventory/management/reporting/valuation-report>`: A detailed record
of the monetary value of all tracked inventory.
- :ref:`Stock Valuation report <inventory/product_management/valuation-report>`: A
detailed record of the monetary value of all tracked inventory.

View File

@@ -1,488 +0,0 @@
=============================
Automatic inventory valuation
=============================
.. |right arrow| replace:: :icon:`fa-arrow-right` :guilabel:`(right arrow)`
All of a company's stock on-hand contributes to the valuation of its inventory. That value should
be reflected in the company's accounting records to accurately show the value of the company and
all of its assets.
By default, Odoo uses a periodic inventory valuation (also known as manual inventory valuation).
This method implies that the accounting team manually posts journal entries, based on the physical
inventory of the company, and warehouse employees take the time to count the stock. In Odoo, each
product category reflects this, with the :guilabel:`Costing Method` set to :guilabel:`Standard
Price`, and the :guilabel:`Inventory Valuation` (not visible by default) set to :guilabel:`Manual`.
.. image:: inventory_valuation_config/inventory-valuation-fields.png
:align: center
:alt: The Costing Method field is located on the Product Categories form.
Alternatively, perpetual (automatic) inventory valuation creates real-time *journal entries* in the
*Accounting* app whenever stock enters or leaves the company's warehouse.
This document is focused on the proper setup of automatic inventory valuation, which is an
integrated valuation method that ensures journal entries in the *Accounting* app match stock
valuation updates in the *Inventory* app. For an introduction of inventory valuation in Odoo, refer
to the :doc:`using_inventory_valuation` documentation.
.. warning::
Switching from manual to automatic inventory valuation may cause discrepancies between stock
valuation and accounting journals.
One `successful strategy <https://www.odoo.com/r/Kvfg>`_ for switching to automated valuation:
#. Clear existing stock (possibly with an :doc:`inventory adjustment
<../../warehouses_storage/inventory_management/count_products>`)
#. Change the inventory valuation method to *Automatic*
#. Return the existing stock, with the original monetary value (using an inventory adjustment)
Once the existing stock is recovered, the Odoo *Accounting* app automatically generates the
journal entries to corresponding stock valuation records.
Configuration
=============
To properly set up automatic inventory valuation, follow these steps in Odoo:
#. :ref:`Install Accounting app and enable specific settings
<inventory/warehouses_storage/accounting-setup>`
#. :ref:`Set Automatic inventory valuation on product categories
<inventory/warehouses_storage/valuation-on-product-category>`
#. :ref:`Set costing method <inventory/warehouses_storage/costing_methods>`
.. _inventory/warehouses_storage/accounting-setup:
Accounting setup
----------------
To use automatic inventory valuation, install the *Accounting* app. Next, go to
:menuselection:`Accounting app --> Configuration --> Settings`, and in the :guilabel:`Stock
Valuation` section, tick the :guilabel:`Automatic Accounting` checkbox. Then, click
:guilabel:`Save`.
.. note::
Enabling :guilabel:`Automatic Accounting` shows the previously invisible *Inventory Valuation*
field on a product category.
.. image:: inventory_valuation_config/auto-accounting.png
:align: center
:alt: Automatic Accounting feature in Stock Valuation section of Settings page.
Refer to the :ref:`Expense <inventory/warehouses_storage/expense-account>` and :ref:`Stock
input/output <inventory/warehouses_storage/stock-account>` sections of documentation for details on
configuring the accounting journals shown.
.. _inventory/warehouses_storage/valuation-on-product-category:
Product category setup
----------------------
After :ref:`enabling inventory valuation <inventory/warehouses_storage/accounting-setup>`, the next
step is to set the product category to use automatic inventory valuation.
Go to :menuselection:`Inventory app --> Configuration --> Product Categories`, and select the
desired product category. In the :guilabel:`Inventory Valuation` section, set the
:guilabel:`Inventory Valuation` field to :guilabel:`Automated`. Repeat this step for every product
category intending to use automatic inventory valuation.
.. note::
After enabling automatic accounting, each new stock move layer (SVL), that is created during
inventory valuation updates, generates a journal entry.
.. image:: inventory_valuation_config/automated-inventory-valuation.png
:align: center
:alt: Inventory Valuation field on the product category, with its various stock accounts.
.. _inventory/warehouses_storage/costing_methods:
Costing method
==============
After :ref:`enabling inventory valuation <inventory/warehouses_storage/accounting-setup>`, the
*costing method* for calculating and recording inventory costs is defined on the product category in
Odoo.
Go to :menuselection:`Inventory app --> Configuration --> Product Categories` and select the desired
product category. In the :guilabel:`Inventory Valuation` section, select the appropriate
:guilabel:`Costing Method`:
.. tabs::
.. tab:: Standard Price
The default costing method in Odoo. The cost of the product is manually defined on the product
form, and this cost is used to compute the valuation. Even if the purchase price on a purchase
order differs, the valuation is the cost defined on the product form.
.. list-table::
:header-rows: 1
:stub-columns: 1
* - Operation
- Unit Cost
- Qty On Hand
- Incoming Value
- Inventory Value
* -
- $10
- 0
-
- $0
* - Receive 8 products for $10/unit
- $10
- 8
- 8 * $10
- $80
* - Receive 4 products for $16/unit
- $10
- 12
- 4 * $10
- $120
* - Deliver 10 products
- $10
- 2
- -10 * $10
- $20
* - Receive 2 products for $9/unit
- $10
- 4
- 2 * $10
- $40
.. tab:: Average Cost (AVCO)
Calculates the valuation of a product based on the average cost of that product, divided by
the total number of available stock on-hand. With this costing method, inventory valuation is
*dynamic*, and constantly adjusts based on the purchase price of products.
.. list-table::
:header-rows: 1
:stub-columns: 1
* - Operation
- Unit Cost
- Qty On Hand
- Incoming Value
- Inventory Value
* -
- $0
- 0
-
- $0
* - Receive 8 products for $10/unit
- $10
- 8
- 8 * $10
- $80
* - Receive 4 products for $16/unit
- $12
- 12
- 4 * $16
- $144
* - Deliver 10 products
- $12
- 2
- -10 * $12
- $24
* - Receive 2 products for $6/unit
- $9
- 4
- 2 * $6
- $36
How are unit cost and inventory value calculated at each step?
- When receiving four products for $16 each:
- Inventory value is calculated by adding the previous inventory value with the incoming
value: :math:`$80 + (4 * $16) = $144`.
- Unit cost is calculated by dividing the inventory value by the quantity on-hand:
:math:`$144 / 12 = $12`.
- When delivering ten products, the average unit cost is used to calculate the inventory
value, regardless of the purchase price of the product. Therefore, inventory value is
:math:`$144 + (-10 * $12) = $24`.
- Receive two products for $6 each:
- Inventory value: :math:`$24 + (2 * $6) = $36`
- Unit cost: :math:`$36 / 4 = $9`
.. note::
When choosing :guilabel:`Average Cost (AVCO)` as the :guilabel:`Costing Method`, changing
the numerical value in the *Cost* field for products in the respective product category
creates a new record in the *Inventory Valuation* report to adjust the value of the
product. The *Cost* amount is then automatically updated, based on the average purchase
price of both the inventory on-hand and the costs accumulated from validated purchase
orders.
.. tab:: First In First Out (FIFO)
Tracks the costs of incoming and outgoing items in real-time, and uses the real price of the
products to change the valuation. The oldest purchase price is used as the cost for the next
good sold, until an entire lot of that product is sold. When the next inventory lot moves up
in the queue, an updated product cost is used based on the valuation of that specific lot.
This method is arguably the most accurate inventory valuation method for a variety of reasons,
but it is highly sensitive to input data and human error.
.. list-table::
:header-rows: 1
:stub-columns: 1
* - Operation
- Unit Cost
- Qty On Hand
- Incoming Value
- Inventory Value
* -
- $0
- 0
-
- $0
* - Receive 8 products for $10/unit
- $10
- 8
- 8 * $10
- $80
* - Receive 4 products for $16/unit
- $12
- 12
- 4 * $16
- $144
* - Deliver 10 products
- $16
- 2
- | -8 * $10
| -2 * $16
- $32
* - Receive 2 products for $6/unit
- $11
- 4
- 2 * $6
- $44
How are unit cost and inventory value calculated at each step?
- When receiving four products for $16 each:
- Inventory value is calculated by adding the previous inventory value to the incoming
value: :math:`$80 + (4 * $16) = $144`.
- Unit cost is calculated by dividing the inventory value by the quantity on-hand:
:math:`$144 / 12 = $12`.
- When delivering ten products, eight units were purchased for $10, and two units were
purchased for $16.
- First, the incoming value is calculated by multiplying the on-hand quantity by the
purchased price: :math:`(-8 * $10) + (-2 * $16) = -112`.
- The inventory value is calculated by subtracting the incoming value from the previous
inventory value: :math:`$144 - $112 = $32`.
- Unit cost is calculated by dividing the inventory value by the remaining quantity:
:math:`$32 / 2 = $16`.
- When receiving two products for $6, inventory value is :math:`$32 + $12 = $44`. Unit cost is
:math:`$44 / 4 = $11`.
.. warning::
Changing the costing method greatly impacts inventory valuation. It is highly recommended to
consult an accountant first before making any adjustments here.
.. seealso::
:doc:`using_inventory_valuation`
When the :guilabel:`Costing Method` is changed, products already in stock that were using the
:guilabel:`Standard` costing method **do not** change value; rather, the existing units keep their
value, and any product moves from then on affect the average cost, and the cost of the product will
change. If the value in the :guilabel:`Cost` field on a product form is changed manually, Odoo
generates a corresponding record in the *Inventory Valuation* report.
.. note::
It is possible to use different valuation settings for different product categories.
.. _inventory/warehouses_storage/accounting-types:
Types of accounting
===================
With automated inventory valuation set up, the generated journal entries depend on the chosen
accounting mode: *Continental* or *Anglo-Saxon*.
.. tip::
Verify the accounting mode by activating the :ref:`developer-mode`, and navigating to
:menuselection:`Accounting app --> Configuration --> Settings`.
Then, in the :guilabel:`Search...` bar, look for `Anglo-Saxon Accounting`, to see if the feature
is enabled. If it is **not** enabled, *Continental* accounting mode is in use.
.. image:: inventory_valuation_config/anglo-saxon.png
:align: center
:alt: Show the Anglo-Saxon accounting mode feature.
In *Anglo-Saxon* accounting, the costs of goods sold (COGS) are reported when products are sold or
delivered. This means the cost of a good is only recorded as an expense when a customer is invoiced
for a product.
So, for **manual** valuation method, set the *Expense Account* to *Stock Valuation* for the current
asset type; for **automatic** valuation method, set the *Expense Account* to an *Expenses* or a
*Cost of Revenue* type (e.g. *Cost of Production*, *Cost of Goods Sold*, etc.).
In *Continental* accounting, the cost of a good is reported as soon as a product is received into
stock. Because of this, the *Expense Account* can be set to **either** *Expenses* or a *Cost of
Revenue* type, however, it is more commonly set to an *Expenses* account.
Refer to the :ref:`Expense <inventory/warehouses_storage/expense-account>` and :ref:`Stock
input/output <inventory/warehouses_storage/stock-account>` sections for details on configuring each
account type.
.. _inventory/warehouses_storage/expense-account:
Expense account
---------------
To configure the *expense account*, which is used in both manual and automatic inventory valuation,
go to the :guilabel:`Account Properties` section of the intended product category
(:menuselection:`Inventory app --> Configuration --> Product Categories`). Then, choose an existing
account from the :guilabel:`Expense Account` drop-down menu.
To ensure the chosen account is the correct :guilabel:`Type,` click the |right arrow| icon to the
right of the account. Then, set the account type based on the information below.
.. tabs::
.. group-tab:: Anglo-Saxon
.. tabs::
.. group-tab:: Automated
In Anglo-Saxon accounting for automated inventory valuation, set the :guilabel:`Expense
Account` to the `Expenses` account. Then, click the |right arrow| icon to the right of
the account.
In the pop-up window, choose :guilabel:`Expenses` or :guilabel:`Cost of Revenue` from
the :guilabel:`Type` drop-down menu.
.. image:: inventory_valuation_config/external-link.png
:align: center
:alt: Show **Expense Account** field, and external link icon.
.. group-tab:: Manual
To configure the :guilabel:`Expense Account`, choose :guilabel:`Stock Valuation` from
the field's drop-down menu. Verify the account's type by clicking the |right arrow|
icon, and then ensure the :guilabel:`Type` is :guilabel:`Current Assets`.
.. image:: inventory_valuation_config/manual-anglo-saxon-expense.png
:align: center
:alt: Show the **Expense Account** field.
.. group-tab:: Continental
.. tabs::
.. group-tab:: Automated
Set the :guilabel:`Expense Account` to the :guilabel:`Expenses` or :guilabel:`Cost of
Revenue` account type.
.. group-tab:: Manual
Set the :guilabel:`Expense Account` to the :guilabel:`Expenses` or :guilabel:`Cost of
Revenue` account type.
.. _inventory/warehouses_storage/stock-account:
Stock input/output (automated only)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To configure the :guilabel:`Stock Input Account` and :guilabel:`Stock Output Account`, go to
:menuselection:`Inventory app --> Configuration --> Product Categories` and select the desired
product category.
In the :guilabel:`Inventory Valuation` field, select :guilabel:`Automated`. Doing so makes the
:guilabel:`Account Stock Properties` section appear. These accounts are defined as follows:
- :guilabel:`Stock Valuation Account`: when automated inventory valuation is enabled on a product,
this account will hold the current value of the products.
- :guilabel:`Stock Journal`: accounting journal where entries are automatically posted when a
product's inventory valuation changes.
- :guilabel:`Stock Input Account`: counterpart journal items for all incoming stock moves will be
posted in this account, unless there is a specific valuation account set on the source location.
This is the default value for all products in a given category, and can also be set directly on
each product.
- :guilabel:`Stock Output Account`: counterpart journal items for all outgoing stock moves will be
posted in this account, unless there is a specific valuation account set on the destination
location. This is the default value for all products in a given category, and can also be set
directly on each product.
.. tabs::
.. group-tab:: Anglo-Saxon
In Anglo-Saxon accounting, the :guilabel:`Stock Input Account` and :guilabel:`Stock Output
Account` are set to *different* :guilabel:`Current Assets` accounts. This way, delivering
products and invoicing the customer balance the *Stock Output* account, while receiving
products and billing vendors balance the *Stock Input* account.
To modify the account type, go to the click the |right arrow| icon to the right of the stock
input/output account. In the pop-up window, choose :guilabel:`Current Assets` from the
:guilabel:`Type` drop-down menu.
.. figure:: inventory_valuation_config/account-type.png
:align: center
:alt: Display account setup page, highlighting the **Type** field.
The *Stock Input* account is set to `Stock Interim (Received)`, a *Current Asset* account
type.
.. group-tab:: Continental
In Continental accounting, the :guilabel:`Stock Input Account` and :guilabel:`Stock Output
Account` are set to **the same** :guilabel:`Current Assets` account. That way, one account can
be balanced when items are bought and sold.
.. example::
The stock input and output accounts are both set to `Stock Interim (Received)`, a
:guilabel:`Current Assets` account type. They can also be set to the `Stock Interim
(Delivered)`, as long as the input and output accounts are assigned to the **same**
account.
.. image:: inventory_valuation_config/continental-stock-account.png
:align: center
:alt: Show the Stock Input and Output accounts.
Inventory valuation reporting
=============================
To start, go to :menuselection:`Accounting app --> Reporting --> Balance Sheet`. Click the
:guilabel:`Current Assets` line item to unfold the drop-down menu, and look for the nested
:guilabel:`Stock Valuation`, :guilabel:`Stock Interim (Received)`, and :guilabel:`Stock Interim
(Delivered)` lines.
.. tip::
At the top of the dashboard, click the :guilabel:`As of [date]` button to display accounting
records up to a specified date.
.. seealso::
- :ref:`Stock accounts and what they do <inventory/warehouses_storage/stock-account>`
- :doc:`../../../../finance/accounting/get_started/cheat_sheet`
.. image:: inventory_valuation_config/stock-balance-sheet.png
:align: center
:alt: See the full inventory valuation breakdown in Odoo Accounting app.
Access more specific information by clicking the :icon:`fa-ellipsis-v` :guilabel:`(ellipsis)` icon
to the right of the desired journal. Select :guilabel:`General Ledger` to see a list of all of the
journal entries, where each line item's :icon:`fa-ellipsis-v` :guilabel:`(ellipsis)` icon can be
clicked to reveal the :guilabel:`View Journal Entry` option to open the individualized journal
entry.
Additionally, annotations to the :guilabel:`Balance Sheet` can be added by choosing
:guilabel:`Annotate`, filling in the text box, and clicking :guilabel:`Save`.
.. image:: inventory_valuation_config/journals.png
:align: center
:alt: Show Stock Valuation journals in a list.

View File

@@ -1,178 +0,0 @@
=========================
Using inventory valuation
=========================
.. _inventory/reporting/using_inventory_val:
*Inventory valuation* is a quintessential accounting procedure that calculates the value of on-hand
stock. Once determined, the inventory valuation amount is then incorporated into a company's overall
value.
In Odoo, this process can be conducted manually— by warehouse employees physically counting the
products— or automatically through the database.
Automatic inventory valuation
=============================
To use Odoo to automatically generate a trail of inventory valuation entries, first navigate to the
:menuselection:`Product Categories` list by going to :menuselection:`Inventory app --> Configuration
--> Product Categories` and select the desired product category. On the form, set the
:guilabel:`Inventory Valuation` as :guilabel:`Automated` and the :guilabel:`Costing Method` to any
of the three options.
.. seealso::
:doc:`Set up inventory valuation <inventory_valuation_config>`
In order to understand how moving products in and out of stock affects the company's overall value,
consider the following product and stock moves scenario below.
Receive a product
-----------------
To track the value of incoming products, such as a simple *table*, configure the product category on
the the product itself. To get there, navigate to :menuselection:`Inventory app --> Products -->
Products` and click the desired product. On the product form, click the :guilabel:`➡️ (right arrow)`
icon beside the :guilabel:`Product Category` field, which opens an internal link to edit the product
category. Next, set the :guilabel:`Costing Method` as :guilabel:`First In First Out (FIFO)` and
:guilabel:`Inventory Valuation` as :guilabel:`Automated`.
.. tip::
Alternatively access the :guilabel:`Product Categories` dashboard by navigating to
:menuselection:`Inventory app --> Configuration --> Product Categories` and select the desired
product category.
Next, assume 10 tables are purchased at a price of $10.00, each. The :abbr:`PO (Purchase Order)` for
those tables will show the subtotal of the purchase as $100, plus any additional costs or taxes.
.. image:: using_inventory_valuation/purchase-order.png
:align: center
:alt: Purchase order with 10 tables products valued at $10.00 each.
After selecting :guilabel:`Validate` on the :abbr:`PO (Purchase Order)`, the :guilabel:`Valuation`
smart button is enabled. Clicking on this button displays a report showing how the inventory
valuation for the table was affected by this purchase.
.. important::
:ref:`Developer mode <developer-mode>` **must** be turned on to see the :guilabel:`Valuation`
smart button.
.. tip::
The :doc:`consignment <../../shipping_receiving/daily_operations/owned_stock>` feature allows
ownership to items in stock. Thus, products owned by other companies are not accounted for in the
host company's inventory valuation.
.. image:: using_inventory_valuation/valuation-smart-button.png
:align: center
:alt: See Valuation smart button on a receipt, with Developer mode enabled.
For a comprehensive dashboard that includes the inventory valuation of all product shipments,
inventory adjustments, and warehouse operations, refer to the :ref:`stock valuation report
<inventory/management/reporting/valuation-report>`.
Deliver a product
-----------------
In the same logic, when a table is shipped to a customer and leaves the warehouse, the stock
valuation decreases. The :guilabel:`Valuation` smart button on the :abbr:`DO (Delivery Order)`,
likewise, displays the stock valuation record as it does on a :abbr:`PO (Purchase Order)`.
.. image:: using_inventory_valuation/decreased-stock-valuation.png
:align: center
:alt: Decreased stock valuation after a product is shipped.
.. _inventory/management/reporting/valuation-report:
Inventory valuation report
==========================
To view the current value of all products in the warehouse, first turn on :ref:`Developer mode
<developer-mode>` and navigate to :menuselection:`Inventory app --> Reporting --> Valuation`. The
:guilabel:`Stock Valuation` dashboard displays detailed records of products with the
:guilabel:`Date`, :guilabel:`Quantity`, :guilabel:`Unit Value`, and :guilabel:`Total Value` of the
inventory.
.. important::
:ref:`Developer mode <developer-mode>` **must** be enabled to see the :guilabel:`Valuation`
option under :guilabel:`Reporting`.
.. image:: using_inventory_valuation/inventory-valuation-products.png
:align: center
:alt: Inventory valuation report showing multiple products.
The :guilabel:`Valuation At Date` button, located in the top-left corner of the :guilabel:`Stock
Valuation` page, reveals a pop-up window. In this pop-up, the inventory valuation of products
available during a prior specified date can be seen and selected.
.. tip::
View a detailed record of a product's inventory value, stock move, and on-hand stock by selecting
the teal :guilabel:`➡️ (right arrow)` button to the right of the :guilabel:`Reference` column
value.
.. _inventory/product_management/update-unit-price:
Update product unit price
-------------------------
For any company: lead times, supply chain failures, and other risk factors can contribute to
invisible costs. Although Odoo attempts to accurately represent the stock value, *manual valuation*
serves as an additional tool to update the unit price of products.
.. important::
Manual valuation is intended for products that can be purchased and received for a cost greater
than 0, or have product categories set with :guilabel:`Costing Method` set as either
:guilabel:`Average Cost (AVCO)` or :guilabel:`First In First Out (FIFO)`.
.. image:: using_inventory_valuation/add-manual-valuation.png
:align: center
:alt: Add manual valuation of stock value to a product.
Create manual valuation entries on the :guilabel:`Stock Valuation` dashboard by first navigating to
:menuselection:`Inventory app --> Reporting --> Valuation`. Next, to enable the *product
revaluation* feature, select :menuselection:`Group by --> Product` to organize all the records by
product. Click on the gray :guilabel:`▶️ (drop-down triangle)` icon to reveal stock valuation line
items below, as well as a teal :guilabel:` (plus)` button on the right.
Click the teal :guilabel:`+ (plus)` button to open up the :guilabel:`Product Revaluation` form.
Here, the inventory valuation for a product can be recalculated, by increasing or decreasing the
unit price of each product.
.. note::
The :guilabel:`▶️ (drop-down triangle)` and :guilabel:` (plus)` buttons are only visible after
grouping entries by product.
.. image:: using_inventory_valuation/product-revaluation.png
:align: center
:alt: Product revaluation form adding a value of $1.00 with the reason being inflation.
Inventory valuation journal entries
-----------------------------------
In Odoo, automatic inventory valuation records are also recorded in the :menuselection:`Accounting
app --> Accounting --> Journal Entries` dashboard. On this comprehensive list of accounting entries,
inventory valuation records are identified by checking values in the :guilabel:`Journal` column, or
looking for the :guilabel:`Reference` column value which matches the warehouse operation reference
(e.g. `WH/IN/00014` for receipts).
Clicking on an inventory valuation journal entry opens a *double-entry accounting* record. These
records are generated by Odoo to track the change of value in inventory valuation as products are
moved in and out of the warehouse.
.. example::
To view the inventory valuation of 10 *tables*, costing $10.00 each, upon reception from the
vendor, go to the :menuselection:`Journal Entries` page found in :menuselection:`Accounting app
--> Accounting --> Journal Entries`. Here, click the journal line where the :guilabel:`Reference`
column value matches the reference on the receipt, `WH/IN/00014`.
.. image:: using_inventory_valuation/stock-valuation-product.png
:align: center
:alt: Stock valuation page depicting the products within a shipment.
`Stock interim` is a holding account for money intended to pay vendors for the product. The
`stock valuation` account stores the value of all on-hand stock.
.. image:: using_inventory_valuation/inventory-valuation-entry.png
:align: center
:alt: Accounting entry for the inventory valuation of 10 tables.
.. seealso::
`Odoo Tutorial: Inventory Valuation <https://www.odoo.com/slides/slide/2795/share>`_

View File

@@ -64,8 +64,7 @@ field, there are:
View locations should **not** contain products, but it is possible to move them there.
- :guilabel:`Internal Location`: storage locations within the warehouse. Items stored in these
locations are accounted for in :doc:`inventory valuation
<../product_management/inventory_valuation/using_inventory_valuation>`.
locations are accounted for in :doc:`inventory valuation <../inventory_valuation/cheat_sheet>`.
- :guilabel:`Customer Location`: where sold products are tracked; items here are no longer in stock.
@@ -110,10 +109,9 @@ Odoo databases include preconfigured view locations to organize the hierarchy of
provide helpful context, and distinguish between internal and external locations.
- *Physical locations* group internal locations—such as secondary warehouses and subcontractor
sites. Because :doc:`inventory valuation
<../product_management/inventory_valuation/inventory_valuation_config>` changes only when goods
move from internal to external locations, Odoo uses physical locations to track stock that is
off-site or in transit without affecting valuation.
sites. Because :doc:`inventory valuation <../inventory_valuation/cheat_sheet>` changes only when
goods move from internal to external locations, Odoo uses physical locations to track stock that
is off-site or in transit without affecting valuation.
.. _inventory/warehouses_storage/interwarehouse-transit:

View File

@@ -422,7 +422,7 @@ Example where visibility days is triggered
------------------------------------------
A product shipped from Asia has a combined vendor lead time of 30 days and a shipping cost of $100
(including :doc:`landed costs <../../product_management/inventory_valuation/landed_costs>` and
(including :doc:`landed costs <../../inventory_valuation/landed_costs>` and
tariffs).
- November 4: Current date. The forecasted date is December 4 (30 days later).

View File

@@ -11,8 +11,8 @@ assigning a monetary value to account for inventory is known as *stock valuation
This value is often reported for accounting purposes. For instance, an insurance company may want to
know the value of goods stored in a warehouse, in the event of a flood or fire.
:doc:`Stock valuation <../../product_management/inventory_valuation/using_inventory_valuation>`
typically utilizes one of two accounting systems:
:doc:`Stock valuation <../../inventory_valuation/cheat_sheet>` typically utilizes one of two
accounting systems:
- **Perpetual**: The inventory is constantly (perpetually) being updated, and the value is
constantly changing.
@@ -21,11 +21,10 @@ typically utilizes one of two accounting systems:
Using :ref:`tracked inventory <inventory/product_management/tracking-inventory>` in Odoo
necessitates a *perpetual* inventory accounting system because of the need to know when and where
inventory exists, and how much of it is available or forecasted. There are a few common :ref:`stock
valuation methods <inventory/warehouses_storage/costing_methods>` used in Odoo: *standard price*,
*average cost* (AVCO), and *first in, first out* (FIFO) accounting. It is important to know that the
valuation method chosen for a product impacts the calculation of several fields in the stock
valuation reports.
inventory exists, and how much of it is available or forecasted. There are a few common valuation
methods used in Odoo: *standard price*, *average cost* (AVCO), and *first in, first out* (FIFO)
accounting. It is important to know that the valuation method chosen for a product impacts the
calculation of several fields in the stock valuation reports.
Open the dashboard
==================

View File

@@ -36,8 +36,7 @@ In the report itself, the columns represent:
.. seealso::
- :ref:`Compute average cost inventory valuation per unit <inventory/avg_cost/formula>`
- :doc:`Inventory valuation methods
<../../product_management/inventory_valuation/inventory_valuation_config>`
- :doc:`Inventory valuation methods <../../inventory_valuation/cheat_sheet>`
- :guilabel:`On Hand`: current quantity of products. Click the :icon:`fa-pencil`
:guilabel:`(pencil)` icon to :doc:`modify the on-hand quantity

View File

@@ -1,3 +1,10 @@
# applications/websites
applications/websites/ecommerce/payments.rst applications/websites/ecommerce/checkout.rst
# applications/inventory_and_mrp
applications/inventory_and_mrp/inventory/product_management/inventory_valuation/landed_costs.rst applications/inventory_and_mrp/inventory/inventory_valuation/landed_costs.rst
applications/inventory_and_mrp/inventory/product_management/inventory_valuation/inventory_valuation_config.rst applications/inventory_and_mrp/inventory/inventory_valuation/cheat_sheet.rst
applications/inventory_and_mrp/inventory/product_management/inventory_valuation/using_inventory_valuation.rst applications/inventory_and_mrp/inventory/inventory_valuation/cheat_sheet.rst
applications/inventory_and_mrp/inventory/product_management/inventory_valuation/valuation_by_lots.rst applications/inventory_and_mrp/inventory/inventory_valuation/valuation_by_lots.rst

View File

@@ -106,13 +106,6 @@ label:hover,
font-style: normal;
}
.values-table tr > * {
text-align: right;
}
.values-table tr > :first-child {
text-align: left;
}
/* 3-column (thing, debit, credit) tables */
/* 2nd and 3rd th & td of each row right-aligned and 1/4th width */
.d-c-table tr > :nth-child(2),
@@ -139,21 +132,7 @@ label:hover,
background-color: #eee !important;
color: #7A436B !important;
}
.chart-of-accounts .highlight-op,
.valuation-chart .highlight-op {
background-color: #030035;
border-bottom: 1px solid #000000 !important;
}
.chart-of-accounts .highlight-op,
.valuation-chart-continental .highlight-op {
background-color: #030035;
border-bottom: 1px solid #000000 !important;
}
.chart-of-accounts .highlight-op,
.valuation-chart-anglo-saxon .highlight-op {
.chart-of-accounts .highlight-op {
background-color: #030035;
border-bottom: 1px solid #000000 !important;
}
@@ -217,17 +196,3 @@ blockquote.highlights, blockquote.highlights p{
margin-bottom: 1rem;
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;
}

128
static/css/valuation.css Normal file
View File

@@ -0,0 +1,128 @@
/* Used in valuation cheat_sheet.rst */
/* Prevent titles from wrapping upwards */
h3 { clear: both !important; }
.full-width { width: 100% !important; }
.accounting-entries, .journal-entries, .values-table {
border: 1px solid #d5d5d5;
background-color: #f8f8f8;
margin: 0px auto;
}
/* Costing methods table - see misc.js */
.alternatives-controls {
padding-bottom: 24px;
label {
display: block;
}
}
dl.alternatives > dt {
display: none;
}
dl.alternatives > dd {
display: none;
margin-left: 0;
}
.values-table {
text-align: right;
tr > th:first-of-type {
text-align: left;
}
}
/* Accounting methods table - see valuation-accounting.js */
.accounting-entries {
th, td {
padding-top: 0px !important;
padding-bottom: 0px !important;
}
thead th, tr > td {
padding-left: 48px;
}
tbody th {
font-weight: normal;
}
.parent-line { background-color: #fafafa; }
.child-line { background-color: #f0f0f0; }
}
.entries-listing {
padding: .5rem;
}
#accounting-entries-controls label,
#journaling-entries-controls label {
display: block;
}
/* Highlighting of selected options - see misc.js */
label:hover, .highlighter-list li:hover {
background-color: hsl(0, 0%, 94%);
cursor: pointer;
}
.related {
background-color: hsl(317deg 16% 90%) !important;
border: 1px solid #000000 !important;
transition: .3s;
}
.secondary {
background-color: hsl(180deg 67% 94%) !important;
transition: .3s;
}
.highlight-op {
background-color: hsl(317deg 16% 90%) !important;
transition: .3s;
}
.highlighter-target {
th {
font-weight: 400;
}
.related {
/*background-color: #eee !important;*/
color: #7A436B !important;
}
.secondary {
background-color: #eee !important;
color: #7A436B !important;
}
}
/* Static tables */
.feature-table, .config-table {
table {
/* width: unset; */
margin: auto;
white-space: nowrap;
text-align: center;
}
tbody td:first-child {
text-align: left;
}
}
.config-table {
font-size: 90%;
}
.accounting-app-paragraph:hover ~ .feature-table tr > td:first-child:has(+ td .good),
.inventory-app-paragraph:hover ~ .feature-table tr > td:first-child:has(+ td + td .good)
{ font-weight: bold; }
.feature-table {
td { width: 32px; }
td:has(.good) { background-color: #d9ead3 !important; }
td:has(.meh) { background-color: #fce5cd !important; }
td:has(.bad) { background-color: #f4cccc !important; }
}
.config-table {
th, td {
padding-top: 0px !important;
padding-bottom: 0px !important;
}
td:has(.washed) { color: var(--bs-gray); }
}
.yellow, td:has(.yellow), th:has(.yellow) { background-color: #fff2cc !important; }
.green, td:has(.green), th:has(.green) { background-color: #d9ead3 !important; }
.blue, td:has(.blue), th:has(.blue) { background-color: #cfe2f3 !important; }
.darkblue, td:has(.darkblue), th:has(.darkblue) { background-color: #6d9eeb !important; }
.purple, td:has(.purple), th:has(.purple) { background-color: #d9d2e9 !important; }

View File

@@ -1,6 +1,6 @@
/* global Immutable, React */
(function () {
// NOTE: used by cheat_sheet.rst
// NOTE: used by accounting cheat_sheet.rst
'use strict';
function highlight(primary, secondary) {

View File

@@ -1,7 +1,7 @@
/* global Immutable, React */
/* global createAtom */
(function () {
// NOTE: used by cheat_sheet.rst
// NOTE: used by accounting cheat_sheet.rst
'use strict';
var data = createAtom();

View File

@@ -2,7 +2,7 @@
/* global createAtom, findAncestor */
(function () {
'use strict';
// NOTE: cheat_sheet.rst
// NOTE: used by accounting cheat_sheet.rst
var data = createAtom();
data.addWatch('chart', function (k, m, prev, next) {

View File

@@ -6,7 +6,7 @@
});
function highlight() {
// NOTE: used by double-entry.rst
// NOTE: used by valuation cheat_sheet.rst
$('.highlighter-list').each(function () {
var $this = $(this),
$target = $($this.data('target'));
@@ -34,7 +34,7 @@
* - automatically select first control on startup
*/
function alternatives() {
// NOTE: used by double-entry.rst & valuation_methods pages
// NOTE: used by valuation cheat_sheet.rst
$('dl.alternatives').each(function (index) {
var $list = $(this),
$contents = $list.children('dd');
@@ -51,7 +51,18 @@
label.appendChild(input);
label.appendChild(document.createTextNode(' '));
label.appendChild(document.createTextNode(this.textContent));
// Hack to bold the definition since we have to strip rST formatting
const [headText, tailText] = this.textContent.split(':', 2);
if (tailText) {
const bold = document.createElement('b'),
defined = document.createTextNode(`${headText}:`);
bold.appendChild(defined);
label.appendChild(bold);
}
label.appendChild(document.createTextNode(tailText || headText));
label.normalize();
return label;
}))
@@ -65,9 +76,10 @@
})
.find('input:first').click();
});
$('.alternatives-note').insertAfter($('.alternatives-controls'));
}
function checks_handling() {
// NOTE: used by cheat_sheet.rst
// NOTE: used by accounting cheat_sheet.rst
var $section = $('.checks-handling');
if (!$section.length) { return; }

View File

@@ -1,5 +1,5 @@
(function () {
// NOTE: cheat_sheet.rst
// NOTE: used by accounting cheat_sheet.rst
document.addEventListener('DOMContentLoaded', function () {
var $rec = $('#reconciliation .reconciliation-example');
if (!$rec.length) { return; }

View File

@@ -0,0 +1,262 @@
/* global Immutable, React */
/* global createAtom */
/* global VALUATION_{STANDARDS,METHODS,JOURNALS,ENTRIES,REVIEWS} */
(function () {
'use strict';
// NOTE: used by valuation cheat_sheet.rst
const selectedMode = createAtom(['continental', 'periodic']);
const selectedOps = createAtom();
function watch (next) {
React.render(
React.createElement(Controls, { p: next }),
document.getElementById('accounting-entries-controls'));
React.render(
React.createElement(Chart, { p: next }),
document.querySelector('.accounting-entries'));
}
selectedOps.addWatch('chart', (k, m, prev, next) => watch(next));
selectedMode.addWatch('chart', (k, m, prev, next) => watch(selectedOps.deref()));
document.addEventListener('DOMContentLoaded', function () {
const chart = document.querySelector('.accounting-entries');
if (!chart) { return; }
const controls = document.createElement('div');
controls.setAttribute('id', 'accounting-entries-controls');
chart.parentNode.insertBefore(controls, chart);
selectedOps.reset(Immutable.Map({
// last-selected operation
active: null,
// set of all currently enabled operations
operations: Immutable.OrderedSet()
}));
});
function toKey(s, postfix) {
if (postfix) {
s += ' ' + postfix;
}
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
}
const Controls = React.createClass({
render: function () {
const state = this.props.p;
return React.DOM.div(
null,
React.DOM.b(null, "Choose a standard:"),
VALUATION_STANDARDS.map(function (item, index) {
return React.DOM.label(
{ key: index },
React.DOM.input({
type: 'radio',
checked: item.get('name') === selectedMode.deref()[0],
onChange: function (e) {
const newValue = item.get('name');
selectedMode.reset([newValue, newValue === 'continental' ? 'periodic' : 'perpetual']);
}
}),
' ',
item.get('text')
);
}),
React.DOM.br(),
React.DOM.b(null, "Choose an accounting method:"),
VALUATION_METHODS.map(function (item, index) {
return React.DOM.label(
{ key: index },
React.DOM.input({
type: 'radio',
checked: item.get('name') === selectedMode.deref()[1],
onChange: e => selectedMode.swap(vals => [vals[0], item.get('name')]),
}),
' ',
item.get('text')
);
}),
React.DOM.br(),
React.DOM.b(null, "Activate operations to see the impact:"),
VALUATION_ENTRIES.map(function (item, key) {
return React.DOM.label(
{
key: key,
style: { display: 'block' },
className: (key === state.get('active') ? 'highlight-op' : void 0)
},
React.DOM.input({
type: 'checkbox',
checked: state.get('operations').contains(key),
onChange: function (e) {
if (e.target.checked) {
selectedOps.swap(d => d.set('active', key)
.update('operations', ops => ops.add(key)));
} else {
selectedOps.swap(d => d.set('active', null)
.update('operations', ops => ops.remove(key)));
}
}
}),
' ',
item.get('title')
);
}),
React.DOM.br(),
"Closing",
VALUATION_REVIEWS.map(function (item, key) {
// We bold the text if any of the operations in this review is
// relevant to the currently selected operations.
const boldable = item.getIn([...selectedMode.deref(), 'operations'])
.some(function (op) {
if (!op.has('entries') && !op.has('except'))
return true;
const opset = state.get('operations').toSet();
if (opset.isSuperset(op.get('entries', []))
&& opset.intersect(op.get('except', [])).isEmpty())
return true;
});
return React.DOM.label(
{
key: key,
style: { display: 'block' },
className: (key === state.get('active') ? 'highlight-op' : void 0)
},
React.DOM.input({
type: 'checkbox',
checked: state.get('operations').contains(key),
onChange: function (e) {
if (e.target.checked) {
selectedOps.swap(d => d.set('active', key)
.update('operations', ops => ops.add(key)));
} else {
selectedOps.swap(d => d.set('active', null)
.update('operations', ops => ops.remove(key)));
}
}
}),
' ',
boldable ? React.DOM.b(null, item.get('title')) : item.get('title'),
);
}),
React.DOM.br(),
);
}
});
const Chart = React.createClass({
render: function () {
// Only used for highlighting cells.
const lastop = Immutable.Map(
this.props.p.get('active')
? (VALUATION_ENTRIES.concat(VALUATION_REVIEWS)
.getIn([this.props.p.get('active'), ...selectedMode.deref(), 'operations'], Immutable.List()))
.map(op => [VALUATION_JOURNALS.getIn([selectedMode.deref()[0], ...op.get('account'), 'code']),
op.has('credit') ? 'credit' : 'debit'])
: Immutable.Map());
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) {
// Don't highlight the cell if it's going to be empty.
const highlight = lastop.get(data.get('code')),
debit = format(data.get('debit')),
credit = format(data.get('credit'));
return React.DOM.tr(
{
key: data.get('code'),
className: data.get('level') ? 'parent-line' : 'child-line',
},
React.DOM.th(
null,
data.get('level') ? '\u2001 ' : '',
data.get('code') || '', ' ', data.get('title')
),
React.DOM.td(
{ className: React.addons.classSet({
'text-right': true,
'highlight-op': debit ? highlight === 'debit' : void 0 }) },
debit),
React.DOM.td(
{ className: React.addons.classSet({
'text-right': true,
'highlight-op': credit ? highlight === 'credit' : void 0 }) },
credit),
React.DOM.td(
{ className: 'text-right' },
((data.get('debit') || data.get('credit'))
? format(data.get('debit') - data.get('credit'), 0)
: ''),
)
);
})
)
)
);
},
accounts: function() {
const currentOperations = this.props.p.get('operations');
if (!currentOperations)
return null;
const totals = VALUATION_ENTRIES.concat(VALUATION_REVIEWS)
.filter((val, key) => currentOperations.includes(key))
.valueSeq()
.flatMap(entry => entry.getIn([...selectedMode.deref(), 'operations']))
.reduce(function (acc, op) {
// `entries' and `except' fields are explained in valuation-data.js (quod vide)
if (op.has('entries') || op.has('except')) {
const opset = currentOperations.toSet();
if (!(opset.isSuperset(op.get('entries', []))
&& opset.intersect(op.get('except', [])).isEmpty())) {
return acc;
}
}
const code = VALUATION_JOURNALS.getIn([selectedMode.deref()[0], ...op.get('account'), 'code']);
return acc
.updateIn([code, 'debit'],
d => (d || 0) + op.get('debit', 0))
.updateIn([code, 'credit'],
c => (c || 0) + op.get('credit', 0));
}, Immutable.Map());
return accounts.get(selectedMode.deref()[0]).map(account =>
account.merge(account.get('accounts')
.map(code => totals.get(code, NULL))
.reduce((acc, it) => acc.mergeWith((a, b) => a + b, it, NULL))));
}
});
const NULL = Immutable.Map({ debit: 0, credit: 0 });
const accounts = VALUATION_JOURNALS.map(method => method.toList().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; })
)
);
}));
function format(val, def) {
if (!val) { return def === undefined ? '' : def; }
if (val % 1 === 0) { return val; }
return val.toFixed(2);
}
})();

1343
static/js/valuation-data.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,176 @@
/* global Immutable, React */
/* global createAtom, findAncestor */
/* global VALUATION_{STANDARDS,METHODS,JOURNALS,ENTRIES,REVIEWS} */
(function () {
'use strict';
// NOTE: used by valuation cheat_sheet.rst
const selectedMode = createAtom()
const selectedOp = createAtom();
const entries = VALUATION_ENTRIES.concat(VALUATION_REVIEWS);
function watch (next) {
React.render(
React.createElement(Controls, { entryKey: next }),
document.getElementById('journaling-entries-controls'));
React.render(
React.createElement(FormatEntry, { entryKey: next }),
document.querySelector('.journal-entries'));
}
selectedOp.addWatch('chart', (k, m, prev, next) => watch([next, ...selectedMode.deref()]));
selectedMode.addWatch('chart', (k, m, prev, next) => watch([selectedOp.deref(), ...next]));
document.addEventListener('DOMContentLoaded', function () {
const entriesSection = findAncestor(document.querySelector('.journal-entries'), 'section');
if (!entriesSection) { return; }
const controls = document.createElement('div');
controls.setAttribute('id', 'journaling-entries-controls');
entriesSection.insertBefore(controls, entriesSection.lastElementChild);
selectedMode.reset(['continental', 'periodic']);
selectedOp.reset('initial_inventory');
});
const Controls = React.createClass({
render: function () {
const key = this.props.entryKey;
return React.DOM.div(
null,
React.DOM.b(null, "Choose a standard:"),
VALUATION_STANDARDS.map(function (item, index) {
return React.DOM.label(
{ key: index },
React.DOM.input({
type: 'radio',
checked: item.get('name') === key[1],
onChange: function (e) {
const newValue = item.get('name');
selectedMode.reset([newValue, newValue === 'continental' ? 'periodic' : 'perpetual']);
}
}),
' ',
item.get('text')
);
}),
React.DOM.br(),
React.DOM.b(null, "Choose an accounting method:"),
VALUATION_METHODS.map(function (item, index) {
return React.DOM.label(
{ key: index },
React.DOM.input({
type: 'radio',
checked: item.get('name') === key[2],
onChange: e => selectedMode.swap(vals => [vals[0], item.get('name')]),
}),
' ',
item.get('text')
);
}),
React.DOM.br(),
React.DOM.b(null, "Activate operations to see the impact:"),
VALUATION_ENTRIES.map(function (item, index) {
return React.DOM.label(
{ key: index },
React.DOM.input({
type: 'radio',
checked: index === key[0],
onChange: e => selectedOp.reset(index),
}),
' ',
item.get('title')
);
}),
React.DOM.br(),
"Closing",
VALUATION_REVIEWS.map(function (item, index) {
return React.DOM.label(
{ key: index },
React.DOM.input({
type: 'radio',
checked: index === key[0],
onChange: e => selectedOp.reset(index),
}),
' ',
item.get('title')
);
}),
React.DOM.br(),
);
}
});
const FormatEntry = React.createClass({
render: function () {
const entry = entries.getIn(this.props.entryKey);
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,
// Use `journal_operations' if it's a review. See `valuation-data.js'.
entry && entry.get('journal_operations', entry.get('operations', [])).map(this.renderRow)
)
),
React.createElement(Listing, {
heading: "Explanation",
items: entry && entry.get('explanation'),
}),
React.createElement(Listing, {
heading: "Configuration",
items: entry && entry.get('configuration'),
})
);
},
renderRow: function (entry, index) {
const standard = this.props.entryKey[1];
if (!entry) {
return React.DOM.tr(
{ key: 'spacer-' + index },
React.DOM.td({ colSpan: 3 }, "\u00A0")
);
}
const journalEntry = VALUATION_JOURNALS.getIn([standard, ...entry.get('account')]);
const title = journalEntry.get('title');
// Don't display 0 for 'General Balance for Inventory Initial Value'
const code = journalEntry.get('code') || '';
return React.DOM.tr(
{ key: index },
React.DOM.td(null, `${code} ${title}`),
React.DOM.td(null, entry.get('debit')),
React.DOM.td(null, entry.get('credit'))
);
}
});
const Listing = React.createClass({
render: function () {
if (!this.props.items || this.props.items.isEmpty()) {
return React.DOM.div();
}
const items = this.props.items;
const idx = items.indexOf(null);
if (idx !== -1) {
// console.log(items.slice(idx + 1).deref());
items = items.take(idx);
}
return React.DOM.div(
{ className: 'entries-listing' },
React.DOM.h4(null, this.props.heading, ':'),
items.map(function (item, index) {
return React.DOM.p({ key: index }, item);
})
);
}
});
}());