Merge PR #149 into 13.0

Signed-off-by LoisRForgeFlow
This commit is contained in:
OCA-git-bot
2022-01-21 11:27:56 +00:00
23 changed files with 1709 additions and 0 deletions

View File

@@ -0,0 +1 @@
../../../../stock_account_valuation_report

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@@ -0,0 +1,82 @@
==============================
Stock Account Valuation Report
==============================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--reporting-lightgray.png?logo=github
:target: https://github.com/OCA/stock-logistics-reporting/tree/13.0/stock_account_valuation_report
:alt: OCA/stock-logistics-reporting
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/stock-logistics-reporting-13-0/stock-logistics-reporting-13-0-stock_account_valuation_report
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/151/13.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
When you trigger a report of inventory valuation, and you use
perpetual inventory, you should be able to reconcile the valuation
from an inventory perspective with the valuation
from an accounting perspective.
This module changes the report in *Inventory / Reporting /
Dual Inventory Valuation*
to display separately the Quantity and Value of each product for the
Inventory and the Accounting systems .
**Table of contents**
.. contents::
:local:
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-reporting/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/stock-logistics-reporting/issues/new?body=module:%20stock_account_valuation_report%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* ForgeFlow S.L.
Contributors
~~~~~~~~~~~~
* Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
* Aaron Henriquez <ahenriquez@forgeflow.com>
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/stock-logistics-reporting <https://github.com/OCA/stock-logistics-reporting/tree/13.0/stock_account_valuation_report>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,2 @@
from . import models
from . import wizards

View File

@@ -0,0 +1,16 @@
# Copyright 2020 ForgeFlow S.L.
# Copyright 2019 Aleph Objects, Inc.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "Stock Account Valuation Report",
"version": "13.0.1.0.0",
"summary": "Improves logic of the Inventory Valuation Report",
"author": "ForgeFlow S.L., Odoo Community Association (OCA)",
"website": "https://github.com/OCA/stock-logistics-reporting",
"category": "Warehouse Management",
"depends": ["stock_account"],
"license": "AGPL-3",
"data": ["views/product_product_views.xml", "wizards/stock_valuation_history.xml"],
"installable": True,
}

View File

@@ -0,0 +1,83 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_account_valuation_report
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Qty"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_qty_at_date
msgid "Accounting Quantity"
msgstr ""
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Valuation"
msgstr ""
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:115
#, python-format
msgid "Accounting Valuation at date"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_value
msgid "Accounting Value"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__qty_at_date
msgid "Inventory Quantity"
msgstr ""
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:131
#, python-format
msgid "Inventory Valuation"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_value
msgid "Inventory Value"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_account_move_line
msgid "Journal Item"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_product_product
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_account_move_line__product_id
msgid "Product"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_manual_move_ids
msgid "Stock Fifo Manual Move"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_real_time_aml_ids
msgid "Stock Fifo Real Time Aml"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_stock_quantity_history
msgid "Stock Quantity History"
msgstr ""

View File

@@ -0,0 +1,85 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_account_valuation_report
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2020-02-26 15:13+0000\n"
"Last-Translator: Jesús Alan Ramos Rodríguez <alan.ramos@jarsa.com.mx>\n"
"Language-Team: none\n"
"Language: es_MX\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.10\n"
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Qty"
msgstr "Cant Contable"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_qty_at_date
msgid "Accounting Quantity"
msgstr "Cantidad Contable"
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Valuation"
msgstr "Valoración Contable"
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:115
#, python-format
msgid "Accounting Valuation at date"
msgstr "Valoración contable a la fecha"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_value
msgid "Accounting Value"
msgstr "Valor Contable"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__qty_at_date
msgid "Inventory Quantity"
msgstr "Cantidad en Inventario"
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:131
#, python-format
msgid "Inventory Valuation"
msgstr "Valoración de Inventario"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_value
msgid "Inventory Value"
msgstr "Valor de Inventario"
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_account_move_line
msgid "Journal Item"
msgstr "Elemento de Diario"
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_product_product
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_account_move_line__product_id
msgid "Product"
msgstr "Producto"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_manual_move_ids
msgid "Stock Fifo Manual Move"
msgstr "Movimiento manual de stock PEPS"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_real_time_aml_ids
msgid "Stock Fifo Real Time Aml"
msgstr "Movimientos contables PEPS en Tiempo Real"
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_stock_quantity_history
msgid "Stock Quantity History"
msgstr "Historial de cantidad de inventario"

