diff --git a/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config.rst b/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config.rst index 15dff5805..4a03119ac 100644 --- a/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config.rst +++ b/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config.rst @@ -1,424 +1,132 @@ -:code-column: -:custom-css: accounting.css -:custom-js: coa-valuation-continental.js,coa-valuation-anglo-saxon.js,misc.js - ================================= -Inventory valuation configuration +Inventory Valuation Configuration ================================= -Inventory valuation refers to how you value your stock. It’s a very -important aspect of a business as the inventory can be the biggest asset -of a company. - -Inventory valuation implies two main choices: - -- The cost method you use to value your goods (standard, fifo, avco) -- The way you record this value into your accounting books (manually or automatically) - -Those two concepts are explained in the sections below. - -Costing Methods: Standard, FIFO, AVCO -===================================== - -The costing method is defined in the product category. There are three -options available. Each of them is explained in detail below. - -.. 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 - - In **Standard Price**, any product will be valued at the cost that you defined - manually on the product form. Usually, this cost is an estimation based - on the material and labor needed to obtain the product. This cost must - be reviewed periodically. - -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 - - €12 - - 2 - - | -10*€12 - | - - €24 - * - Receive 2 Products at €6 - - €9 - - 4 - - +2*€6 - - €36 - - In **AVCO (Average Cost)**, each product has the same value and this - value is the average purchase cost of the product. With this costing method, the - cost of the product is recomputed as each receipt. - - The average cost does not change when products leave the warehouse. - -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 - - In **FIFO (First In First Out)**, the products are valued at their - purchase cost. When a product leaves the stock, that’s the “First in, - first out” rule that applies. - - Pay attention, that this is a financial FIFO. The first value “in” - is the first value “out”, no matter the storage location, warehouse - or serial number. - - FIFO is advised if you manage all your workflows into Odoo (Sales, - Purchases, Inventory). It suits any kind of users. - -Inventory Valuation: Manual or Automated -======================================== - -There are two ways to record your inventory valuation in your accounting -books. As the costing method, this is defined in your product category. -Those two methods are detailed below. - -It is important to also note that the accounting entries will depend on -your accounting mode: it can be continental or anglo-saxon. In -continental accounting, the cost of a good is taken into account as soon -as the product is received in stock. In anglo-saxon accounting, the cost -of a good is only recorded as an expense when this good is invoiced to a -final customer. In the tables below, you can easily compare those two -accounting modes. - -Usually, based on your country, the correct accounting mode will be -chosen by default. If you want to verify your accounting mode, activate -the :ref:`developer mode ` and open your accounting -settings. - -Manual Inventory Valuation --------------------------- - -In this case, goods receipts and deliveries won’t have any direct impact -on your accounting books. Periodically, you create a manual journal -entry representing the value of what you have in stock. To know that -value, go in :menuselection:`Inventory --> Reporting --> Inventory Valuation`. - -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. - -Continental Accounting -~~~~~~~~~~~~~~~~~~~~~~ - -.. 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 - -
- -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, the **Inventory** account is credited - and the **Inventory Variations** debited. - -.. raw:: html - -
- -Anglo-Saxon Accounting -~~~~~~~~~~~~~~~~~~~~~~ - -.. 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 - -
- -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. - -Then you need to break down the purchase balance into both the inventory and -the cost of goods sold using the following formula: - -Cost of goods sold (COGS) = Starting inventory value + Purchases – Closing inventory value - -To update the stock valuation in your books, record such an entry: - -.. h:div:: doc-aside - - .. rst-class:: values-table - - ===================================== ===== ====== - \ Debit Credit - ===================================== ===== ====== - Assets: Inventory (closing value) X - Expenses: Cost of Good Sold X - Expenses: Purchased Goods X - Assets: Inventory (starting value) X - ===================================== ===== ====== - -Automated Inventory Valuation ------------------------------ - -In that case, when a product enters or leaves your stock, an accounting -entry will be automatically created. This means your accounting books -are 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. - -First, you need to define the accounts that will be used for those -accounting entries. This is done on the product category. - -Continental Accounting -~~~~~~~~~~~~~~~~~~~~~~ - -.. h:div:: valuation-chart-continental doc-aside - - .. placeholder - -.. raw:: html - -
- -.. 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 - -Anglo-Saxon Accounting -~~~~~~~~~~~~~~~~~~~~~~ - -.. h:div:: valuation-chart-anglo-saxon doc-aside - - .. placeholder - -.. raw:: html - -
- -.. 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: defined on the product category as a default, or specifically - to a specific product. - - - Expenses: this is where you should set the "Cost of Goods Sold" account. - Defined on the product category as a default value, or specifically on - the product form. - - - Goods Received Not Purchased: to set as Stock Input Account in product's - internal category - - - Goods Issued Not Invoiced: to set as Stock Output Account in product's - internal category - - - Inventory: to set as Stock Valuation Account in product's internal category - - - Price Difference: to set in product's internal category or in product - form as a specific replacement value +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 posts journal entries based on the physical inventory +of the company, and that warehouse employees take the time to count the stock. In Odoo, this method +is reflected inside each product category, where the :guilabel:`Costing Method` field will be set +to `Standard Price` by default, and the :guilabel:`Inventory Valuation` field will be set to +`Manual`. + +.. image:: inventory_valuation_config/inventory-valuation-fields.png + :align: center + :alt: The Inventory Valuation fields are located on the Product Categories form. + +Alternatively, automated inventory valuation is an integrated valuation method that updates the +inventory value in real-time by creating journal entries whenever there are stock moves initiated +between locations in a company's inventory. + +.. note:: + Automated inventory valuation is a method recommended for expert accountants, given the extra + steps involved in journal entry configuration. Even after the initial setup, the method will + need to be periodically checked to ensure accuracy, and adjustments may be needed on an ongoing + basis depending on the needs and priorities of the business. + +Types of Accounting +------------------- + +Accounting entries will depend on the accounting mode: Continental or Anglo-Saxon. + +.. tip:: + Verify the accounting mode by activating the :ref:`developer-mode` + and navigating to :menuselection:`Accounting --> Configuration --> Settings`. + +In Anglo-Saxon accounting, the costs of goods sold (COGS) are reported when products are sold or +delivered. This means that the cost of a good is only recorded as an expense when a customer is +invoiced for a product. Interim Stock Accounts are used for the input and output accounts, and are +both Asset Accounts in the Balance Sheet. + +In Continental accounting, the cost of a good is reported as soon as a product is received into +stock. Additionally, a *single* Expense account is used for both input and output accounts in +the Balance Sheet. + +Costing Methods +--------------- + +Below are the three costing methods that can be used in Odoo for inventory valuation. + +- **Standard Price**: is 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 will still use the cost defined on the product + form. +- **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. +- **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, however, it's highly sensitive to input data and human error. + +.. warning:: + Changing the costing method greatly impacts inventory valuation. It's highly recommended to + consult an accountant first before making any adjustments here. + +Configure automated inventory valuation in Odoo +----------------------------------------------- + +Make changes to inventory valuation options by navigating to :menuselection:`Inventory --> +Configuration --> Product Categories`, and choose the category/categories where the automated +valuation method should apply. + +.. note:: + It is possible to use different valuation settings for different product categories. + +Under the :guilabel:`Inventory Valuation` heading are two labels: :guilabel:`Costing Method` and +:guilabel:`Inventory Valuation`. Pick the desired :guilabel:`Costing Method` using the drop-down +menu (e.g. :guilabel:`Standard`, :guilabel:`Average Cost (AVCO)`, or :guilabel:`First In First Out +(FIFO)` and switch the :guilabel:`Inventory Valuation` to :guilabel:`Automated`. + +.. seealso:: + :doc:`Using the inventory valuation ` + +.. note:: + When choosing :guilabel:`Average Cost (AVCO)` as the :guilabel:`Costing Method`, the numerical + value in the :guilabel:`Cost` field for products in the respective product category will no + longer be editable, and will appear grayed out. The :guilabel:`Cost` amount will instead + automatically update based on the average purchase price both of inventory on hand and the costs + accumulated from validated purchase orders. + +On the same screen, the :guilabel:`Account Stock Properties` fields will appear, as they are now +required fields given the change to automated inventory valuation. 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 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. + +Access reporting data generated by inventory valuation +------------------------------------------------------ + +To start, go to :menuselection:`Accounting --> Reporting --> Balance Sheet`. At the top of the +dashboard, change the :guilabel:`As of` field value to :guilabel:`Today`, and adjust the filtering +:guilabel:`Options` to :guilabel:`Unfold All` in order to see all of the latest data displayed, +all at once. + +Under the parent :guilabel:`Current Assets` line item, look for the nested :guilabel:`Stock +Valuation Account` line item, where the total valuation of all of the inventory on hand is +displayed. + +Access more specific information with the :guilabel:`Stock Valuation Account` drop-down menu, by +selecting either the :guilabel:`General Ledger` to see an itemized view of all of the journal +entries, or by selecting :guilabel:`Journal Items` to review all of the individualized journal +entries that were submitted to the account. As well, 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/stock-valuation-breakdown-in-accounting.png + :align: center + :alt: See the full inventory valuation breakdown in Odoo Accounting app. diff --git a/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config/inventory-valuation-fields.png b/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config/inventory-valuation-fields.png new file mode 100644 index 000000000..c6550b6ae Binary files /dev/null and b/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config/inventory-valuation-fields.png differ diff --git a/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config/stock-valuation-breakdown-in-accounting.png b/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config/stock-valuation-breakdown-in-accounting.png new file mode 100644 index 000000000..4741e645b Binary files /dev/null and b/content/applications/inventory_and_mrp/inventory/management/reporting/inventory_valuation_config/stock-valuation-breakdown-in-accounting.png differ diff --git a/static/js/coa-valuation-anglo-saxon.js b/static/js/coa-valuation-anglo-saxon.js deleted file mode 100644 index f67e3711f..000000000 --- a/static/js/coa-valuation-anglo-saxon.js +++ /dev/null @@ -1,276 +0,0 @@ -/* global Immutable, React */ -/* global createAtom */ -(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-sm' }, - 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-anglo-saxon')); - React.render( - React.createElement(Chart, { p: next }), - document.querySelector('.valuation-chart-anglo-saxon')); - }); - - document.addEventListener('DOMContentLoaded', function () { - var chart = document.querySelector('.valuation-chart-anglo-saxon'); - if (!chart) { return; } - - var controls = document.createElement('div'); - controls.setAttribute('id', 'chart-controls-anglo-saxon'); - chart.parentNode.insertBefore(controls, chart); - - data.reset(Immutable.Map({ - // last-selected operation - active: null, - // set of all currently enabled operations - operations: Immutable.OrderedSet() - })); - }); - - var NULL = Immutable.Map({ debit: 0, credit: 0 }); - var ASSETS = { - code: 1, - label: "Assets", - BANK: { code: 11000, label: "Cash" }, - ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" }, - STOCK: { code: 14000, label: "Inventory" }, - RAW_MATERIALS: { code: 14100, label: "Raw Materials Inventory" }, - STOCK_OUT: { code: 14600, label: "Goods Issued Not Invoiced" }, - TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" } - }; - var LIABILITIES = { - code: 2, - label: "Liabilities", - ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" }, - STOCK_IN: { code: 23000, label: "Goods Received Not Purchased" }, - TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" } - }; - var EQUITY = { - code: 3, - label: "Equity", - CAPITAL: { code: 31000, label: "Common Stock" } - }; - var REVENUE = { - code: 4, - label: "Revenue", - SALES: { code: 41000, label: "Goods" }, - }; - var EXPENSES = { - code: 5, - label: "Expenses", - GOODS_SOLD: { code: 51100, label: "Cost of Goods Sold" }, - MANUFACTURING_OVERHEAD: { code: 52000, label: "Manufacturing Overhead" }, - PRICE_DIFFERENCE: { code: 53000, label: "Price Difference" } - }; - var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) { - return Immutable.Iterable.isIndexed(v) - ? v.toList() - : v.toOrderedMap(); - }); - var accounts = categories.toSeq().flatMap(function (cat) { - return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) { - return k.toUpperCase() === k; - }).toIndexedSeq().map(function (acc) { return acc.set('level', 1) })); - }).map(function (account) { // add accounts: Seq 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 Bill (PO $50, Invoice $50)", - operations: [ - { account: LIABILITIES.STOCK_IN.code, debit: constant(50) }, - { account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) }, - { account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) }, - ] - }, { - label: "Supplier Goods Reception (PO $50, Invoice $50)", - operations: [ - { account: LIABILITIES.STOCK_IN.code, credit: constant(50) }, - { account: ASSETS.STOCK.code, debit: constant(50) }, - ] - }, { - label: "Vendor Bill (PO $48, Invoice $50)", - operations: [ - { account: EXPENSES.PRICE_DIFFERENCE.code, debit: constant(2) }, - { account: LIABILITIES.STOCK_IN.code, debit: constant(48) }, - { account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) }, - { account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) }, - ] - }, { - label: "Supplier Goods Reception (PO $48, Invoice $50)", - operations: [ - { account: LIABILITIES.STOCK_IN.code, credit: constant(48) }, - { account: ASSETS.STOCK.code, debit: constant(48) }, - ] - }, { - label: "Customer Invoice", - operations: [ - { account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) }, - { account: EXPENSES.GOODS_SOLD.code, debit: constant(cor) }, - { account: REVENUE.SALES.code, credit: constant(sale) }, - { account: ASSETS.STOCK_OUT.code, credit: constant(cor) }, - { account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) } - ] - }, { - label: "Customer Shipping", - operations: [ - { account: ASSETS.STOCK_OUT.code, debit: constant(cor) }, - { account: ASSETS.STOCK.code, credit: constant(cor) } - ] - }, { - label: "Production Order", - operations: [ - { account: ASSETS.STOCK.code, debit: constant(50) }, - { account: EXPENSES.MANUFACTURING_OVERHEAD.code, debit: constant(2) }, - { account: ASSETS.RAW_MATERIALS.code, credit: constant(52) } - ] - }]); - function constant(val) { return function () { return val; }; } - var zero = constant(0); - function format(val, def) { - if (!val) { return def === undefined ? '' : def; } - if (val % 1 === 0) { return val; } - return val.toFixed(2); - } -})(); diff --git a/static/js/coa-valuation-continental.js b/static/js/coa-valuation-continental.js deleted file mode 100644 index d9d9c0e25..000000000 --- a/static/js/coa-valuation-continental.js +++ /dev/null @@ -1,265 +0,0 @@ -/* global Immutable, React */ -/* global createAtom */ -(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-sm' }, - 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-continental')); - 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-continental'); - 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 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(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 €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: REVENUE.SALES.code, credit: constant(sale) }, - { 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); - } -})();