diff --git a/setup/stock_account_anglo_saxon_cogs_kit/odoo/addons/stock_account_anglo_saxon_cogs_kit b/setup/stock_account_anglo_saxon_cogs_kit/odoo/addons/stock_account_anglo_saxon_cogs_kit new file mode 120000 index 000000000..453f0182d --- /dev/null +++ b/setup/stock_account_anglo_saxon_cogs_kit/odoo/addons/stock_account_anglo_saxon_cogs_kit @@ -0,0 +1 @@ +../../../../stock_account_anglo_saxon_cogs_kit \ No newline at end of file diff --git a/setup/stock_account_anglo_saxon_cogs_kit/setup.py b/setup/stock_account_anglo_saxon_cogs_kit/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/stock_account_anglo_saxon_cogs_kit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_account_anglo_saxon_cogs_kit/README.rst b/stock_account_anglo_saxon_cogs_kit/README.rst new file mode 100644 index 000000000..7b030826f --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/README.rst @@ -0,0 +1,101 @@ +================================== +Stock Account Anglo Saxon COGS Kit +================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:95246b5c8140145c330025892f1165ea1839841376ce1796732d29fb5b4c1962 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Faccount--financial--tools-lightgray.png?logo=github + :target: https://github.com/OCA/account-financial-tools/tree/16.0/stock_account_anglo_saxon_cogs_kit + :alt: OCA/account-financial-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-financial-tools-16-0/account-financial-tools-16-0-stock_account_anglo_saxon_cogs_kit + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/account-financial-tools&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +The Odoo Official documentation recommends to use consumable for kit products and only to be +storable in case Anglosaxon accounting: + +https://www.odoo.com/documentation/16.0/applications/inventory_and_mrp/manufacturing/management/kit_shipping.html + +"Although kits should almost always be set to Consumable, +companies using Anglo-Saxon accounting might need to create kits as a Storable Product. This is +because when processing invoices for kits, the Cost of Goods Sold (COGS) will be posted in accounting journals." + +However, that forces companies to track quantities for kits, and that is not always desirable. + +With this module companies using Anglo-Saxon accounting don't need to set the kit products to be +storable if they don't want to track quantities for the kit. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* ForgeFlow, S.L. (https://www.forgeflow.com) + * Marina Alapont + * Aaron Henriquez + +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. + +.. |maintainer-MarinaAForgeFlow| image:: https://github.com/MarinaAForgeFlow.png?size=40px + :target: https://github.com/MarinaAForgeFlow + :alt: MarinaAForgeFlow +.. |maintainer-AaronHForgeFlow| image:: https://github.com/AaronHForgeFlow.png?size=40px + :target: https://github.com/AaronHForgeFlow + :alt: AaronHForgeFlow + +Current `maintainers `__: + +|maintainer-MarinaAForgeFlow| |maintainer-AaronHForgeFlow| + +This module is part of the `OCA/account-financial-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_account_anglo_saxon_cogs_kit/__init__.py b/stock_account_anglo_saxon_cogs_kit/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_account_anglo_saxon_cogs_kit/__manifest__.py b/stock_account_anglo_saxon_cogs_kit/__manifest__.py new file mode 100644 index 000000000..48f51125e --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2024 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Stock Account Anglo Saxon COGS Kit", + "category": "Accounting", + "version": "16.0.1.0.0", + "depends": ["sale_mrp"], + "data": [], + "author": "ForgeFlow, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/account-financial-tools", + "license": "AGPL-3", + "installable": True, + "maintainers": ["MarinaAForgeFlow", "AaronHForgeFlow"], +} diff --git a/stock_account_anglo_saxon_cogs_kit/models/__init__.py b/stock_account_anglo_saxon_cogs_kit/models/__init__.py new file mode 100644 index 000000000..8795b3bea --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/models/__init__.py @@ -0,0 +1 @@ +from . import account_move_line diff --git a/stock_account_anglo_saxon_cogs_kit/models/account_move_line.py b/stock_account_anglo_saxon_cogs_kit/models/account_move_line.py new file mode 100644 index 000000000..8fe0bb247 --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/models/account_move_line.py @@ -0,0 +1,14 @@ +# Copyright 2024 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + def _eligible_for_cogs(self): + return super()._eligible_for_cogs() or any( + p.type == "product" and p.valuation == "real_time" + for p in self.sale_line_ids.mapped("move_ids.product_id") + ) diff --git a/stock_account_anglo_saxon_cogs_kit/readme/CONTRIBUTORS.rst b/stock_account_anglo_saxon_cogs_kit/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..b8e9029bb --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* ForgeFlow, S.L. (https://www.forgeflow.com) + * Marina Alapont + * Aaron Henriquez diff --git a/stock_account_anglo_saxon_cogs_kit/readme/DESCRIPTION.rst b/stock_account_anglo_saxon_cogs_kit/readme/DESCRIPTION.rst new file mode 100644 index 000000000..5d25c3031 --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/readme/DESCRIPTION.rst @@ -0,0 +1,13 @@ +The Odoo Official documentation recommends to use consumable for kit products and only to be +storable in case Anglosaxon accounting: + +https://www.odoo.com/documentation/16.0/applications/inventory_and_mrp/manufacturing/management/kit_shipping.html + +"Although kits should almost always be set to Consumable, +companies using Anglo-Saxon accounting might need to create kits as a Storable Product. This is +because when processing invoices for kits, the Cost of Goods Sold (COGS) will be posted in accounting journals." + +However, that forces companies to track quantities for kits, and that is not always desirable. + +With this module companies using Anglo-Saxon accounting don't need to set the kit products to be +storable if they don't want to track quantities for the kit. diff --git a/stock_account_anglo_saxon_cogs_kit/static/description/index.html b/stock_account_anglo_saxon_cogs_kit/static/description/index.html new file mode 100644 index 000000000..ae12f5e46 --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/static/description/index.html @@ -0,0 +1,433 @@ + + + + + + +Stock Account Anglo Saxon COGS Kit + + + +
+