View File

@@ -0,0 +1,85 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_account_valuation_report
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2021-01-22 15:46+0000\n"
"Last-Translator: Yann Papouin <y.papouin@dec-industrie.com>\n"
"Language-Team: none\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Qty"
msgstr "Qté comptable"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_qty_at_date
msgid "Accounting Quantity"
msgstr "Quantité comptable"
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Valuation"
msgstr "Valorisation comptable"
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:115
#, python-format
msgid "Accounting Valuation at date"
msgstr "Valorisation comptable à ce jour"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_value
msgid "Accounting Value"
msgstr "Valeur comptable"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__qty_at_date
msgid "Inventory Quantity"
msgstr "Quantité d'inventaire"
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:131
#, python-format
msgid "Inventory Valuation"
msgstr "Valorisation de l'inventaire"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_value
msgid "Inventory Value"
msgstr "Valeur d'inventaire"
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_account_move_line
msgid "Journal Item"
msgstr "Écriture comptable"
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_product_product
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_account_move_line__product_id
msgid "Product"
msgstr "Article"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_manual_move_ids
msgid "Stock Fifo Manual Move"
msgstr "Mouvement manuel de stock FIFO"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_real_time_aml_ids
msgid "Stock Fifo Real Time Aml"
msgstr "Stock FIFO en temps réel"
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_stock_quantity_history
msgid "Stock Quantity History"
msgstr "Historique des quantités en stock"

View File

@@ -0,0 +1,86 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_account_valuation_report
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2020-08-13 19:59+0000\n"
"Last-Translator: Matjaz Mozetic <matjaz@matmoz.si>\n"
"Language-Team: none\n"
"Language: sl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
"n%100==4 ? 2 : 3;\n"
"X-Generator: Weblate 3.10\n"
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Qty"
msgstr "Knjigovodska kol"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_qty_at_date
msgid "Accounting Quantity"
msgstr "Knjigovodska količina"
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Valuation"
msgstr "Knjig. vrednotenje"
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:115
#, python-format
msgid "Accounting Valuation at date"
msgstr "Knjigovodsko vrednotenje na datum"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_value
msgid "Accounting Value"
msgstr "Knjigovodska vrednost"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__qty_at_date
msgid "Inventory Quantity"
msgstr "Količina inventarja"
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:131
#, python-format
msgid "Inventory Valuation"
msgstr "Vrednotenje inventarja"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_value
msgid "Inventory Value"
msgstr "Vrednost inventarja"
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_account_move_line
msgid "Journal Item"
msgstr "Dnevniška postavka"
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_product_product
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_account_move_line__product_id
msgid "Product"
msgstr "Izdelek"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_manual_move_ids
msgid "Stock Fifo Manual Move"
msgstr "FIFO ročni premik zalog"
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_real_time_aml_ids
msgid "Stock Fifo Real Time Aml"
msgstr "FIFO premik v realnem času"
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_stock_quantity_history
msgid "Stock Quantity History"
msgstr "Zgodovina količin zalog"

View File

@@ -0,0 +1,83 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_account_valuation_report
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Qty"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_qty_at_date
msgid "Accounting Quantity"
msgstr ""
#. module: stock_account_valuation_report
#: model_terms:ir.ui.view,arch_db:stock_account_valuation_report.view_stock_product_tree2
msgid "Accounting Valuation"
msgstr ""
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:115
#, python-format
msgid "Accounting Valuation at date"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__account_value
msgid "Accounting Value"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__qty_at_date
msgid "Inventory Quantity"
msgstr ""
#. module: stock_account_valuation_report
#: code:addons/stock_account_valuation_report/models/product_product.py:131
#, python-format
msgid "Inventory Valuation"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_value
msgid "Inventory Value"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_account_move_line
msgid "Journal Item"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_product_product
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_account_move_line__product_id
msgid "Product"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_manual_move_ids
msgid "Stock Fifo Manual Move"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model.fields,field_description:stock_account_valuation_report.field_product_product__stock_fifo_real_time_aml_ids
msgid "Stock Fifo Real Time Aml"
msgstr ""
#. module: stock_account_valuation_report
#: model:ir.model,name:stock_account_valuation_report.model_stock_quantity_history
msgid "Stock Quantity History"
msgstr ""

View File

@@ -0,0 +1,2 @@
from . import product_product
from . import account_move_line

View File

@@ -0,0 +1,11 @@
# Copyright 2020 ForgeFlow S.L.
# Copyright 2019 Aleph Objects, Inc.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
product_id = fields.Many2one(index=True)

View File

@@ -0,0 +1,182 @@
# Copyright 2020 ForgeFlow S.L.
# Copyright 2019 Aleph Objects, Inc.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
class ProductProduct(models.Model):
_inherit = "product.product"
stock_value = fields.Float("Inventory Value", compute="_compute_inventory_value")
account_value = fields.Float("Accounting Value", compute="_compute_inventory_value")
qty_at_date = fields.Float("Inventory Quantity", compute="_compute_inventory_value")
account_qty_at_date = fields.Float(
"Accounting Quantity", compute="_compute_inventory_value"
)
stock_fifo_real_time_aml_ids = fields.Many2many(
"account.move.line", compute="_compute_inventory_value"
)
stock_valuation_layer_ids = fields.Many2many(
"stock.valuation.layer", compute="_compute_inventory_value"
)
valuation_discrepancy = fields.Float(
compute="_compute_inventory_value", search="_search_valuation_discrepancy",
)
qty_discrepancy = fields.Float(
compute="_compute_inventory_value", search="_search_qty_discrepancy",
)
valuation = fields.Selection(
related="product_tmpl_id.valuation", search="_search_valuation"
)
@api.model
def _search_valuation(self, operator, value):
domain = [
"|",
("categ_id.property_valuation", operator, value),
("property_valuation", operator, value),
]
products = self.env["product.product"].search(domain)
if value:
return [("id", "in", products.ids)]
else:
return [("id", "not in", products.ids)]
@api.model
def _search_qty_discrepancy(self, operator, value):
products = self.env["product.product"].search([("type", "=", "product")])
pp_list = []
for pp in products:
if pp.qty_at_date != pp.account_qty_at_date:
pp_list.append(pp.id)
return [("id", "in", pp_list)]
@api.model
def _search_valuation_discrepancy(self, operator, value):
products = self.env["product.product"].search([("type", "=", "product")])
pp_list = []
for pp in products:
if pp.stock_value != pp.account_value:
pp_list.append(pp.id)
return [("id", "in", pp_list)]
def _compute_inventory_value(self):
self.env["account.move.line"].check_access_rights("read")
to_date = self.env.context.get("at_date", False)
accounting_values = {}
layer_values = {}
# pylint: disable=E8103
query = """
SELECT aml.product_id, aml.account_id,
sum(aml.balance), sum(quantity),
array_agg(aml.id)
FROM account_move_line AS aml
WHERE aml.product_id IN %%s
AND aml.company_id=%%s %s
GROUP BY aml.product_id, aml.account_id"""
params = (tuple(self._ids,), self.env.company.id)
if to_date:
# pylint: disable=sql-injection
query = query % ("AND aml.date <= %s",)
params = params + (to_date,)
else:
query = query % ("",)
# pylint: disable=E8103
self.env.cr.execute(query, params=params)
res = self.env.cr.fetchall()
for row in res:
accounting_values[(row[0], row[1])] = (row[2], row[3], list(row[4]))
# pylint: disable=E8103
query = """
SELECT DISTINCT ON ("product_id") product_id, sum(quantity),
sum(value), array_agg(svl.id)
FROM "stock_valuation_layer" AS svl
WHERE svl.product_id IN %%s
AND svl.company_id=%%s %s
GROUP BY product_id
ORDER BY "product_id" DESC NULLS LAST
"""
params = (tuple(self._ids,), self.env.company.id)
if to_date:
# pylint: disable=sql-injection
query = query % ("AND svl.create_date <= %s",)
params = params + (to_date,)
else:
query = query % ("",)
# pylint: disable=E8103
self.env.cr.execute(query, params=params)
res = self.env.cr.fetchall()
aml_ids = self.env["account.move.line"]
for row in res:
layer_values[row[0]] = (row[1], row[2], list(row[3]))
for product in self:
# Retrieve the values from accounting
# We cannot provide location-specific accounting valuation,
# so better, leave the data empty in that case:
if product.valuation == "real_time":
valuation_account_id = (
product.categ_id.property_stock_valuation_account_id.id
)
value, quantity, aml_ids = accounting_values.get(
(product.id, valuation_account_id)
) or (0, 0, [])
product.account_value = value
product.account_qty_at_date = quantity
product.stock_fifo_real_time_aml_ids = self.env[
"account.move.line"
].browse(aml_ids)
else:
product.account_value = 0.0
product.account_qty_at_date = 0.0
product.stock_fifo_real_time_aml_ids = []
# Retrieve the values from inventory
quantity, value, svl_ids = layer_values.get(product.id) or (0, 0, [])
product.stock_value = value
product.qty_at_date = quantity
product.stock_valuation_layer_ids = self.env[
"stock.valuation.layer"
].browse(svl_ids)
if product.valuation == "real_time":
product.valuation_discrepancy = (
product.stock_value - product.account_value
)
product.qty_discrepancy = (
product.qty_at_date - product.account_qty_at_date
)
else:
product.valuation_discrepancy = 0.0
product.qty_discrepancy = 0.0
def action_view_amls(self):
self.ensure_one()
tree_view_ref = self.env.ref("account.view_move_line_tree")
form_view_ref = self.env.ref("account.view_move_line_form")
action = {
"name": _("Accounting Valuation at date"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "tree,form",
"context": self.env.context,
"res_model": "account.move.line",
"domain": [("id", "in", self.stock_fifo_real_time_aml_ids.ids,)],
"views": [(tree_view_ref.id, "tree"), (form_view_ref.id, "form")],
}
return action
def action_view_valuation_layers(self):
self.ensure_one()
tree_view_ref = self.env.ref("stock_account.stock_valuation_layer_tree")
form_view_ref = self.env.ref("stock_account.stock_valuation_layer_form")
action = {
"name": _("Inventory Valuation"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "tree,form",
"context": self.env.context,
"res_model": "stock.valuation.layer",
"domain": [("id", "in", self.stock_valuation_layer_ids.ids,)],
"views": [(tree_view_ref.id, "tree"), (form_view_ref.id, "form")],
}
return action

View File

@@ -0,0 +1,2 @@
* Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
* Aaron Henriquez <ahenriquez@forgeflow.com>

View File

@@ -0,0 +1,9 @@
When you trigger a report of inventory valuation, and you use
perpetual inventory, you should be able to reconcile the valuation
from an inventory perspective with the valuation
from an accounting perspective.
This module changes the report in *Inventory / Reporting /
Dual Inventory Valuation*
to display separately the Quantity and Value of each product for the
Inventory and the Accounting systems .

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,427 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>Stock Account Valuation Report</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="stock-account-valuation-report">
<h1 class="title">Stock Account Valuation Report</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/stock-logistics-reporting/tree/13.0/stock_account_valuation_report"><img alt="OCA/stock-logistics-reporting" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--reporting-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/stock-logistics-reporting-13-0/stock-logistics-reporting-13-0-stock_account_valuation_report"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/151/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>When you trigger a report of inventory valuation, and you use
perpetual inventory, you should be able to reconcile the valuation
from an inventory perspective with the valuation
from an accounting perspective.</p>
<p>This module changes the report in <em>Inventory / Reporting /
Dual Inventory Valuation</em>
to display separately the Quantity and Value of each product for the
Inventory and the Accounting systems .</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#bug-tracker" id="id1">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id2">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id3">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id4">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id5">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id1">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/stock-logistics-reporting/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/stock-logistics-reporting/issues/new?body=module:%20stock_account_valuation_report%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id2">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id3">Authors</a></h2>
<ul class="simple">
<li>ForgeFlow S.L.</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id4">Contributors</a></h2>
<ul class="simple">
<li>Jordi Ballester Alomar &lt;<a class="reference external" href="mailto:jordi.ballester&#64;forgeflow.com">jordi.ballester&#64;forgeflow.com</a>&gt;</li>
<li>Aaron Henriquez &lt;<a class="reference external" href="mailto:ahenriquez&#64;forgeflow.com">ahenriquez&#64;forgeflow.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id5">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/stock-logistics-reporting/tree/13.0/stock_account_valuation_report">OCA/stock-logistics-reporting</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
from . import test_stock_account_valuation_report

View File

@@ -0,0 +1,372 @@
# 2020 Copyright ForgeFlow, S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.tests.common import TransactionCase
class TestStockAccountValuationReport(TransactionCase):
def setUp(self):
super(TestStockAccountValuationReport, self).setUp()
# Get required Model
self.product_model = self.env["product.product"]
self.template_model = self.env["product.template"]
self.product_ctg_model = self.env["product.category"]
self.account_model = self.env["account.account"]
self.quant_model = self.env["stock.quant"]
self.layer_model = self.env["stock.valuation.layer"]
self.stock_location_model = self.env["stock.location"]
self.res_users_model = self.env["res.users"]
self.account_move_model = self.env["account.move"]
self.aml_model = self.env["account.move.line"]
self.journal_model = self.env["account.journal"]
# Get required Model data
self.product_uom = self.env.ref("uom.product_uom_unit")
self.company = self.env.ref("base.main_company")
self.stock_picking_type_out = self.env.ref("stock.picking_type_out")
self.stock_picking_type_in = self.env.ref("stock.picking_type_in")
self.stock_location_id = self.ref("stock.stock_location_stock")
self.stock_location_customer_id = self.ref("stock.stock_location_customers")
self.stock_location_supplier_id = self.ref("stock.stock_location_suppliers")
# Account types
expense_type = self.env.ref("account.data_account_type_expenses")
equity_type = self.env.ref("account.data_account_type_equity")
asset_type = self.env.ref("account.data_account_type_fixed_assets")
# Create account for Goods Received Not Invoiced
name = "Goods Received Not Invoiced"
code = "grni"
acc_type = equity_type
self.account_grni = self._create_account(acc_type, name, code, self.company)
# Create account for Cost of Goods Sold
name = "Cost of Goods Sold"
code = "cogs"
acc_type = expense_type
self.account_cogs = self._create_account(acc_type, name, code, self.company)
# Create account for Goods Delivered Not Invoiced
name = "Goods Delivered Not Invoiced"
code = "gdni"
acc_type = expense_type
self.account_gdni = self._create_account(acc_type, name, code, self.company)
# Create account for Inventory
name = "Inventory"
code = "inventory"
acc_type = asset_type
self.account_inventory = self._create_account(
acc_type, name, code, self.company
)
self.stock_journal = self.env["account.journal"].create(
{"name": "Stock journal", "type": "general", "code": "STK00"}
)
# Create product category
self.product_ctg = self._create_product_category()
# Create partners
self.supplier = self.env["res.partner"].create({"name": "Test supplier"})
self.customer = self.env["res.partner"].create({"name": "Test customer"})
# Create a Product with real cost
standard_price = 10.0
list_price = 20.0
self.product = self._create_product(standard_price, False, list_price)
# Create a vendor
self.vendor_partner = self.env["res.partner"].create(
{"name": "dropship vendor"}
)
def _create_user(self, login, groups, company):
""" Create a user."""
group_ids = [group.id for group in groups]
user = self.res_users_model.with_context({"no_reset_password": True}).create(
{
"name": "Test User",
"login": login,
"password": "demo",
"email": "test@yourcompany.com",
"company_id": company.id,
"company_ids": [(4, company.id)],
"groups_id": [(6, 0, group_ids)],
}
)
return user
def _create_account(self, acc_type, name, code, company):
"""Create an account."""
account = self.account_model.create(
{
"name": name,
"code": code,
"user_type_id": acc_type.id,
"company_id": company.id,
}
)
return account
def _create_product_category(self):
product_ctg = self.product_ctg_model.create(
{
"name": "test_product_ctg",
"property_stock_valuation_account_id": self.account_inventory.id,
"property_stock_account_input_categ_id": self.account_grni.id,
"property_account_expense_categ_id": self.account_cogs.id,
"property_stock_account_output_categ_id": self.account_gdni.id,
"property_valuation": "real_time",
"property_cost_method": "fifo",
"property_stock_journal": self.stock_journal.id,
}
)
return product_ctg
def _create_product(self, standard_price, template, list_price):
"""Create a Product variant."""
if not template:
template = self.template_model.create(
{
"name": "test_product",
"categ_id": self.product_ctg.id,
"type": "product",
"standard_price": standard_price,
"valuation": "real_time",
"invoice_policy": "delivery",
}
)
return template.product_variant_ids[0]
product = self.product_model.create(
{"product_tmpl_id": template.id, "list_price": list_price}
)
return product
def _create_delivery(self, product, qty, price_unit=10.0):
return self.env["stock.picking"].create(
{
"name": self.stock_picking_type_out.sequence_id._next(),
"partner_id": self.customer.id,
"picking_type_id": self.stock_picking_type_out.id,
"location_id": self.stock_location_id,
"location_dest_id": self.stock_location_customer_id,
"move_lines": [
(
0,
0,
{
"name": product.name,
"product_id": product.id,
"product_uom": product.uom_id.id,
"product_uom_qty": qty,
"price_unit": price_unit,
"location_id": self.stock_location_id,
"location_dest_id": self.stock_location_customer_id,
"procure_method": "make_to_stock",
},
)
],
}
)
def _create_drophip_picking(self, product, qty, price_unit=10.0):
return self.env["stock.picking"].create(
{
"name": self.stock_picking_type_out.sequence_id._next(),
"partner_id": self.customer.id,
"picking_type_id": self.stock_picking_type_out.id,
"location_id": self.stock_location_supplier_id,
"location_dest_id": self.stock_location_customer_id,
"move_lines": [
(
0,
0,
{
"name": product.name,
"product_id": product.id,
"product_uom": product.uom_id.id,
"product_uom_qty": qty,
"price_unit": price_unit,
"location_id": self.stock_location_supplier_id,
"location_dest_id": self.stock_location_customer_id,
},
)
],
}
)
def _create_receipt(self, product, qty, move_dest_id=False, price_unit=10.0):
move_dest_id = [(4, move_dest_id)] if move_dest_id else False
return self.env["stock.picking"].create(
{
"name": self.stock_picking_type_in.sequence_id._next(),
"partner_id": self.vendor_partner.id,
"picking_type_id": self.stock_picking_type_in.id,
"location_id": self.stock_location_supplier_id,
"location_dest_id": self.stock_location_id,
"move_lines": [
(
0,
0,
{
"name": product.name,
"product_id": product.id,
"product_uom": product.uom_id.id,
"product_uom_qty": qty,
"price_unit": price_unit,
"move_dest_ids": move_dest_id,
"location_id": self.stock_location_supplier_id,
"location_dest_id": self.stock_location_id,
"procure_method": "make_to_stock",
},
)
],
}
)
def _do_picking(self, picking, date, qty):
"""Do picking with only one move on the given date."""
picking.write({"date": date})
picking.move_lines.write({"date": date})
picking.action_confirm()
picking.action_assign()
picking.move_lines.quantity_done = qty
picking.button_validate()
# hacking the create_date of the layer in order to test
self.env.cr.execute(
"""UPDATE stock_valuation_layer SET create_date = %s WHERE id in %s""",
(date, tuple(picking.move_lines.stock_valuation_layer_ids.ids)),
)
return True
def test_01_stock_receipt(self):
"""Receive into stock and ship to the customer
"""
# Create receipt
in_picking = self._create_receipt(self.product, 1.0)
# Receive one unit.
self._do_picking(in_picking, fields.Datetime.now(), 1.0)
# This will create an entry:
# dr cr
# GRNI 10
# Inventory 10
# Inventory is 10
aml = self.aml_model.search([("product_id", "=", self.product.id)])
inv_aml = aml.filtered(lambda l: l.account_id == self.account_inventory)
balance_inv = sum(inv_aml.mapped("balance"))
self.assertEquals(balance_inv, 10.0)
move = in_picking.move_lines
layer = self.layer_model.search([("stock_move_id", "=", move.id)])
self.assertEquals(layer.remaining_value, 10.0)
# The accounting value and the stock value match
self.assertEquals(self.product.stock_value, 10.0)
self.assertEquals(self.product.account_value, 10.0)
# The qty also match
self.assertEquals(self.product.qty_at_date, 1.0)
self.assertEquals(self.product.account_qty_at_date, 1.0)
# Create an out picking
out_picking = self._create_delivery(self.product, 1)
self._do_picking(out_picking, fields.Datetime.now(), 1.0)
# The original layer must have been reduced.
self.assertEquals(layer.remaining_qty, 0.0)
self.assertEquals(layer.remaining_value, 0.0)
# The layer out took that out
move = out_picking.move_lines
layer = self.layer_model.search([("stock_move_id", "=", move.id)])
self.assertEquals(layer.value, -10.0)
# The report shows the material is gone
self.product._compute_inventory_value()
self.assertEquals(self.product.stock_value, 0.0)
self.assertEquals(self.product.account_value, 0.0)
self.assertEquals(self.product.qty_at_date, 0.0)
self.assertEquals(self.product.account_qty_at_date, 0.0)
def test_02_drop_ship(self):
"""Drop shipment from vendor to customer
"""
# Create drop_shipment
dropship_picking = self._create_drophip_picking(self.product, 1.0)
# Receive one unit.
self._do_picking(dropship_picking, fields.Datetime.now(), 1.0)
# This will create the following entries
# dr cr
# GRNI 10
# Inventory 10
# dr cr
# Inventory 10
# GDNI 10
aml = self.aml_model.search([("product_id", "=", self.product.id)])
# Inventory is 0
inv_aml = aml.filtered(lambda l: l.account_id == self.account_inventory)
balance_inv = sum(inv_aml.mapped("balance"))
self.assertEquals(balance_inv, 0.0)
# There are two a stock valuation layers associated to this product
move = dropship_picking.move_lines
layers = self.layer_model.search([("stock_move_id", "=", move.id)])
self.assertEquals(len(layers), 2)
in_layer = layers.filtered(lambda l: l.quantity > 0)
# Check that the layer created for the outgoing move
self.assertEquals(in_layer.remaining_qty, 0.0)
self.assertEquals(in_layer.remaining_value, 0.0)
# The report shows the material is gone
self.assertEquals(self.product.stock_value, 0.0)
self.assertEquals(self.product.account_value, 0.0)
self.assertEquals(self.product.qty_at_date, 0.0)
self.assertEquals(self.product.account_qty_at_date, 0.0)
def test_03_stock_receipt_several_costs_several_dates(self):
"""Receive into stock at different cost
"""
# Create receipt
in_picking = self._create_receipt(self.product, 1.0)
# Receive one unit.
self._do_picking(in_picking, fields.Datetime.now(), 1.0)
# This will create an entry:
# dr cr
# GRNI 10
# Inventory 10
# Inventory is 10
aml = self.aml_model.search([("product_id", "=", self.product.id)])
inv_aml = aml.filtered(lambda l: l.account_id == self.account_inventory)
balance_inv = sum(inv_aml.mapped("balance"))
self.assertEquals(balance_inv, 10.0)
move = in_picking.move_lines
layer = self.layer_model.search([("stock_move_id", "=", move.id)])
self.assertEquals(layer.remaining_value, 10.0)
# Receive more
in_picking2 = self._create_receipt(self.product, 2.0, False, 20.0)
# Receive two unitsat double cost.
self._do_picking(
in_picking2, fields.Datetime.now() + relativedelta(days=3), 2.0
)
# This will create an entry:
# dr cr
# GRNI 40
# Inventory 40
# Inventory is 50
aml = self.aml_model.search([("product_id", "=", self.product.id)])
inv_aml = aml.filtered(lambda l: l.account_id == self.account_inventory)
balance_inv = sum(inv_aml.mapped("balance"))
self.assertEquals(balance_inv, 50.0)
move2 = in_picking2.move_lines
layer = self.layer_model.search([("stock_move_id", "=", move2.id)])
self.assertEquals(layer.remaining_value, 40.0)
# Now we check the report reflects the same
self.assertEquals(self.product.stock_value, 50.0)
self.assertEquals(self.product.account_value, 50.0)
self.assertEquals(self.product.qty_at_date, 3.0)
self.assertEquals(self.product.account_qty_at_date, 3.0)
# That is the value tomorrow, today it is less
# We hack the date in the account move, not a topic for this module
aml_layer = layer.account_move_id.line_ids
self.env.cr.execute(
"""UPDATE account_move_line SET date = %s WHERE id in %s""",
(fields.Datetime.now() + relativedelta(days=3), tuple(aml_layer.ids)),
)
self.product.with_context(
at_date=fields.Datetime.now() + relativedelta(days=1)
)._compute_inventory_value()
self.assertEquals(self.product.stock_value, 10.0)
self.assertEquals(self.product.account_value, 10.0)
self.assertEquals(self.product.qty_at_date, 1.0)
self.assertEquals(self.product.account_qty_at_date, 1.0)

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_stock_product_tree2" model="ir.ui.view">
<field name="name">product.stock.tree.2</field>
<field name="model">product.product</field>
<field name="arch" type="xml">
<tree>
<field name="display_name" />
<field name="qty_at_date" />
<field name="uom_id" groups="uom.group_uom" />
<field name="currency_id" invisible="1" />
<field name="cost_currency_id" invisible="1" />
<field name="valuation" invisible="1" />
<field
name="stock_value"
sum="Stock Valuation"
widget="monetary"
options="{'currency_field': 'cost_currency_id'}"
/>
<button
name="action_view_valuation_layers"
type="object"
icon="fa-info-circle"
attrs="{'invisible': [('valuation', '!=', 'real_time')]}"
/>
<field
name="account_qty_at_date"
sum="Accounting Qty"
attrs="{'invisible': [('valuation', '!=', 'real_time')]}"
/>
<field
name="account_value"
sum="Accounting Valuation"
widget="monetary"
attrs="{'invisible': [('valuation', '!=', 'real_time')]}"
/>
<button
name="action_view_amls"
type="object"
icon="fa-info-circle"
attrs="{'invisible': [('valuation', '!=', 'real_time')]}"
/>
<field name="qty_discrepancy" sum="Qty Discrepancy" />
<field name="valuation_discrepancy" sum="Valuation Discrepancy" />
</tree>
</field>
</record>
<record id="product_valuation_action" model="ir.actions.act_window">
<field name="name">Dual Inventory Valuation</field>
<field name="res_model">product.product</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_stock_product_tree2" />
<field name="domain">[('type', '=', 'product')]</field>
<field
name="context"
>{'company_owned': True, 'create': False, 'edit' : False}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a product to see its name and valuation.
</p>
</field>
</record>
<record id="product_search_form_view" model="ir.ui.view">
<field name="name">product.product.search</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_search_form_view" />
<field name="arch" type="xml">
<field name="name" position="after">
<field name="valuation_discrepancy" />
</field>
<filter name="inactive" position="after">
<separator />
<filter
string="Real time"
name="real_valuation"
domain="[('valuation', '=', 'real_time')]"
/>
<filter
string="Manual"
name="manual_valuation"
domain="[('valuation', '=', 'manual_periodic')]"
/>
<filter
string="Qty Discrepancy"
name="qty_discrepancy_not_null"
domain="[('qty_discrepancy', '!=', 0.0), ('valuation', '=', 'real_time')]"
/>
<filter
string="Valuation Discrepancy"
name="valuation_discrepancy_not_null"
domain="[('valuation_discrepancy', '!=', 0.0), ('valuation', '=', 'real_time')]"
/>
</filter>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
from . import stock_valuation_history

View File

@@ -0,0 +1,36 @@
# Copyright 2019 Eficent Business and IT Consulting Services, S.L.
# Copyright 2019 Aleph Objects, Inc.
from odoo import fields, models
from odoo.osv import expression
from odoo.tools.safe_eval import safe_eval
class StockValuationHistory(models.TransientModel):
_name = "stock.valuation.history"
_description = "Stock Valuation History"
inventory_datetime = fields.Datetime(
"Dual Valuation at Date",
help="Choose a date to get the valuation at that date",
default=fields.Datetime.now,
)
def open_at_date(self):
action = self.env["ir.actions.act_window"].for_xml_id(
"stock_account_valuation_report", "product_valuation_action"
)
domain = [("type", "=", "product")]
product_id = self.env.context.get("product_id", False)
product_tmpl_id = self.env.context.get("product_tmpl_id", False)
if product_id:
domain = expression.AND([domain, [("id", "=", product_id)]])
elif product_tmpl_id:
domain = expression.AND(
[domain, [("product_tmpl_id", "=", product_tmpl_id)]]
)
action["domain"] = domain
if self.inventory_datetime:
action_context = safe_eval(action["context"])
action_context["at_date"] = self.inventory_datetime
action["context"] = action_context
return action

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_stock_valuation_history" model="ir.ui.view">
<field name="name">Dual Valuation Report at Date</field>
<field name="model">stock.valuation.history</field>
<field name="arch" type="xml">
<form string="Choose your date">
<group>
<field name="inventory_datetime" />
</group>
<footer>
<button
name="open_at_date"
string="Confirm"
type="object"
class="btn-primary"
/>
<button string="Cancel" class="btn-secondary" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="history_valuation_action" model="ir.actions.act_window">
<field name="name">Dual Inventory Valuation</field>
<field name="res_model">stock.valuation.history</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_stock_valuation_history" />
</record>
<menuitem
id="menu_dual_valuation"
name="Dual Inventory Valuation"
parent="stock.menu_warehouse_report"
sequence="120"
action="history_valuation_action"
/>
</odoo>