Stock Account Anglo Saxon COGS Kit

+ + +

Beta License: AGPL-3 OCA/account-financial-tools Translate me on Weblate Try me on Runboat

+

The Odoo Official documentation recommends to use consumable for kit products and only to be +storable in case Anglosaxon accounting:

+

https://www.odoo.com/documentation/16.0/applications/inventory_and_mrp/manufacturing/management/kit_shipping.html

+

“Although kits should almost always be set to Consumable, +companies using Anglo-Saxon accounting might need to create kits as a Storable Product. This is +because when processing invoices for kits, the Cost of Goods Sold (COGS) will be posted in accounting journals.”

+

However, that forces companies to track quantities for kits, and that is not always desirable.

+

With this module companies using Anglo-Saxon accounting don’t need to set the kit products to be +storable if they don’t want to track quantities for the kit.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ForgeFlow
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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.

+

Current maintainers:

+

MarinaAForgeFlow AaronHForgeFlow

+

This module is part of the OCA/account-financial-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_account_anglo_saxon_cogs_kit/tests/__init__.py b/stock_account_anglo_saxon_cogs_kit/tests/__init__.py new file mode 100644 index 000000000..1ae2ab44a --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_account_anglo_saxon_cogs_kit diff --git a/stock_account_anglo_saxon_cogs_kit/tests/test_stock_account_anglo_saxon_cogs_kit.py b/stock_account_anglo_saxon_cogs_kit/tests/test_stock_account_anglo_saxon_cogs_kit.py new file mode 100644 index 000000000..d362f49fd --- /dev/null +++ b/stock_account_anglo_saxon_cogs_kit/tests/test_stock_account_anglo_saxon_cogs_kit.py @@ -0,0 +1,230 @@ +# Copyright 2024 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields +from odoo.tests import Form, common + + +# these tests create accounting entries, and therefore need a chart of accounts +@common.tagged("post_install", "-at_install") +class TestStockAccountAngloSaxonCogsKit(common.TransactionCase): + @classmethod + def setUpClass(cls): + super(TestStockAccountAngloSaxonCogsKit, cls).setUpClass() + # Useful models + cls.StockMove = cls.env["stock.move"] + cls.UoM = cls.env["uom.uom"] + cls.MrpProduction = cls.env["mrp.production"] + cls.ProductCategory = cls.env["product.category"] + cls.categ_unit = cls.env.ref("uom.product_uom_categ_unit") + + def test_01_sale_mrp_anglo_saxon(self): + """Test sale order for kit, deliver and invoice and ensure + COGS is hit + """ + self.env.company.currency_id = self.env.ref("base.USD") + self.uom_unit = self.UoM.create( + { + "name": "Test-Unit", + "category_id": self.categ_unit.id, + "factor": 1, + "uom_type": "bigger", + "rounding": 1.0, + } + ) + self.company = self.env.ref("base.main_company") + self.company.anglo_saxon_accounting = True + self.partner = self.env.ref("base.res_partner_1") + self.category = self.env.ref("product.product_category_1").copy( + { + "name": "Test category", + "property_valuation": "real_time", + "property_cost_method": "fifo", + } + ) + account_receiv = self.env["account.account"].create( + { + "name": "Receivable", + "code": "RCV00", + "account_type": "asset_receivable", + "reconcile": True, + } + ) + account_expense = self.env["account.account"].create( + { + "name": "Expense", + "code": "EXP00", + "account_type": "expense", + "reconcile": True, + } + ) + account_input = self.env["account.account"].create( + { + "name": "Input", + "code": "IN00", + "account_type": "liability_current", + "reconcile": True, + } + ) + account_output = self.env["account.account"].create( + { + "name": "Output", + "code": "OUT00", + "account_type": "asset_current", + "reconcile": True, + } + ) + account_valuation = self.env["account.account"].create( + { + "name": "Valuation", + "code": "STV00", + "account_type": "asset_fixed", + "reconcile": True, + } + ) + self.partner.property_account_receivable_id = account_receiv + self.category.property_stock_account_input_categ_id = account_input + self.category.property_stock_account_output_categ_id = account_output + self.category.property_stock_valuation_account_id = account_valuation + self.category.property_account_expense_categ_id = account_expense + self.category.property_stock_journal = self.env["account.journal"].create( + {"name": "Stock journal", "type": "sale", "code": "STK00"} + ) + + Product = self.env["product.product"] + self.finished_product = Product.create( + { + "name": "KIT product", + "type": "consu", + "uom_id": self.uom_unit.id, + "invoice_policy": "delivery", + "categ_id": self.category.id, + } + ) + self.component1 = Product.create( + { + "name": "Component 1", + "type": "product", + "uom_id": self.uom_unit.id, + "categ_id": self.category.id, + "standard_price": 20, + } + ) + self.component2 = Product.create( + { + "name": "Component 2", + "type": "product", + "uom_id": self.uom_unit.id, + "categ_id": self.category.id, + "standard_price": 10, + } + ) + self.env["stock.quant"].create( + { + "product_id": self.component1.id, + "location_id": self.env.ref("stock.stock_location_stock").id, + "quantity": 6.0, + } + ) + self.env["stock.quant"].create( + { + "product_id": self.component2.id, + "location_id": self.env.ref("stock.stock_location_stock").id, + "quantity": 3.0, + } + ) + kit = self.env["mrp.bom"].create( + { + "product_tmpl_id": self.finished_product.product_tmpl_id.id, + "product_qty": 1.0, + "type": "phantom", + } + ) + BomLine = self.env["mrp.bom.line"] + BomLine.create( + { + "product_id": self.component1.id, + "product_qty": 2.0, + "bom_id": kit.id, + } + ) + BomLine.create( + { + "product_id": self.component2.id, + "product_qty": 1.0, + "bom_id": kit.id, + } + ) + + # Create a SO for a specific partner for three units of the + # finished product + so_vals = { + "partner_id": self.partner.id, + "date_order": fields.Datetime.now(), + "order_line": [ + ( + 0, + 0, + { + "name": self.finished_product.name, + "product_id": self.finished_product.id, + "product_uom_qty": 3, + "product_uom": self.finished_product.uom_id.id, + "price_unit": self.finished_product.list_price, + }, + ) + ], + "pricelist_id": self.env.ref("product.list0").id, + "company_id": self.company.id, + } + self.so = self.env["sale.order"].create(so_vals) + # Validate the SO + self.so.action_confirm() + # Deliver the three finished products + pick = self.so.picking_ids + # To check the products on the picking + self.assertEqual( + pick.move_line_ids.mapped("product_id"), self.component1 | self.component2 + ) + pick.action_confirm() + pick.action_assign() + for move in pick.move_ids: + move.quantity_done = move.product_uom_qty + pick.button_validate() + # Create the invoice + self.so._create_invoices() + self.invoice = self.so.invoice_ids + # Changed the invoiced quantity of the finished product to 2 + move_form = Form(self.invoice) + with move_form.invoice_line_ids.edit(0) as line_form: + line_form.quantity = 2.0 + self.invoice = move_form.save() + self.invoice.action_post() + aml = self.invoice.line_ids + aml_expense = aml.filtered(lambda l: l.account_id == account_expense) + aml_output = aml.filtered(lambda l: l.account_id == account_output) + # Check that the cost of Good Sold entries are equal to: + # 2* (2 * 20 + 1 * 10) = 100 + self.assertEqual( + sum(aml_expense.mapped("debit")), + 100, + "Cost of Good Sold entry missing or mismatching", + ) + self.assertEqual( + sum(aml_output.mapped("credit")), 100, "GDNI missing or mismatching" + ) + # Now checking that the stock entries for the finished product are + # created and not the component + self.assertNotEqual( + aml.filtered( + lambda ml: ml.product_id == self.finished_product + and ml.account_id == account_expense + ), + self.env["account.move.line"], + ) + self.assertEqual( + aml.filtered( + lambda ml: ml.product_id == self.component1 + and ml.account_id == account_expense + ), + self.env["account.move.line"], + )