From 88fe0d99a7363d2aad0da9b52c969f18209acf22 Mon Sep 17 00:00:00 2001
From: Daniel Reis
Date: Sat, 22 May 2021 13:50:50 +0100
Subject: [PATCH 01/36] [ADD] account_analytic_wip: Analytic Accounting support
for WIP and Variances
---
account_analytic_wip/README.rst | 1 +
account_analytic_wip/__init__.py | 4 +
account_analytic_wip/__manifest__.py | 24 ++
account_analytic_wip/data/ir_cron_data.xml | 18 ++
account_analytic_wip/models/__init__.py | 9 +
.../models/account_analytic_line.py | 13 +
.../models/account_analytic_tracked.py | 83 ++++++
.../models/account_analytic_tracking.py | 265 ++++++++++++++++++
account_analytic_wip/models/account_move.py | 15 +
.../models/product_category.py | 48 ++++
.../models/product_template.py | 33 +++
account_analytic_wip/readme/CONFIGURATION.rst | 7 +
account_analytic_wip/readme/CONTRIBUTORS.rst | 3 +
account_analytic_wip/readme/DESCRIPTION.rst | 15 +
account_analytic_wip/readme/USAGE.rst | 50 ++++
.../security/ir.model.access.csv | 3 +
.../static/description/icon.png | Bin 0 -> 9455 bytes
account_analytic_wip/tests/__init__.py | 1 +
account_analytic_wip/tests/test_analytic.py | 53 ++++
.../views/account_analytic_line.xml | 17 ++
.../views/account_analytic_tracking.xml | 93 ++++++
account_analytic_wip/views/account_move.xml | 17 ++
.../views/product_category.xml | 26 ++
23 files changed, 798 insertions(+)
create mode 100644 account_analytic_wip/README.rst
create mode 100644 account_analytic_wip/__init__.py
create mode 100644 account_analytic_wip/__manifest__.py
create mode 100644 account_analytic_wip/data/ir_cron_data.xml
create mode 100644 account_analytic_wip/models/__init__.py
create mode 100644 account_analytic_wip/models/account_analytic_line.py
create mode 100644 account_analytic_wip/models/account_analytic_tracked.py
create mode 100644 account_analytic_wip/models/account_analytic_tracking.py
create mode 100644 account_analytic_wip/models/account_move.py
create mode 100644 account_analytic_wip/models/product_category.py
create mode 100644 account_analytic_wip/models/product_template.py
create mode 100644 account_analytic_wip/readme/CONFIGURATION.rst
create mode 100644 account_analytic_wip/readme/CONTRIBUTORS.rst
create mode 100644 account_analytic_wip/readme/DESCRIPTION.rst
create mode 100644 account_analytic_wip/readme/USAGE.rst
create mode 100644 account_analytic_wip/security/ir.model.access.csv
create mode 100644 account_analytic_wip/static/description/icon.png
create mode 100644 account_analytic_wip/tests/__init__.py
create mode 100644 account_analytic_wip/tests/test_analytic.py
create mode 100644 account_analytic_wip/views/account_analytic_line.xml
create mode 100644 account_analytic_wip/views/account_analytic_tracking.xml
create mode 100644 account_analytic_wip/views/account_move.xml
create mode 100644 account_analytic_wip/views/product_category.xml
diff --git a/account_analytic_wip/README.rst b/account_analytic_wip/README.rst
new file mode 100644
index 0000000000..876fdb5f40
--- /dev/null
+++ b/account_analytic_wip/README.rst
@@ -0,0 +1 @@
+Generated
diff --git a/account_analytic_wip/__init__.py b/account_analytic_wip/__init__.py
new file mode 100644
index 0000000000..bb83730e95
--- /dev/null
+++ b/account_analytic_wip/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from . import models
diff --git a/account_analytic_wip/__manifest__.py b/account_analytic_wip/__manifest__.py
new file mode 100644
index 0000000000..ad91e899fa
--- /dev/null
+++ b/account_analytic_wip/__manifest__.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+{
+ "name": "Analytic Accounting support for WIP and Variances",
+ "version": "14.0.1.0.0",
+ "author": "Open Source Integrators, Odoo Community Association (OCA)",
+ "summary": "Track and report WIP and Variances based on Analytic Items",
+ "website": "https://github.com/OCA/account-analytic",
+ "license": "AGPL-3",
+ "depends": ["stock_account"],
+ "category": "Accounting/Accounting",
+ "data": [
+ "security/ir.model.access.csv",
+ "data/ir_cron_data.xml",
+ "views/product_category.xml",
+ "views/account_move.xml",
+ "views/account_analytic_line.xml",
+ "views/account_analytic_tracking.xml",
+ ],
+ "development_status": "Alpha",
+ "maintainers": ["dreispt"],
+ "installable": True,
+}
diff --git a/account_analytic_wip/data/ir_cron_data.xml b/account_analytic_wip/data/ir_cron_data.xml
new file mode 100644
index 0000000000..95508ddf0f
--- /dev/null
+++ b/account_analytic_wip/data/ir_cron_data.xml
@@ -0,0 +1,18 @@
+
+
+ Account Analytic: post WIP and Variances journal entries
+ 1
+ days
+ -1
+
+
+
+ model._cron_process_wip_and_variance()
+ code
+
+
diff --git a/account_analytic_wip/models/__init__.py b/account_analytic_wip/models/__init__.py
new file mode 100644
index 0000000000..91bc5605af
--- /dev/null
+++ b/account_analytic_wip/models/__init__.py
@@ -0,0 +1,9 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from . import product_category
+from . import product_template
+from . import account_move
+from . import account_analytic_line
+from . import account_analytic_tracking
+from . import account_analytic_tracked
diff --git a/account_analytic_wip/models/account_analytic_line.py b/account_analytic_wip/models/account_analytic_line.py
new file mode 100644
index 0000000000..fa2cf876a6
--- /dev/null
+++ b/account_analytic_wip/models/account_analytic_line.py
@@ -0,0 +1,13 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+
+from odoo import fields, models
+
+
+class AnalyticLine(models.Model):
+ _inherit = "account.analytic.line"
+
+ analytic_tracking_item_id = fields.Many2one(
+ "account.analytic.tracking.item", string="Tracking Item"
+ )
diff --git a/account_analytic_wip/models/account_analytic_tracked.py b/account_analytic_wip/models/account_analytic_tracked.py
new file mode 100644
index 0000000000..3477c99a52
--- /dev/null
+++ b/account_analytic_wip/models/account_analytic_tracked.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+
+from odoo import api, fields, models
+
+
+class AnalyticTrackedItem(models.AbstractModel):
+ _name = "account.analytic.tracked.mixin"
+ _description = "Cost Tracked Mixin"
+
+ analytic_tracking_item_id = fields.Many2one(
+ "account.analytic.tracking.item",
+ string="Tracking Item",
+ ondelete="cascade",
+ copy=False,
+ )
+
+ def _prepare_tracking_item_values(self):
+ """
+ To be implemented by inheriting models.
+ Return a dict with the values to create the related Tracking Item.
+
+ return {
+ "analytic_id": ...,
+ "product_id": ...,
+ ...,
+ }
+ """
+ self.ensure_one()
+ return {}
+
+ def _get_tracking_planned_qty(self):
+ """
+ To be extended by inheriting Model
+ """
+ return 0.0
+
+ def set_tracking_item(self, update_planned=False, force=False):
+ """
+ Create and set the related Tracking Item, where actuals will be accumulated to.
+ The _prepare_tracking_item_values() provides the values used to create it.
+
+ If the update_planned flag is set, the planned amount is updated.
+ The _get_tracking_planned_qty() method provides the planned quantity.
+
+ By default is is not set, and will be zero for new tracking items.
+
+ Returns one Tracking Item record or an empty recordset.
+ """
+ TrackingItem = self.env["account.analytic.tracking.item"]
+ for item in self:
+ if not item.analytic_tracking_item_id or force:
+ vals = item._prepare_tracking_item_values()
+ item.analytic_tracking_item_id = vals and TrackingItem.create(vals)
+ # The Product my be a Cost Type with child Products
+ cost_rules = item.analytic_tracking_item_id.product_id.activity_cost_ids
+ for cost_type in cost_rules.product_id:
+ child_vals = dict(vals)
+ child_vals.update(
+ {
+ "parent_id": item.analytic_tracking_item_id.id,
+ "product_id": cost_type.id,
+ }
+ )
+ TrackingItem.create(child_vals)
+
+ if update_planned and item.analytic_tracking_item_id:
+ planned_qty = item._get_tracking_planned_qty()
+ tracking_item = item.analytic_tracking_item_id
+ for subitem in tracking_item | tracking_item.child_ids:
+ qty = planned_qty if subitem.to_calculate else 0.0
+ unit_cost = subitem.product_id.standard_price
+ subitem.planned_amount = qty * unit_cost
+
+ @api.model
+ def create(self, vals):
+ """
+ New tracked records automatically create Tracking Items, if possible.
+ """
+ new = super().create(vals)
+ new.set_tracking_item()
+ return new
diff --git a/account_analytic_wip/models/account_analytic_tracking.py b/account_analytic_wip/models/account_analytic_tracking.py
new file mode 100644
index 0000000000..fb79f5b5c9
--- /dev/null
+++ b/account_analytic_wip/models/account_analytic_tracking.py
@@ -0,0 +1,265 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+
+from odoo import _, api, fields, models
+
+
+class AnalyticTrackingItem(models.Model):
+ """
+ Tracking Items provide a central point to report WIP and Variances.
+ Expected amounts are stored on a key event, such a confirming an order.
+ Done amounts are captured by Analytic items.
+ They can then be posted as journal entries.
+ """
+
+ _name = "account.analytic.tracking.item"
+ _description = "Cost Tracking Item"
+
+ name = fields.Char(compute="_compute_name", store=True)
+ date = fields.Date(default=fields.Date.today())
+ analytic_id = fields.Many2one(
+ "account.analytic.account",
+ string="Analytic Account",
+ required=True,
+ ondelete="restrict",
+ )
+ product_id = fields.Many2one(
+ "product.product", string="Cost Product", ondelete="restrict"
+ )
+ analytic_line_ids = fields.One2many(
+ "account.analytic.line",
+ "analytic_tracking_item_id",
+ string="Analytic Items",
+ help="Related analytic items with the project actuals.",
+ )
+ account_move_ids = fields.One2many(
+ "account.move",
+ "analytic_tracking_item_id",
+ string="Journal Entries",
+ help="Related journal entries with the posted WIP.",
+ )
+ state = fields.Selection(
+ [
+ ("open", "Open"),
+ ("close", "Closed"),
+ ("cancel", "Cancelled"),
+ ],
+ default="open",
+ help="Open operations are in progress, no negative variances are computed. "
+ "Done operations are completed, negative variances are computed. "
+ "Closed operations are done and posting, no more actions to do.",
+ )
+ to_calculate = fields.Boolean(compute="_compute_to_calculate", store=True)
+
+ parent_id = fields.Many2one(
+ "account.analytic.tracking.item", "Parent Tracking Item", ondelete="cascade"
+ )
+ child_ids = fields.One2many(
+ "account.analytic.tracking.item", "parent_id", string="Child Tracking Items"
+ )
+
+ # Planned Amount
+ planned_amount = fields.Float()
+
+ # Actual Amounts
+ actual_amount = fields.Float(
+ compute="_compute_actual_amounts",
+ help="Total cost amount of the related Analytic Items. "
+ "These Analytic Items are generated when a cost is incurred, "
+ "and will later generated WIP and Variance Journal Entries.",
+ )
+ wip_actual_amount = fields.Float(
+ compute="_compute_actual_amounts",
+ help="Actual amount incurred below the planned amount limit.",
+ )
+ variance_actual_amount = fields.Float(
+ compute="_compute_actual_amounts",
+ help="Actual amount incurred above the planned amount limit.",
+ )
+ remaining_actual_amount = fields.Float(
+ compute="_compute_actual_amounts",
+ help="Actual amount planned and not yet consumed.",
+ )
+ pending_amount = fields.Float(
+ compute="_compute_actual_amounts",
+ help="Amount not yet posted to journal entries.",
+ )
+
+ # Accounted Amounts
+ accounted_amount = fields.Float(
+ help="Amount accounted in Journal Entries. "
+ "Directly set by the routine creating the Journal Entries, "
+ "and not directly read from the jpunral items."
+ )
+ wip_accounted_amount = fields.Float(
+ help="Accounted amount incurred below the planned amount limit."
+ )
+ variance_accounted_amount = fields.Float(
+ help="Accounted amount incurred above the planned amount limit."
+ )
+
+ @api.depends("product_id")
+ def _compute_name(self):
+ for item in self:
+ item.name = item.product_id.display_name
+
+ @api.depends("state", "parent_id", "child_ids")
+ def _compute_to_calculate(self):
+ for tracking in self:
+ tracking.to_calculate = tracking.state != "cancel" and (
+ tracking.parent_id or not tracking.child_ids
+ )
+
+ @api.depends(
+ "analytic_line_ids.amount",
+ "planned_amount",
+ "accounted_amount",
+ "state",
+ "child_ids",
+ )
+ def _compute_actual_amounts(self):
+ for item in self:
+ # Actuals
+ if not item.to_calculate:
+ item.actual_amount = 0
+ else:
+ if item.parent_id:
+ actuals = item.parent_id.analytic_line_ids.filtered(
+ lambda x: x.product_id == item.product_id
+ )
+ else:
+ actuals = item.analytic_line_ids
+ item.actual_amount = -sum(actuals.mapped("amount")) or 0.0
+
+ item.pending_amount = item.actual_amount - item.accounted_amount
+ item.wip_actual_amount = min(item.actual_amount, item.planned_amount)
+
+ if not item.to_calculate:
+ item.remaining_actual_amount = 0
+ item.variance_actual_amount = 0
+ item.pending_amount = 0
+ elif item.state == "open":
+ # Negative variances show in the Remaining column
+ item.remaining_actual_amount = (
+ item.planned_amount - item.wip_actual_amount
+ )
+ item.variance_actual_amount = max(
+ item.actual_amount - item.planned_amount, 0
+ )
+ else:
+ # Negative variances show in the Variance column
+ item.remaining_actual_amount = 0
+ item.variance_actual_amount = item.actual_amount - item.planned_amount
+
+ def _prepare_account_move(self, journal):
+ return {
+ "journal_id": journal.id,
+ "date": self.env.context.get(
+ "force_period_date", fields.Date.context_today(self)
+ ),
+ "ref": self.display_name,
+ "move_type": "entry",
+ "analytic_tracking_item_id": self.id,
+ }
+
+ def _prepare_account_move_line(self, account, debit_amount=0, credit_amount=0):
+ self.ensure_one()
+ vals = {}
+ if account and (debit_amount or credit_amount):
+ debit = (debit_amount if debit_amount > 0 else 0) + (
+ -credit_amount if credit_amount < 0 else 0
+ )
+ credit = (credit_amount if credit_amount > 0 else 0) + (
+ -debit_amount if debit_amount < 0 else 0
+ )
+ vals = {
+ "name": self.display_name,
+ "product_id": self.product_id.id,
+ "product_uom_id": self.product_id.uom_id.id,
+ "analytic_account_id": self.analytic_id.id,
+ "ref": self.display_name,
+ "account_id": account.id,
+ "debit": debit,
+ "credit": credit,
+ }
+ return vals
+
+ def _create_jornal_entry(self, wip_amount, var_amount):
+ self.ensure_one()
+ if wip_amount or var_amount:
+ accounts = self.product_id.product_tmpl_id.get_product_accounts()
+ move_lines = [
+ self._prepare_account_move_line(
+ accounts["stock_input"], debit_amount=wip_amount
+ ),
+ self._prepare_account_move_line(
+ accounts["stock_variance"], debit_amount=var_amount
+ ),
+ ]
+ if var_amount < 0:
+ move_lines.extend(
+ [
+ self._prepare_account_move_line(
+ accounts["stock_valuation"], credit_amount=wip_amount
+ ),
+ self._prepare_account_move_line(
+ accounts["stock_valuation"], credit_amount=var_amount
+ ),
+ ]
+ )
+ else:
+ move_lines.append(
+ self._prepare_account_move_line(
+ accounts["stock_valuation"],
+ credit_amount=wip_amount + var_amount,
+ )
+ )
+ wip_journal = accounts["wip_journal"]
+ if wip_journal:
+ je_vals = self._prepare_account_move(wip_journal)
+ je_vals["line_ids"] = [(0, 0, x) for x in move_lines if x]
+ je_new = self.env["account.move"].sudo().create(je_vals)
+ je_new._post()
+ # Update Analytic lines with the Consumption journal item
+ consume_move = je_new.line_ids.filtered(
+ lambda x: x.account_id == accounts["stock_valuation"]
+ )
+ self.analytic_line_ids.write({"move_id": consume_move[:1].id})
+ return bool(wip_journal)
+
+ def process_wip_and_variance(self):
+ """
+ For each Analytic Tracking Item with a Pending Amount different from zero,
+ generate Journal Entries for WIP and excess Variances
+ """
+ all_tracking = self | self.child_ids
+ for item in all_tracking.filtered("pending_amount"):
+ # TODO: use float_compare()
+ wip_pending = round(item.wip_actual_amount - item.wip_accounted_amount, 6)
+ var_pending = round(
+ item.variance_actual_amount - item.variance_accounted_amount, 6
+ )
+ is_posted = item._create_jornal_entry(wip_pending, var_pending)
+ if is_posted:
+ # Update accounted amount to equal actual amounts
+ item.accounted_amount = item.actual_amount
+ item.wip_accounted_amount = item.wip_actual_amount
+ item.variance_accounted_amount = item.variance_actual_amount
+
+ def _cron_process_wip_and_variance(self):
+ items = self.search([("state", "not in", ["close", "cancel"])])
+ items.process_wip_and_variance()
+
+ def reverse_wip_moves(self):
+ all_tracking = self | self.child_ids
+ all_tracking.write({"state": "close"})
+ wip_moves = all_tracking.mapped("account_move_ids")
+ default_values = [{"ref": _("Reversal of: %s") % x.ref} for x in wip_moves]
+ reverse_moves = wip_moves._reverse_moves(default_values)
+ reverse_moves._post()
+
+ def action_cancel(self):
+ # TODO: what to do if there are JEs done?
+ all_tracking = self | self.child_ids
+ all_tracking.write({"state": "cancel"})
diff --git a/account_analytic_wip/models/account_move.py b/account_analytic_wip/models/account_move.py
new file mode 100644
index 0000000000..865382fca0
--- /dev/null
+++ b/account_analytic_wip/models/account_move.py
@@ -0,0 +1,15 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+
+from odoo import fields, models
+
+
+class AccountMove(models.Model):
+ _inherit = "account.move"
+
+ analytic_tracking_item_id = fields.Many2one(
+ "account.analytic.tracking.item",
+ string="Tracking Item",
+ help="Tracking item generating this journal entry",
+ )
diff --git a/account_analytic_wip/models/product_category.py b/account_analytic_wip/models/product_category.py
new file mode 100644
index 0000000000..db1a2a4bc3
--- /dev/null
+++ b/account_analytic_wip/models/product_category.py
@@ -0,0 +1,48 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+
+from odoo import _, api, exceptions, fields, models
+
+
+class ProductCategory(models.Model):
+ _inherit = "product.category"
+
+ property_wip_journal_id = fields.Many2one(
+ "account.journal",
+ "WIP Journal",
+ company_dependent=True,
+ domain="[('company_id', '=', allowed_company_ids[0])]",
+ check_company=True,
+ help="When doing automated WIP valuation, this is the Accounting Journal "
+ "in which entries will be automatically posted.",
+ )
+ property_variance_account_id = fields.Many2one(
+ "account.account",
+ "Variance Account",
+ company_dependent=True,
+ domain="[('company_id', '=', allowed_company_ids[0]), "
+ "('deprecated', '=', False)]",
+ check_company=True,
+ )
+
+ @api.constrains(
+ "property_wip_journal_id",
+ "property_variance_account_id",
+ )
+ def _constrains_wip_config(self):
+ for categ in self:
+ configs = [
+ categ.property_wip_journal_id,
+ categ.property_variance_account_id,
+ ]
+ if any(configs) and not all(configs):
+ raise exceptions.ValidationError(
+ _(
+ "Then configuring costing, a Journal "
+ " and account for Consumption,"
+ " WIP and Variance must be provided. "
+ "Check the configuration in Category %s."
+ )
+ % categ.display_name
+ )
diff --git a/account_analytic_wip/models/product_template.py b/account_analytic_wip/models/product_template.py
new file mode 100644
index 0000000000..2810d04e3e
--- /dev/null
+++ b/account_analytic_wip/models/product_template.py
@@ -0,0 +1,33 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+
+from odoo import models
+
+
+class ProductTemplate(models.Model):
+ _inherit = "product.template"
+
+ def _get_product_accounts(self):
+ """
+ Add the Variance account, used to post WIP amount exceeding the expected.
+ The "Consumed" account (credited) is the stock_input,
+ and the "WIP" account (debited) is the sock_valuation account.
+ """
+ self.ensure_one()
+ accounts = super()._get_product_accounts()
+ accounts.update(
+ {
+ "stock_variance": self.categ_id.property_variance_account_id or False,
+ }
+ )
+ return accounts
+
+ def get_product_accounts(self, fiscal_pos=None):
+ """
+ Add the journal to use for WIP journal entries, 'wip_journal'
+ """
+ self.ensure_one()
+ accounts = super().get_product_accounts(fiscal_pos=fiscal_pos)
+ accounts.update({"wip_journal": self.categ_id.property_wip_journal_id or False})
+ return accounts
diff --git a/account_analytic_wip/readme/CONFIGURATION.rst b/account_analytic_wip/readme/CONFIGURATION.rst
new file mode 100644
index 0000000000..9a469b742c
--- /dev/null
+++ b/account_analytic_wip/readme/CONFIGURATION.rst
@@ -0,0 +1,7 @@
+On the Product Category, "Costing" section, configure the accounts to use:
+
+* **Consumption Account**: the account to credit.
+
+* **Work in Progress Account**: the account to debit the work in progress amounts.
+
+* **Variance Account**: the account to debit the variance versus the expected.
diff --git a/account_analytic_wip/readme/CONTRIBUTORS.rst b/account_analytic_wip/readme/CONTRIBUTORS.rst
new file mode 100644
index 0000000000..f5d2c92978
--- /dev/null
+++ b/account_analytic_wip/readme/CONTRIBUTORS.rst
@@ -0,0 +1,3 @@
+* `Open Source Integrators `:
+
+ * Daniel Reis
diff --git a/account_analytic_wip/readme/DESCRIPTION.rst b/account_analytic_wip/readme/DESCRIPTION.rst
new file mode 100644
index 0000000000..7f27125fe8
--- /dev/null
+++ b/account_analytic_wip/readme/DESCRIPTION.rst
@@ -0,0 +1,15 @@
+This feature proposes a strategy to track and report work in progress and variances.
+
+The base components are implemented here, a minimum viable process is working,
+but the process is best leveraged by other apps, such as Projects or Manufacturing.
+
+Resource consumption is to be recorded as Analytic Items
+when operations are logged in the system of resources.
+
+These Analytic Items are then used to calculate WIP and variances
+versus the original expected amounts.
+An "Analytic Tracking Items" object is used to hold the expected amount,
+and calculate the WIP and variances to record.
+
+A regular scheduled job uses that information
+to generate the corresponding accounting moves.
diff --git a/account_analytic_wip/readme/USAGE.rst b/account_analytic_wip/readme/USAGE.rst
new file mode 100644
index 0000000000..a01d184347
--- /dev/null
+++ b/account_analytic_wip/readme/USAGE.rst
@@ -0,0 +1,50 @@
+The "Analytic Tracking Items" holds planned amounts, and track their WIP and variances.
+These must be automatically created by specific logic in the Apps supporting them.
+
+With this module alone the Tracking Item creation can be done manually:
+
+* Navigate to ''Invoicing/Accounting > Reporting > Management > Analytic Tracking''
+
+* Create an Analytic Tracking Item:
+
+ * Set the Analytic Account.
+
+ * Set the Product, use one that has a non-zero cost
+ and belongs to a category with the "Costing" section configured.
+
+ * Set the Planned Amount.
+
+
+Analytic Items are used to record the actual costs:
+
+* Navigate to *Invoicing/Accounting > Configuration
+ > Analytic Accounting > Analytic Items*.
+
+* Create an Analytic Item:
+
+ * Set the Analytic Account, Description and Date.
+
+ * Set the Product, use one that has a non-zero cost
+ and belongs to a category with the "Costing" section configured.
+
+ * Set the quantity consumed.
+
+ * The Amount field should be automatically computed, with a negative amount.
+
+
+Analytic Tracking Items are used to follow the costs incurred
+and the comparison with the planned amounts. This can be used for analysis:
+
+* Navigate to ''Invoicing/Accounting > Reporting > Management > Analytic Tracking''
+
+* The list presents lines being tracked, and displays columns with Actual Amount,
+ Expected Amount, WIP Amount, Variance Amount, etc.
+
+
+WIP and variances journal entries are generated by a scheduled job:
+
+* Navigate to *Setting > Technical > Automation > Scheduled Actions*.
+
+* Locate and open the *Account: Process WIP and Variances* record, and click on the RUN MANUALLY button.
+
+* Check the generated journal entries, at *Accounting > Miscellaneous > Journal Entries*.
diff --git a/account_analytic_wip/security/ir.model.access.csv b/account_analytic_wip/security/ir.model.access.csv
new file mode 100644
index 0000000000..09c8a97f49
--- /dev/null
+++ b/account_analytic_wip/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_account_analytic_tracking_item_user,Analytic Tracking Item: User,model_account_analytic_tracking_item,base.group_user,1,1,1,1
+access_account_analytic_tracking_item_mngr,Analytic Tracking Item: Manager,model_account_analytic_tracking_item,account.group_account_manager,1,1,1,1
diff --git a/account_analytic_wip/static/description/icon.png b/account_analytic_wip/static/description/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d
GIT binary patch
literal 9455
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
z
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
Xviia!U7SGha1wx#SCgwmn*{w2TRX*I
literal 0
HcmV?d00001
diff --git a/account_analytic_wip/tests/__init__.py b/account_analytic_wip/tests/__init__.py
new file mode 100644
index 0000000000..f8ce2f2644
--- /dev/null
+++ b/account_analytic_wip/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_analytic
diff --git a/account_analytic_wip/tests/test_analytic.py b/account_analytic_wip/tests/test_analytic.py
new file mode 100644
index 0000000000..5e62e80567
--- /dev/null
+++ b/account_analytic_wip/tests/test_analytic.py
@@ -0,0 +1,53 @@
+from odoo import exceptions
+from odoo.tests import common
+
+
+class TestAnalytic(common.TransactionCase):
+ def setUp(self):
+ super().setUp()
+ self.analytic_x = self.env["account.analytic.account"].create(
+ {"name": "Analytic X"}
+ )
+ self.wip_journal = self.env["account.journal"].create(
+ {"name": "Inventory WIP", "type": "general", "code": "WIP"}
+ )
+ self.consume_account = self.env["account.account"].create(
+ {
+ "code": "600010",
+ "name": "Costing Consumed",
+ "user_type_id": self.env.ref("account.data_account_type_expenses").id,
+ }
+ )
+ self.variance_account = self.consume_account.copy(
+ {"code": "600012", "name": "Costing Variance"}
+ )
+ self.costing_categ = self.env["product.category"].create(
+ {
+ "name": "Costing",
+ "property_cost_method": "standard",
+ "property_valuation": "real_time",
+ "property_wip_journal_id": self.wip_journal.id,
+ "property_variance_account_id": self.variance_account.id,
+ }
+ )
+ self.cost_product_template = self.env["product.template"].create(
+ {
+ "name": "Cost Service",
+ "type": "service",
+ "categ_id": self.costing_categ.id,
+ }
+ )
+ self.cost_product = self.cost_product_template.product_variant_ids.write(
+ {"standard_price": 10.0}
+ )
+
+ def test_100_categ_config_complete(self):
+ with self.assertRaises(exceptions.ValidationError):
+ self.env["product.category"].create(
+ {
+ "name": "Engineer to Order",
+ "property_cost_method": "standard",
+ "property_valuation": "real_time",
+ "property_variance_account_id": self.variance_account.id,
+ }
+ )
diff --git a/account_analytic_wip/views/account_analytic_line.xml b/account_analytic_wip/views/account_analytic_line.xml
new file mode 100644
index 0000000000..03340af0fa
--- /dev/null
+++ b/account_analytic_wip/views/account_analytic_line.xml
@@ -0,0 +1,17 @@
+
+
+ Analytic Line Form: add Tracking Item
+ account.analytic.line
+
+
+
+
+
+
+
+
+
+
diff --git a/account_analytic_wip/views/account_analytic_tracking.xml b/account_analytic_wip/views/account_analytic_tracking.xml
new file mode 100644
index 0000000000..89e974022d
--- /dev/null
+++ b/account_analytic_wip/views/account_analytic_tracking.xml
@@ -0,0 +1,93 @@
+
+
+ account.analytic.tracking.form
+ account.analytic.tracking.item
+
+
+
+
+
+ account.analytic.tracking.tree
+ account.analytic.tracking.item
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Analytic Tracking
+ account.analytic.tracking.item
+ tree,form,pivot
+
+
+
diff --git a/account_analytic_wip/views/account_move.xml b/account_analytic_wip/views/account_move.xml
new file mode 100644
index 0000000000..925a60ffa4
--- /dev/null
+++ b/account_analytic_wip/views/account_move.xml
@@ -0,0 +1,17 @@
+
+
+ Account Move Form: add Tracking Item
+ account.move
+
+
+
+
+
+
+
+
+
+
diff --git a/account_analytic_wip/views/product_category.xml b/account_analytic_wip/views/product_category.xml
new file mode 100644
index 0000000000..bf599a00e9
--- /dev/null
+++ b/account_analytic_wip/views/product_category.xml
@@ -0,0 +1,26 @@
+
+
+ account.costing.product.category
+ product.category
+
+
+
+
+
+
+
+
+
+
+
From d77f61a148fcf9220afc621bd37a93bcd4435ff6 Mon Sep 17 00:00:00 2001
From: Daniel Reis
Date: Sat, 22 May 2021 17:22:09 +0100
Subject: [PATCH 02/36] [FIX] account_analytic_wip: tracking items Post button
was not working
---
account_analytic_wip/views/account_analytic_tracking.xml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/account_analytic_wip/views/account_analytic_tracking.xml b/account_analytic_wip/views/account_analytic_tracking.xml
index 89e974022d..a3641d20b2 100644
--- a/account_analytic_wip/views/account_analytic_tracking.xml
+++ b/account_analytic_wip/views/account_analytic_tracking.xml
@@ -58,7 +58,12 @@
decoration-bf="pending_amount"
>
-
+
From 37308d44ebb625ceafb0504ea1e2adaaeabd5be5 Mon Sep 17 00:00:00 2001
From: Daniel Reis
Date: Mon, 24 May 2021 14:23:20 +0100
Subject: [PATCH 03/36] [FIX] account_analytic_wip: refactored, based on field
tests
[FIX] account_analytic_wip: get accounts errors on empty Product recordsets
[FIX] analytic_activity_based_cost: don't generate multiple Timesheet lines
---
account_analytic_wip/models/__init__.py | 1 +
.../models/account_analytic.py | 28 +++
.../models/account_analytic_line.py | 5 +
.../models/account_analytic_tracked.py | 52 +++--
.../models/account_analytic_tracking.py | 205 ++++++++++--------
account_analytic_wip/models/account_move.py | 5 +
.../models/product_category.py | 20 +-
.../models/product_template.py | 2 -
.../views/product_category.xml | 7 +-
9 files changed, 201 insertions(+), 124 deletions(-)
create mode 100644 account_analytic_wip/models/account_analytic.py
diff --git a/account_analytic_wip/models/__init__.py b/account_analytic_wip/models/__init__.py
index 91bc5605af..53cf912040 100644
--- a/account_analytic_wip/models/__init__.py
+++ b/account_analytic_wip/models/__init__.py
@@ -4,6 +4,7 @@
from . import product_category
from . import product_template
from . import account_move
+from . import account_analytic
from . import account_analytic_line
from . import account_analytic_tracking
from . import account_analytic_tracked
diff --git a/account_analytic_wip/models/account_analytic.py b/account_analytic_wip/models/account_analytic.py
new file mode 100644
index 0000000000..610a45d5fe
--- /dev/null
+++ b/account_analytic_wip/models/account_analytic.py
@@ -0,0 +1,28 @@
+# Copyright (C) 2021 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+
+from odoo import api, fields, models
+
+
+class AccountAnalytic(models.Model):
+ """
+ An Analytic Account can have one or more related Tracking Items.
+ Depending on the extensions installed, you might have
+ Tracking Items per Task or per Manufacturing Order.
+ """
+
+ _inherit = "account.analytic.account"
+
+ analytic_tracking_item_ids = fields.One2many(
+ "account.analytic.tracking.item", "analytic_id", string="Tracking Items"
+ )
+
+ @api.model
+ def create(self, vals):
+ """
+ A default Tracking Item is automatically created.
+ It will collect Atual Amounts not linked to a specific Tracking Item.
+ """
+ new = super().create(vals)
+ self.env["account.analytic.tracking.item"].create({"analytic_id": new.id})
diff --git a/account_analytic_wip/models/account_analytic_line.py b/account_analytic_wip/models/account_analytic_line.py
index fa2cf876a6..1305a896f7 100644
--- a/account_analytic_wip/models/account_analytic_line.py
+++ b/account_analytic_wip/models/account_analytic_line.py
@@ -6,6 +6,11 @@
class AnalyticLine(models.Model):
+ """
+ Analytic Lines should keep a link to the corresponding Tracking Item,
+ so that it can report the corresponding WIP amounts.
+ """
+
_inherit = "account.analytic.line"
analytic_tracking_item_id = fields.Many2one(
diff --git a/account_analytic_wip/models/account_analytic_tracked.py b/account_analytic_wip/models/account_analytic_tracked.py
index 3477c99a52..706e670ba2 100644
--- a/account_analytic_wip/models/account_analytic_tracked.py
+++ b/account_analytic_wip/models/account_analytic_tracked.py
@@ -1,11 +1,15 @@
# Copyright (C) 2021 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
from odoo import api, fields, models
class AnalyticTrackedItem(models.AbstractModel):
+ """
+ Mixin to use on Models that generate WIp Analytic Items,
+ and should be linked to Tracking Items.
+ """
+
_name = "account.analytic.tracked.mixin"
_description = "Cost Tracked Mixin"
@@ -20,23 +24,18 @@ def _prepare_tracking_item_values(self):
"""
To be implemented by inheriting models.
Return a dict with the values to create the related Tracking Item.
-
- return {
- "analytic_id": ...,
- "product_id": ...,
- ...,
- }
"""
self.ensure_one()
return {}
def _get_tracking_planned_qty(self):
"""
+ Get the initial planned quantity.
To be extended by inheriting Model
"""
return 0.0
- def set_tracking_item(self, update_planned=False, force=False):
+ def set_tracking_item(self, update_planned=False):
"""
Create and set the related Tracking Item, where actuals will be accumulated to.
The _prepare_tracking_item_values() provides the values used to create it.
@@ -50,20 +49,25 @@ def set_tracking_item(self, update_planned=False, force=False):
"""
TrackingItem = self.env["account.analytic.tracking.item"]
for item in self:
- if not item.analytic_tracking_item_id or force:
+ if not item.analytic_tracking_item_id:
vals = item._prepare_tracking_item_values()
- item.analytic_tracking_item_id = vals and TrackingItem.create(vals)
- # The Product my be a Cost Type with child Products
- cost_rules = item.analytic_tracking_item_id.product_id.activity_cost_ids
- for cost_type in cost_rules.product_id:
- child_vals = dict(vals)
- child_vals.update(
- {
- "parent_id": item.analytic_tracking_item_id.id,
- "product_id": cost_type.id,
- }
+ if vals:
+ tracking_item = TrackingItem.create(vals)
+ item.analytic_tracking_item_id = tracking_item
+ # FIXME: remove this code, is is ABC logic, not WIP logic!
+ # The Product my be a Cost Type with child Products
+ cost_rules = (
+ item.analytic_tracking_item_id.product_id.activity_cost_ids
)
- TrackingItem.create(child_vals)
+ for cost_type in cost_rules.product_id:
+ child_vals = dict(vals)
+ child_vals.update(
+ {
+ "parent_id": item.analytic_tracking_item_id.id,
+ "product_id": cost_type.id,
+ }
+ )
+ TrackingItem.create(child_vals)
if update_planned and item.analytic_tracking_item_id:
planned_qty = item._get_tracking_planned_qty()
@@ -81,3 +85,11 @@ def create(self, vals):
new = super().create(vals)
new.set_tracking_item()
return new
+
+ def write(self, vals):
+ """
+ Tracked records automatically create Tracking Items, if possible.
+ """
+ res = super().write(vals)
+ self.set_tracking_item()
+ return res
diff --git a/account_analytic_wip/models/account_analytic_tracking.py b/account_analytic_wip/models/account_analytic_tracking.py
index fb79f5b5c9..ffd5f5716e 100644
--- a/account_analytic_wip/models/account_analytic_tracking.py
+++ b/account_analytic_wip/models/account_analytic_tracking.py
@@ -2,7 +2,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import _, api, fields, models
+from odoo import api, fields, models
class AnalyticTrackingItem(models.Model):
@@ -27,12 +27,22 @@ class AnalyticTrackingItem(models.Model):
product_id = fields.Many2one(
"product.product", string="Cost Product", ondelete="restrict"
)
+
+ # Related calculated data
+ company_id = fields.Many2one(
+ "res.company", related="analytic_id.company_id", store=True
+ )
+ product_categ_id = fields.Many2one(
+ "product.category", related="product_id.categ_id", store=True
+ )
+ # Analytic Items, to compute WIP actuals from
analytic_line_ids = fields.One2many(
"account.analytic.line",
"analytic_tracking_item_id",
string="Analytic Items",
help="Related analytic items with the project actuals.",
)
+ # Journal Entries, to compute Posted actuals from
account_move_ids = fields.One2many(
"account.move",
"analytic_tracking_item_id",
@@ -41,16 +51,17 @@ class AnalyticTrackingItem(models.Model):
)
state = fields.Selection(
[
- ("open", "Open"),
- ("close", "Closed"),
+ ("open", "Open"), # In progress
+ ("done", "Done"), # Completed but not Posted
+ ("close", "Locked"), # Completed and Posted
("cancel", "Cancelled"),
],
default="open",
help="Open operations are in progress, no negative variances are computed. "
"Done operations are completed, negative variances are computed. "
- "Closed operations are done and posting, no more actions to do.",
+ "Locked operations are done and posted, no more actions to do.",
)
- to_calculate = fields.Boolean(compute="_compute_to_calculate", store=True)
+ to_calculate = fields.Boolean(compute="_compute_to_calculate")
parent_id = fields.Many2one(
"account.analytic.tracking.item", "Parent Tracking Item", ondelete="cascade"
@@ -65,24 +76,29 @@ class AnalyticTrackingItem(models.Model):
# Actual Amounts
actual_amount = fields.Float(
compute="_compute_actual_amounts",
+ store=True,
help="Total cost amount of the related Analytic Items. "
"These Analytic Items are generated when a cost is incurred, "
"and will later generated WIP and Variance Journal Entries.",
)
wip_actual_amount = fields.Float(
compute="_compute_actual_amounts",
+ store=True,
help="Actual amount incurred below the planned amount limit.",
)
variance_actual_amount = fields.Float(
compute="_compute_actual_amounts",
+ store=True,
help="Actual amount incurred above the planned amount limit.",
)
remaining_actual_amount = fields.Float(
compute="_compute_actual_amounts",
+ store=True,
help="Actual amount planned and not yet consumed.",
)
pending_amount = fields.Float(
compute="_compute_actual_amounts",
+ store=True,
help="Amount not yet posted to journal entries.",
)
@@ -104,15 +120,15 @@ def _compute_name(self):
for item in self:
item.name = item.product_id.display_name
- @api.depends("state", "parent_id", "child_ids")
+ @api.depends("state", "child_ids")
def _compute_to_calculate(self):
- for tracking in self:
- tracking.to_calculate = tracking.state != "cancel" and (
- tracking.parent_id or not tracking.child_ids
- )
+ for item in self:
+ item.to_calculate = item.state != "cancel" and not item.child_ids
@api.depends(
"analytic_line_ids.amount",
+ "analytic_line_ids.unit_amount",
+ "parent_id.analytic_line_ids.amount",
"planned_amount",
"accounted_amount",
"state",
@@ -120,17 +136,14 @@ def _compute_to_calculate(self):
)
def _compute_actual_amounts(self):
for item in self:
- # Actuals
- if not item.to_calculate:
+ if not item.to_calculate or item.child_ids:
item.actual_amount = 0
else:
- if item.parent_id:
- actuals = item.parent_id.analytic_line_ids.filtered(
- lambda x: x.product_id == item.product_id
- )
- else:
- actuals = item.analytic_line_ids
- item.actual_amount = -sum(actuals.mapped("amount")) or 0.0
+ all_actuals = item.analytic_line_ids or item.parent_id.analytic_line_ids
+ product_actuals = all_actuals.filtered(
+ lambda x: x.product_id == item.product_id
+ )
+ item.actual_amount = -sum(product_actuals.mapped("amount")) or 0.0
item.pending_amount = item.actual_amount - item.accounted_amount
item.wip_actual_amount = min(item.actual_amount, item.planned_amount)
@@ -152,7 +165,7 @@ def _compute_actual_amounts(self):
item.remaining_actual_amount = 0
item.variance_actual_amount = item.actual_amount - item.planned_amount
- def _prepare_account_move(self, journal):
+ def _prepare_account_move_head(self, journal):
return {
"journal_id": journal.id,
"date": self.env.context.get(
@@ -163,102 +176,108 @@ def _prepare_account_move(self, journal):
"analytic_tracking_item_id": self.id,
}
- def _prepare_account_move_line(self, account, debit_amount=0, credit_amount=0):
- self.ensure_one()
- vals = {}
- if account and (debit_amount or credit_amount):
- debit = (debit_amount if debit_amount > 0 else 0) + (
- -credit_amount if credit_amount < 0 else 0
- )
- credit = (credit_amount if credit_amount > 0 else 0) + (
- -debit_amount if debit_amount < 0 else 0
- )
- vals = {
- "name": self.display_name,
- "product_id": self.product_id.id,
- "product_uom_id": self.product_id.uom_id.id,
- "analytic_account_id": self.analytic_id.id,
- "ref": self.display_name,
- "account_id": account.id,
- "debit": debit,
- "credit": credit,
+ def _prepare_account_move_line(self, account, amount):
+ return {
+ "name": self.display_name,
+ "product_id": self.product_id.id,
+ "product_uom_id": self.product_id.uom_id.id,
+ "analytic_account_id": self.analytic_id.id,
+ "ref": self.display_name,
+ "account_id": account.id,
+ "debit": amount if amount > 0.0 else 0.0,
+ "credit": -amount if amount < 0.0 else 0.0,
+ }
+
+ def _get_accounting_data_for_valuation(self):
+ """
+ Extension hook to set the accounts to use
+ """
+ accounts = self.product_id.product_tmpl_id.get_product_accounts()
+ categ = self.with_company(self.company_id).product_id.categ_id
+ accounts.update(
+ {
+ "wip_journal": categ.property_wip_journal_id,
+ "stock_wip": categ.property_wip_account_id,
+ "stock_variance": categ.property_variance_account_id,
}
- return vals
+ )
+ return accounts
- def _create_jornal_entry(self, wip_amount, var_amount):
- self.ensure_one()
- if wip_amount or var_amount:
- accounts = self.product_id.product_tmpl_id.get_product_accounts()
+ def _get_journal_entries_wip(self, wip_amount=0.0, variance_amount=0.0):
+ """
+ Extension hook to set the journal items for WIP records
+ """
+ return {
+ "stock_valuation": -(wip_amount + variance_amount),
+ "stock_wip": wip_amount,
+ "stock_variance": variance_amount,
+ }
+
+ def _get_journal_entries_done(self, wip_amount=0.0, variance_amount=0.0):
+ """
+ Extension hook to set the journal items for WIP records
+ once the WIP Order is done
+ """
+ return {"stock_wip": -wip_amount, "stock_output": wip_amount}
+
+ def _create_journal_entry_from_map(self, je_map):
+ """
+ Given a journal entry map, create the Journal Entry record
+ """
+ accounts = self._get_accounting_data_for_valuation()
+ wip_journal = accounts["wip_journal"]
+ posted = False
+ if wip_journal:
move_lines = [
- self._prepare_account_move_line(
- accounts["stock_input"], debit_amount=wip_amount
- ),
- self._prepare_account_move_line(
- accounts["stock_variance"], debit_amount=var_amount
- ),
+ self._prepare_account_move_line(accounts[account], amount=amount)
+ for account, amount in je_map.items()
+ if amount
]
- if var_amount < 0:
- move_lines.extend(
- [
- self._prepare_account_move_line(
- accounts["stock_valuation"], credit_amount=wip_amount
- ),
- self._prepare_account_move_line(
- accounts["stock_valuation"], credit_amount=var_amount
- ),
- ]
- )
- else:
- move_lines.append(
- self._prepare_account_move_line(
- accounts["stock_valuation"],
- credit_amount=wip_amount + var_amount,
- )
- )
- wip_journal = accounts["wip_journal"]
- if wip_journal:
- je_vals = self._prepare_account_move(wip_journal)
+ if move_lines:
+ je_vals = self._prepare_account_move_head(wip_journal)
je_vals["line_ids"] = [(0, 0, x) for x in move_lines if x]
je_new = self.env["account.move"].sudo().create(je_vals)
je_new._post()
- # Update Analytic lines with the Consumption journal item
- consume_move = je_new.line_ids.filtered(
- lambda x: x.account_id == accounts["stock_valuation"]
- )
- self.analytic_line_ids.write({"move_id": consume_move[:1].id})
- return bool(wip_journal)
+ posted = True
+ return posted
- def process_wip_and_variance(self):
+ def process_wip_and_variance(self, close=False):
"""
For each Analytic Tracking Item with a Pending Amount different from zero,
generate Journal Entries for WIP and excess Variances
"""
all_tracking = self | self.child_ids
- for item in all_tracking.filtered("pending_amount"):
- # TODO: use float_compare()
- wip_pending = round(item.wip_actual_amount - item.wip_accounted_amount, 6)
- var_pending = round(
- item.variance_actual_amount - item.variance_accounted_amount, 6
- )
- is_posted = item._create_jornal_entry(wip_pending, var_pending)
+ if close:
+ # Set to done, to have negative variances computed
+ all_tracking.write({"state": "done"})
+ # Before closing, ensure all WIP is posted
+ self.process_wip_and_variance(close=False)
+ for item in all_tracking:
+ if close:
+ wip_pending = item.wip_actual_amount
+ var_pending = item.variance_actual_amount
+ je_map = item._get_journal_entries_done(wip_pending, var_pending)
+ else:
+ wip_pending = round(
+ item.wip_actual_amount - item.wip_accounted_amount, 6
+ )
+ var_pending = round(
+ item.variance_actual_amount - item.variance_accounted_amount, 6
+ )
+ je_map = item._get_journal_entries_wip(wip_pending, var_pending)
+ is_posted = item._create_journal_entry_from_map(je_map)
if is_posted:
# Update accounted amount to equal actual amounts
item.accounted_amount = item.actual_amount
item.wip_accounted_amount = item.wip_actual_amount
item.variance_accounted_amount = item.variance_actual_amount
+ if close:
+ all_tracking.write({"state": "close"})
def _cron_process_wip_and_variance(self):
items = self.search([("state", "not in", ["close", "cancel"])])
items.process_wip_and_variance()
- def reverse_wip_moves(self):
- all_tracking = self | self.child_ids
- all_tracking.write({"state": "close"})
- wip_moves = all_tracking.mapped("account_move_ids")
- default_values = [{"ref": _("Reversal of: %s") % x.ref} for x in wip_moves]
- reverse_moves = wip_moves._reverse_moves(default_values)
- reverse_moves._post()
-
def action_cancel(self):
# TODO: what to do if there are JEs done?
all_tracking = self | self.child_ids
diff --git a/account_analytic_wip/models/account_move.py b/account_analytic_wip/models/account_move.py
index 865382fca0..6061b4e0df 100644
--- a/account_analytic_wip/models/account_move.py
+++ b/account_analytic_wip/models/account_move.py
@@ -6,6 +6,11 @@
class AccountMove(models.Model):
+ """
+ Journal Entries are linked to Tracking Items
+ to allow computing the actual amounts
+ """
+
_inherit = "account.move"
analytic_tracking_item_id = fields.Many2one(
diff --git a/account_analytic_wip/models/product_category.py b/account_analytic_wip/models/product_category.py
index db1a2a4bc3..60f3892a2f 100644
--- a/account_analytic_wip/models/product_category.py
+++ b/account_analytic_wip/models/product_category.py
@@ -14,8 +14,16 @@ class ProductCategory(models.Model):
company_dependent=True,
domain="[('company_id', '=', allowed_company_ids[0])]",
check_company=True,
- help="When doing automated WIP valuation, this is the Accounting Journal "
- "in which entries will be automatically posted.",
+ help="Set to enable WIP accounting. When doing automated WIP valuation,"
+ " this is the Accounting Journal in which entries will be posted.",
+ )
+ property_wip_account_id = fields.Many2one(
+ "account.account",
+ "WIP Account",
+ company_dependent=True,
+ domain="[('company_id', '=', allowed_company_ids[0]), "
+ "('deprecated', '=', False)]",
+ check_company=True,
)
property_variance_account_id = fields.Many2one(
"account.account",
@@ -28,21 +36,21 @@ class ProductCategory(models.Model):
@api.constrains(
"property_wip_journal_id",
+ "property_cost_wip_account_id",
"property_variance_account_id",
)
def _constrains_wip_config(self):
for categ in self:
configs = [
categ.property_wip_journal_id,
+ categ.property_wip_account_id,
categ.property_variance_account_id,
]
if any(configs) and not all(configs):
raise exceptions.ValidationError(
_(
- "Then configuring costing, a Journal "
- " and account for Consumption,"
- " WIP and Variance must be provided. "
- "Check the configuration in Category %s."
+ "When WIP Journal is set, WIP and Variance accounts"
+ " must also be set."
)
% categ.display_name
)
diff --git a/account_analytic_wip/models/product_template.py b/account_analytic_wip/models/product_template.py
index 2810d04e3e..603254d203 100644
--- a/account_analytic_wip/models/product_template.py
+++ b/account_analytic_wip/models/product_template.py
@@ -14,7 +14,6 @@ def _get_product_accounts(self):
The "Consumed" account (credited) is the stock_input,
and the "WIP" account (debited) is the sock_valuation account.
"""
- self.ensure_one()
accounts = super()._get_product_accounts()
accounts.update(
{
@@ -27,7 +26,6 @@ def get_product_accounts(self, fiscal_pos=None):
"""
Add the journal to use for WIP journal entries, 'wip_journal'
"""
- self.ensure_one()
accounts = super().get_product_accounts(fiscal_pos=fiscal_pos)
accounts.update({"wip_journal": self.categ_id.property_wip_journal_id or False})
return accounts
diff --git a/account_analytic_wip/views/product_category.xml b/account_analytic_wip/views/product_category.xml
index bf599a00e9..ae0c07a558 100644
--- a/account_analytic_wip/views/product_category.xml
+++ b/account_analytic_wip/views/product_category.xml
@@ -7,13 +7,14 @@
+
Date: Tue, 22 Jun 2021 16:29:47 +0100
Subject: [PATCH 04/36] [REF] account_analytic_wip: merge
analytic_activity_based_cost feature to simplify dependencies
---
account_analytic_wip/README.rst | 178 +++++-
account_analytic_wip/__manifest__.py | 2 +
.../i18n/account_analytic_wip.pot | 549 ++++++++++++++++++
account_analytic_wip/models/__init__.py | 1 +
.../models/account_analytic.py | 1 +
.../models/account_analytic_line.py | 104 +++-
.../models/activity_cost_rule.py | 23 +
.../models/product_category.py | 6 +-
.../models/product_template.py | 35 +-
account_analytic_wip/readme/CONFIGURATION.rst | 11 +
account_analytic_wip/readme/CONTRIBUTORS.rst | 1 +
account_analytic_wip/readme/DESCRIPTION.rst | 8 +
account_analytic_wip/readme/USAGE.rst | 12 +
.../security/ir.model.access.csv | 2 +
.../static/description/index.html | 507 ++++++++++++++++
account_analytic_wip/tests/test_analytic.py | 5 +
.../views/account_analytic_line.xml | 25 +
.../views/activity_cost_rule_views.xml | 54 ++
.../views/product_template.xml | 90 +++
19 files changed, 1605 insertions(+), 9 deletions(-)
create mode 100644 account_analytic_wip/i18n/account_analytic_wip.pot
create mode 100644 account_analytic_wip/models/activity_cost_rule.py
create mode 100644 account_analytic_wip/static/description/index.html
create mode 100644 account_analytic_wip/views/activity_cost_rule_views.xml
create mode 100644 account_analytic_wip/views/product_template.xml
diff --git a/account_analytic_wip/README.rst b/account_analytic_wip/README.rst
index 876fdb5f40..fcc0a02faa 100644
--- a/account_analytic_wip/README.rst
+++ b/account_analytic_wip/README.rst
@@ -1 +1,177 @@
-Generated
+=================================================
+Analytic Accounting support for WIP and Variances
+=================================================
+
+.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Alpha
+.. |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--analytic-lightgray.png?logo=github
+ :target: https://github.com/OCA/account-analytic/tree/14.0/account_analytic_wip
+ :alt: OCA/account-analytic
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/account-analytic-14-0/account-analytic-14-0-account_analytic_wip
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
+ :target: https://runbot.odoo-community.org/runbot/87/14.0
+ :alt: Try me on Runbot
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This feature proposes a strategy to track and report work in progress and variances.
+The work in progress can be split in subitems, such as Labour and Overhead.
+
+The base components are implemented here, a minimum viable process is working,
+but the process is best leveraged by other apps, such as Projects or Manufacturing.
+
+Resource consumption is to be recorded as Analytic Items
+when operations are logged in the system of resources.
+
+These Analytic Items are then used to calculate WIP and variances
+versus the original expected amounts.
+An "Analytic Tracking Items" object is used to hold the expected amount,
+and calculate the WIP and variances to record.
+
+A regular scheduled job uses that information
+to generate the corresponding accounting moves.
+
+Products can be seen as cost drivers, driving consumption of other items.
+For example a machine work time can drive consumptions of Labor and Overhead.
+
+This feature models cost driver usage as Analytic Items.
+When an Analytic Item is created, it may then generate additional Analytic Items for the corresponding indirect costs.
+For example, each timesheet hour logged could generate a quantity and amount of overhead assigned to that activity.
+
+.. IMPORTANT::
+ This is an alpha version, the data model and design can change at any time without warning.
+ Only for development or testing purpose, do not use in production.
+ `More details on development status `_
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+The "Analytic Tracking Items" holds planned amounts, and track their WIP and variances.
+These must be automatically created by specific logic in the Apps supporting them.
+
+With this module alone the Tracking Item creation can be done manually:
+
+* Navigate to ''Invoicing/Accounting > Reporting > Management > Analytic Tracking''
+
+* Create an Analytic Tracking Item:
+
+ * Set the Analytic Account.
+
+ * Set the Product, use one that has a non-zero cost
+ and belongs to a category with the "Costing" section configured.
+
+ * Set the Planned Amount.
+
+
+Analytic Items are used to record the actual costs:
+
+* Navigate to *Invoicing/Accounting > Configuration
+ > Analytic Accounting > Analytic Items*.
+
+* Create an Analytic Item:
+
+ * Set the Analytic Account, Description and Date.
+
+ * Set the Product, use one that has a non-zero cost
+ and belongs to a category with the "Costing" section configured.
+
+ * Set the quantity consumed.
+
+ * The Amount field should be automatically computed, with a negative amount.
+
+
+Analytic Tracking Items are used to follow the costs incurred
+and the comparison with the planned amounts. This can be used for analysis:
+
+* Navigate to ''Invoicing/Accounting > Reporting > Management > Analytic Tracking''
+
+* The list presents lines being tracked, and displays columns with Actual Amount,
+ Expected Amount, WIP Amount, Variance Amount, etc.
+
+
+WIP and variances journal entries are generated by a scheduled job:
+
+* Navigate to *Setting > Technical > Automation > Scheduled Actions*.
+
+* Locate and open the *Account: Process WIP and Variances* record, and click on the RUN MANUALLY button.
+
+* Check the generated journal entries, at *Accounting > Miscellaneous > Journal Entries*.
+
+
+When creating Analytic Items, if a configuration is in place, the corresponding Analytic Items for indirect cost are generated.
+
+* When an Analytic Item is created, an automatic process checks the Activity Based Cost Rules to identify the ones that apply.
+* Each triggered rule created a new Analytic Item, with a copy of the original one, and:
+ * Product: is the rule Cost Type Product. A validation error prevents this from being the same as the source Analytic Item Product, to avoid infinite loops.
+ * Quantity: is the original quantity multiplied by the rule's Factor
+ * Amount: is -1 * Quantity * Product Standard Price
+ * Parent Analytic Item (new field): set with the original Analytic Item
+* An update on the Quantity triggers a recalculation of the quantity and amount of the child Analytic Items.
+* A delete cascades to the child Analytic Items, causing them to also be deleted.
+
+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 smashing it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Open Source Integrators
+
+Contributors
+~~~~~~~~~~~~
+
+* `Open Source Integrators `:
+
+ * Daniel Reis
+ * Chandresh Thakkar
+
+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-dreispt| image:: https://github.com/dreispt.png?size=40px
+ :target: https://github.com/dreispt
+ :alt: dreispt
+
+Current `maintainer `__:
+
+|maintainer-dreispt|
+
+This module is part of the `OCA/account-analytic `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/account_analytic_wip/__manifest__.py b/account_analytic_wip/__manifest__.py
index ad91e899fa..40b82286cc 100644
--- a/account_analytic_wip/__manifest__.py
+++ b/account_analytic_wip/__manifest__.py
@@ -13,7 +13,9 @@
"data": [
"security/ir.model.access.csv",
"data/ir_cron_data.xml",
+ "views/activity_cost_rule_views.xml",
"views/product_category.xml",
+ "views/product_template.xml",
"views/account_move.xml",
"views/account_analytic_line.xml",
"views/account_analytic_tracking.xml",
diff --git a/account_analytic_wip/i18n/account_analytic_wip.pot b/account_analytic_wip/i18n/account_analytic_wip.pot
new file mode 100644
index 0000000000..f45b9096b4
--- /dev/null
+++ b/account_analytic_wip/i18n/account_analytic_wip.pot
@@ -0,0 +1,549 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * account_analytic_wip
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.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: account_analytic_wip
+#: model:ir.actions.server,name:account_analytic_wip.ir_cron_post_wip_ir_actions_server
+#: model:ir.cron,cron_name:account_analytic_wip.ir_cron_post_wip
+#: model:ir.cron,name:account_analytic_wip.ir_cron_post_wip
+msgid "Account Analytic: post WIP and Variances journal entries"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_form
+msgid "Accounted"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__accounted_amount
+msgid "Accounted Amount"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__variance_accounted_amount
+msgid "Accounted amount incurred above the planned amount limit."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__wip_accounted_amount
+msgid "Accounted amount incurred below the planned amount limit."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__active
+msgid "Active"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.actions.act_window,name:account_analytic_wip.action_activity_cost_rule
+#: model:ir.ui.menu,name:account_analytic_wip.menu_activity_cost_rule
+msgid "Activity Based Cost Rules"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_activity_cost_rule
+msgid "Activity Cost Rule"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product__activity_cost_ids
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.view_analytic_line_form
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.view_product_product_form
+msgid "Activity Costs"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__parent_id
+msgid "Activity Product"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "Actual"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__actual_amount
+msgid "Actual Amount"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__variance_actual_amount
+msgid "Actual amount incurred above the planned amount limit."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__wip_actual_amount
+msgid "Actual amount incurred below the planned amount limit."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__remaining_actual_amount
+msgid "Actual amount planned and not yet consumed."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_form
+msgid "Actuals"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__accounted_amount
+msgid ""
+"Amount accounted in Journal Entries. Directly set by the routine creating "
+"the Journal Entries, and not directly read from the jpunral items."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__pending_amount
+msgid "Amount not yet posted to journal entries."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_account_analytic_account
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__analytic_id
+msgid "Analytic Account"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__analytic_line_ids
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_form
+msgid "Analytic Items"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_account_analytic_line
+msgid "Analytic Line"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.actions.act_window,name:account_analytic_wip.account_analytic_tracking_action
+msgid "Analytic Tracking"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.ui.menu,name:account_analytic_wip.account_analytic_tracking_menu
+msgid "Analytic Tracking Items"
+msgstr ""
+
+#. module: account_analytic_wip
+#: code:addons/account_analytic_wip/models/product_template.py:0
+#, python-format
+msgid "Can't have Activity Costs set if it is not a Cost Type."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields.selection,name:account_analytic_wip.selection__account_analytic_tracking_item__state__cancel
+msgid "Cancelled"
+msgstr ""
+
+#. module: account_analytic_wip
+#: code:addons/account_analytic_wip/models/product_category.py:0
+#, python-format
+msgid ""
+"Category %s has WIP Journal is set, so the WIP and Variance accounts must "
+"also be set."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__child_ids
+msgid "Child Tracking Items"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__company_id
+msgid "Company"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.view_form_activity_cost_rule
+msgid "Conditions"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__standard_price
+msgid "Cost"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.view_form_activity_cost_rule
+msgid "Cost Generated"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__product_id
+msgid "Cost Product"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__activity_cost_id
+msgid "Cost Rule Applied"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_account_analytic_tracked_mixin
+msgid "Cost Tracked Mixin"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_account_analytic_tracking_item
+msgid "Cost Tracking Item"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__product_id
+msgid "Cost Type Product"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.actions.act_window,name:account_analytic_wip.product_product_action_cost_type
+#: model:ir.ui.menu,name:account_analytic_wip.product_product_menu_cost_type
+msgid "Cost Types"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.actions.act_window,help:account_analytic_wip.product_product_action_cost_type
+msgid "Create a new cost type product"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__create_uid
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__create_uid
+msgid "Created by"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__create_date
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__create_date
+msgid "Created on"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__date
+msgid "Date"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__name
+msgid "Description"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_account__display_name
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__display_name
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracked_mixin__display_name
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__display_name
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move__display_name
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__display_name
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_category__display_name
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product__display_name
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_template__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields.selection,name:account_analytic_wip.selection__account_analytic_tracking_item__state__done
+msgid "Done"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_account__id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracked_mixin__id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move__id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_category__id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product__id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_template__id
+msgid "ID"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_activity_cost_rule__standard_price
+msgid ""
+"In Standard Price & AVCO: value of the product (automatically computed in AVCO).\n"
+" In FIFO: value of the last unit that left the stock (automatically computed).\n"
+" Used to value the product when the purchase cost is not known (e.g. inventory adjustment).\n"
+" Used to compute margins on sale orders."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product__is_cost_type
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_template__is_cost_type
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.product_search_form_view
+msgid "Is Cost Type"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__account_move_ids
+msgid "Journal Entries"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_account_move
+msgid "Journal Entry"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_account____last_update
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line____last_update
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracked_mixin____last_update
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item____last_update
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move____last_update
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule____last_update
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_category____last_update
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product____last_update
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_template____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__write_uid
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__write_uid
+msgid "Last Updated by"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__write_date
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__write_date
+msgid "Last Updated on"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields.selection,name:account_analytic_wip.selection__account_analytic_tracking_item__state__close
+msgid "Locked"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__name
+msgid "Name"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields.selection,name:account_analytic_wip.selection__account_analytic_tracking_item__state__open
+msgid "Open"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__state
+msgid ""
+"Open operations are in progress, no negative variances are computed. Done "
+"operations are completed, negative variances are computed. Locked operations"
+" are done and posted, no more actions to do."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__parent_id
+msgid "Parent Analytic Item"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__parent_id
+msgid "Parent Tracking Item"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__pending_amount
+msgid "Pending Amount"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "Planned"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__planned_amount
+msgid "Planned Amount"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "Post"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "Posted"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_form
+msgid "Process"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_product_product
+msgid "Product"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_product_category
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__product_categ_id
+msgid "Product Category"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_product_template
+msgid "Product Template"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__factor
+msgid "Qty. Factor"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__child_ids
+msgid "Related Analytic Items"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__analytic_line_ids
+msgid "Related analytic items with the project actuals."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__account_move_ids
+msgid "Related journal entries with the posted WIP."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "Remaining"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__remaining_actual_amount
+msgid "Remaining Actual Amount"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__product_categ_id
+msgid "Select category for the current product"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_product_category__property_wip_journal_id
+msgid ""
+"Set to enable WIP accounting. When doing automated WIP valuation, this is "
+"the Accounting Journal in which entries will be posted."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__state
+msgid "State"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_product_product__activity_cost_ids
+msgid ""
+"This product will also generate analytic items for these Activity Costs"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__to_calculate
+msgid "To Calculate"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "To Post"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__actual_amount
+msgid ""
+"Total cost amount of the related Analytic Items. These Analytic Items are "
+"generated when a cost is incurred, and will later generated WIP and Variance"
+" Journal Entries."
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__analytic_tracking_item_id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracked_mixin__analytic_tracking_item_id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_bank_statement_line__analytic_tracking_item_id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move__analytic_tracking_item_id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_payment__analytic_tracking_item_id
+msgid "Tracking Item"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_account__analytic_tracking_item_ids
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_form
+msgid "Tracking Items"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "Tracking List"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,help:account_analytic_wip.field_account_bank_statement_line__analytic_tracking_item_id
+#: model:ir.model.fields,help:account_analytic_wip.field_account_move__analytic_tracking_item_id
+#: model:ir.model.fields,help:account_analytic_wip.field_account_payment__analytic_tracking_item_id
+msgid "Tracking item generating this journal entry"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "Variance"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_category__property_variance_account_id
+msgid "Variance Account"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__variance_accounted_amount
+msgid "Variance Accounted Amount"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__variance_actual_amount
+msgid "Variance Actual Amount"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "WIP"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_category__property_wip_account_id
+msgid "WIP Account"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_product_category__property_wip_journal_id
+msgid "WIP Journal"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.view_account_costing_product_category
+msgid "WIP and Variance"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__wip_accounted_amount
+msgid "Wip Accounted Amount"
+msgstr ""
+
+#. module: account_analytic_wip
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__wip_actual_amount
+msgid "Wip Actual Amount"
+msgstr ""
diff --git a/account_analytic_wip/models/__init__.py b/account_analytic_wip/models/__init__.py
index 53cf912040..8276638480 100644
--- a/account_analytic_wip/models/__init__.py
+++ b/account_analytic_wip/models/__init__.py
@@ -8,3 +8,4 @@
from . import account_analytic_line
from . import account_analytic_tracking
from . import account_analytic_tracked
+from . import activity_cost_rule
diff --git a/account_analytic_wip/models/account_analytic.py b/account_analytic_wip/models/account_analytic.py
index 610a45d5fe..bb26db5cdd 100644
--- a/account_analytic_wip/models/account_analytic.py
+++ b/account_analytic_wip/models/account_analytic.py
@@ -26,3 +26,4 @@ def create(self, vals):
"""
new = super().create(vals)
self.env["account.analytic.tracking.item"].create({"analytic_id": new.id})
+ return new
diff --git a/account_analytic_wip/models/account_analytic_line.py b/account_analytic_wip/models/account_analytic_line.py
index 1305a896f7..bb5c6d675f 100644
--- a/account_analytic_wip/models/account_analytic_line.py
+++ b/account_analytic_wip/models/account_analytic_line.py
@@ -2,7 +2,11 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import fields, models
+import logging
+
+from odoo import api, fields, models
+
+_logger = logging.getLogger(__name__)
class AnalyticLine(models.Model):
@@ -16,3 +20,101 @@ class AnalyticLine(models.Model):
analytic_tracking_item_id = fields.Many2one(
"account.analytic.tracking.item", string="Tracking Item"
)
+ parent_id = fields.Many2one(
+ "account.analytic.line", "Parent Analytic Item", ondelete="cascade"
+ )
+ child_ids = fields.One2many(
+ "account.analytic.line", "parent_id", string="Related Analytic Items"
+ )
+ activity_cost_id = fields.Many2one(
+ "activity.cost.rule", "Cost Rule Applied", ondelete="restrict"
+ )
+
+ @api.onchange("product_id", "product_uom_id", "unit_amount", "currency_id")
+ def on_change_unit_amount(self):
+ """ Do not set Amount on cost parent Analytic Items """
+ super().on_change_unit_amount()
+ if self.child_ids:
+ self.amount = 0
+
+ def _prepare_activity_cost_data(self, cost_type):
+ """
+ Return a dict with the values to create
+ a new Analytic item for a Cost Type.
+ """
+ self.ensure_one()
+ values = {
+ "name": "{} / {}".format(
+ self.name, cost_type.product_id.display_name or cost_type.name
+ ),
+ "activity_cost_id": cost_type.id,
+ "product_id": cost_type.product_id.id,
+ "unit_amount": self.unit_amount * cost_type.factor,
+ }
+ # Remove the link the the Project Task,
+ # otherwise the cost lines would wrongly show as Timesheet
+ if hasattr(self, "project_id"):
+ values["project_id"] = None
+ if hasattr(self, "task_id"):
+ values["task_id"] = None
+ return values
+
+ def _set_tracking_item(self):
+ """
+ When creating a child Analytic Item,
+ find the correct matching child Tracking Item
+ """
+ for analytic_item in self.filtered("parent_id.analytic_tracking_item_id"):
+ tracking_items = analytic_item.parent_id.analytic_tracking_item_id.child_ids
+ tracking_item = tracking_items.filtered(
+ lambda x: x.product_id == analytic_item.product_id
+ )
+ analytic_item.analytic_tracking_item_id = tracking_item
+ if not tracking_item:
+ _logger.error(
+ "Analytic Item %s: could not find related Tracked Item",
+ analytic_item.display_name,
+ )
+
+ def _create_child_lines(self):
+ """
+ Find applicable Activity Cost Rules
+ and create Analytic Lines for each of them.
+
+ This is done copying the original Analytic Item
+ to ensure all other fields are preserved on the new Item.
+ """
+ for analytic_parent in self.filtered("product_id.activity_cost_ids"):
+ cost_ids = analytic_parent.product_id.activity_cost_ids
+ if cost_ids:
+ # Parent Cost Type amount must be zero
+ # to avoid duplication with child cost type amounts
+ analytic_parent.amount = 0
+ for cost_product in cost_ids:
+ cost_vals = analytic_parent._prepare_activity_cost_data(
+ cost_type=cost_product
+ )
+ cost_vals["parent_id"] = analytic_parent.id
+ analytic_child = analytic_parent.copy(cost_vals)
+ analytic_child.on_change_unit_amount()
+
+ @api.model
+ def create(self, vals):
+ res = super().create(vals)
+ res._set_tracking_item()
+ res._create_child_lines()
+ return res
+
+ def write(self, vals):
+ """
+ If Units are updated, also update the related cost Analytic Items
+ """
+ res = super().write(vals)
+ if vals.get("unit_amount"):
+ for analytic_child in self.mapped("child_ids"):
+ cost_vals = analytic_child._prepare_activity_cost_data(
+ cost_type=analytic_child.activity_cost_id
+ )
+ analytic_child.write(cost_vals)
+ analytic_child.on_change_unit_amount()
+ return res
diff --git a/account_analytic_wip/models/activity_cost_rule.py b/account_analytic_wip/models/activity_cost_rule.py
new file mode 100644
index 0000000000..94e5740eee
--- /dev/null
+++ b/account_analytic_wip/models/activity_cost_rule.py
@@ -0,0 +1,23 @@
+# Copyright (C) 2020 Open Source Integrators
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models
+
+
+class ActivityCostRule(models.Model):
+ _name = "activity.cost.rule"
+ _description = "Activity Cost Rule"
+
+ name = fields.Char("Description")
+ active = fields.Boolean(default=True)
+ parent_id = fields.Many2one("product.product", "Activity Product")
+ product_id = fields.Many2one(
+ "product.product",
+ string="Cost Type Product",
+ domain=[("is_cost_type", "=", True)],
+ )
+ factor = fields.Float("Qty. Factor", default=1)
+ standard_price = fields.Float(
+ related="product_id.standard_price",
+ readonly=False,
+ )
diff --git a/account_analytic_wip/models/product_category.py b/account_analytic_wip/models/product_category.py
index 60f3892a2f..0b053269d2 100644
--- a/account_analytic_wip/models/product_category.py
+++ b/account_analytic_wip/models/product_category.py
@@ -36,7 +36,7 @@ class ProductCategory(models.Model):
@api.constrains(
"property_wip_journal_id",
- "property_cost_wip_account_id",
+ "property_wip_account_id",
"property_variance_account_id",
)
def _constrains_wip_config(self):
@@ -49,8 +49,8 @@ def _constrains_wip_config(self):
if any(configs) and not all(configs):
raise exceptions.ValidationError(
_(
- "When WIP Journal is set, WIP and Variance accounts"
- " must also be set."
+ "Category %s has WIP Journal is set"
+ ", so the WIP and Variance accounts must also be set."
)
% categ.display_name
)
diff --git a/account_analytic_wip/models/product_template.py b/account_analytic_wip/models/product_template.py
index 603254d203..ec056de330 100644
--- a/account_analytic_wip/models/product_template.py
+++ b/account_analytic_wip/models/product_template.py
@@ -2,12 +2,14 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import models
+from odoo import _, api, exceptions, fields, models
class ProductTemplate(models.Model):
_inherit = "product.template"
+ is_cost_type = fields.Boolean()
+
def _get_product_accounts(self):
"""
Add the Variance account, used to post WIP amount exceeding the expected.
@@ -16,9 +18,7 @@ def _get_product_accounts(self):
"""
accounts = super()._get_product_accounts()
accounts.update(
- {
- "stock_variance": self.categ_id.property_variance_account_id or False,
- }
+ {"stock_variance": self.categ_id.property_variance_account_id or False}
)
return accounts
@@ -29,3 +29,30 @@ def get_product_accounts(self, fiscal_pos=None):
accounts = super().get_product_accounts(fiscal_pos=fiscal_pos)
accounts.update({"wip_journal": self.categ_id.property_wip_journal_id or False})
return accounts
+
+
+class Product(models.Model):
+ _inherit = "product.product"
+
+ activity_cost_ids = fields.One2many(
+ "activity.cost.rule",
+ "parent_id",
+ string="Activity Costs",
+ help="This product will also generate analytic items for these Activity Costs",
+ )
+
+ @api.constrains("is_cost_type", "activity_cost_ids")
+ def constrains_is_cost_type(self):
+ for product in self:
+ if not product.is_cost_type and product.activity_cost_ids:
+ raise exceptions.ValidationError(
+ _("Can't have Activity Costs set if it is not a Cost Type.")
+ )
+
+ @api.onchange("activity_cost_ids")
+ def onchange_for_standard_price(self):
+ "Rollup Activity Costs to parent Cost Type"
+ for product in self.filtered("is_cost_type").filtered("activity_cost_ids"):
+ product.standard_price = sum(
+ product.mapped("activity_cost_ids.standard_price")
+ )
diff --git a/account_analytic_wip/readme/CONFIGURATION.rst b/account_analytic_wip/readme/CONFIGURATION.rst
index 9a469b742c..e0911ceab6 100644
--- a/account_analytic_wip/readme/CONFIGURATION.rst
+++ b/account_analytic_wip/readme/CONFIGURATION.rst
@@ -5,3 +5,14 @@ On the Product Category, "Costing" section, configure the accounts to use:
* **Work in Progress Account**: the account to debit the work in progress amounts.
* **Variance Account**: the account to debit the variance versus the expected.
+
+Enable the Analytic Accounting setting.
+
+Create the Products representing the Cost Types to use:
+
+* Go to Invoicing/Accounting > ... > Products, and create Products representing the Cost Types to use.
+ On a Cost Type product, set:
+
+ * "Is Cost Type" flag: checked. This will make the "Activity Costs" tab visible
+ * In the "Activity Costs" tab, add a line for each cost line to generate, such as overhead, etc.
+ Set the standard cost to use for each unit used. Thesw will roll up to the parent cost type standard cost.
diff --git a/account_analytic_wip/readme/CONTRIBUTORS.rst b/account_analytic_wip/readme/CONTRIBUTORS.rst
index f5d2c92978..b7af586b0b 100644
--- a/account_analytic_wip/readme/CONTRIBUTORS.rst
+++ b/account_analytic_wip/readme/CONTRIBUTORS.rst
@@ -1,3 +1,4 @@
* `Open Source Integrators `:
* Daniel Reis
+ * Chandresh Thakkar
diff --git a/account_analytic_wip/readme/DESCRIPTION.rst b/account_analytic_wip/readme/DESCRIPTION.rst
index 7f27125fe8..bdf797974e 100644
--- a/account_analytic_wip/readme/DESCRIPTION.rst
+++ b/account_analytic_wip/readme/DESCRIPTION.rst
@@ -1,4 +1,5 @@
This feature proposes a strategy to track and report work in progress and variances.
+The work in progress can be split in subitems, such as Labour and Overhead.
The base components are implemented here, a minimum viable process is working,
but the process is best leveraged by other apps, such as Projects or Manufacturing.
@@ -13,3 +14,10 @@ and calculate the WIP and variances to record.
A regular scheduled job uses that information
to generate the corresponding accounting moves.
+
+Products can be seen as cost drivers, driving consumption of other items.
+For example a machine work time can drive consumptions of Labor and Overhead.
+
+This feature models cost driver usage as Analytic Items.
+When an Analytic Item is created, it may then generate additional Analytic Items for the corresponding indirect costs.
+For example, each timesheet hour logged could generate a quantity and amount of overhead assigned to that activity.
diff --git a/account_analytic_wip/readme/USAGE.rst b/account_analytic_wip/readme/USAGE.rst
index a01d184347..3c31a6f52c 100644
--- a/account_analytic_wip/readme/USAGE.rst
+++ b/account_analytic_wip/readme/USAGE.rst
@@ -48,3 +48,15 @@ WIP and variances journal entries are generated by a scheduled job:
* Locate and open the *Account: Process WIP and Variances* record, and click on the RUN MANUALLY button.
* Check the generated journal entries, at *Accounting > Miscellaneous > Journal Entries*.
+
+
+When creating Analytic Items, if a configuration is in place, the corresponding Analytic Items for indirect cost are generated.
+
+* When an Analytic Item is created, an automatic process checks the Activity Based Cost Rules to identify the ones that apply.
+* Each triggered rule created a new Analytic Item, with a copy of the original one, and:
+ * Product: is the rule Cost Type Product. A validation error prevents this from being the same as the source Analytic Item Product, to avoid infinite loops.
+ * Quantity: is the original quantity multiplied by the rule's Factor
+ * Amount: is -1 * Quantity * Product Standard Price
+ * Parent Analytic Item (new field): set with the original Analytic Item
+* An update on the Quantity triggers a recalculation of the quantity and amount of the child Analytic Items.
+* A delete cascades to the child Analytic Items, causing them to also be deleted.
diff --git a/account_analytic_wip/security/ir.model.access.csv b/account_analytic_wip/security/ir.model.access.csv
index 09c8a97f49..081bae21c5 100644
--- a/account_analytic_wip/security/ir.model.access.csv
+++ b/account_analytic_wip/security/ir.model.access.csv
@@ -1,3 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_analytic_tracking_item_user,Analytic Tracking Item: User,model_account_analytic_tracking_item,base.group_user,1,1,1,1
access_account_analytic_tracking_item_mngr,Analytic Tracking Item: Manager,model_account_analytic_tracking_item,account.group_account_manager,1,1,1,1
+access_activity_cost_rule_user,access_activity_cost_rule_user,model_activity_cost_rule,base.group_user,1,0,0,0
+access_activity_cost_rule_mngr,access_activity_cost_rule_mngr,model_activity_cost_rule,account.group_account_manager,1,1,1,1
diff --git a/account_analytic_wip/static/description/index.html b/account_analytic_wip/static/description/index.html
new file mode 100644
index 0000000000..0f45b160ec
--- /dev/null
+++ b/account_analytic_wip/static/description/index.html
@@ -0,0 +1,507 @@
+
+
+
+
+
+
+Analytic Accounting support for WIP and Variances
+
+
+
+
+
Analytic Accounting support for WIP and Variances
+
+
+
+
This feature proposes a strategy to track and report work in progress and variances.
+The work in progress can be split in subitems, such as Labour and Overhead.
+
The base components are implemented here, a minimum viable process is working,
+but the process is best leveraged by other apps, such as Projects or Manufacturing.
+
Resource consumption is to be recorded as Analytic Items
+when operations are logged in the system of resources.
+
These Analytic Items are then used to calculate WIP and variances
+versus the original expected amounts.
+An “Analytic Tracking Items” object is used to hold the expected amount,
+and calculate the WIP and variances to record.
+
A regular scheduled job uses that information
+to generate the corresponding accounting moves.
+
Products can be seen as cost drivers, driving consumption of other items.
+For example a machine work time can drive consumptions of Labor and Overhead.
+
This feature models cost driver usage as Analytic Items.
+When an Analytic Item is created, it may then generate additional Analytic Items for the corresponding indirect costs.
+For example, each timesheet hour logged could generate a quantity and amount of overhead assigned to that activity.
+
+
Important
+
This is an alpha version, the data model and design can change at any time without warning.
+Only for development or testing purpose, do not use in production.
+More details on development status
The “Analytic Tracking Items” holds planned amounts, and track their WIP and variances.
+These must be automatically created by specific logic in the Apps supporting them.
+
With this module alone the Tracking Item creation can be done manually:
+
+
Navigate to ‘’Invoicing/Accounting > Reporting > Management > Analytic Tracking’’
+
Create an Analytic Tracking Item:
+
Set the Analytic Account.
+
Set the Product, use one that has a non-zero cost
+and belongs to a category with the “Costing” section configured.
+
Set the Planned Amount.
+
+
+
+
Analytic Items are used to record the actual costs:
Set the Product, use one that has a non-zero cost
+and belongs to a category with the “Costing” section configured.
+
Set the quantity consumed.
+
The Amount field should be automatically computed, with a negative amount.
+
+
+
+
Analytic Tracking Items are used to follow the costs incurred
+and the comparison with the planned amounts. This can be used for analysis:
+
+
Navigate to ‘’Invoicing/Accounting > Reporting > Management > Analytic Tracking’’
+
The list presents lines being tracked, and displays columns with Actual Amount,
+Expected Amount, WIP Amount, Variance Amount, etc.
+
+
WIP and variances journal entries are generated by a scheduled job:
+
+
Navigate to Setting > Technical > Automation > Scheduled Actions.
+
Locate and open the Account: Process WIP and Variances record, and click on the RUN MANUALLY button.
+
Check the generated journal entries, at Accounting > Miscellaneous > Journal Entries.
+
+
When creating Analytic Items, if a configuration is in place, the corresponding Analytic Items for indirect cost are generated.
+
+
When an Analytic Item is created, an automatic process checks the Activity Based Cost Rules to identify the ones that apply.
+
+
Each triggered rule created a new Analytic Item, with a copy of the original one, and:
+
+
Product: is the rule Cost Type Product. A validation error prevents this from being the same as the source Analytic Item Product, to avoid infinite loops.
+
Quantity: is the original quantity multiplied by the rule’s Factor
+
Amount: is -1 * Quantity * Product Standard Price
+
Parent Analytic Item (new field): set with the original Analytic Item
+
+
+
+
+
An update on the Quantity triggers a recalculation of the quantity and amount of the child Analytic Items.
+
A delete cascades to the child Analytic Items, causing them to also be deleted.
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 smashing it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
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.
+
+
+
+
+
+
+
From 6a5c038d682892fce8737f9a446175e515d7cce1 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Wed, 28 Jul 2021 20:58:31 +0000
Subject: [PATCH 05/36] account_analytic_wip 14.0.2.0.0
---
account_analytic_wip/__manifest__.py | 2 +-
account_analytic_wip/i18n/account_analytic_wip.pot | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/account_analytic_wip/__manifest__.py b/account_analytic_wip/__manifest__.py
index 40b82286cc..5f0ef9472d 100644
--- a/account_analytic_wip/__manifest__.py
+++ b/account_analytic_wip/__manifest__.py
@@ -3,7 +3,7 @@
{
"name": "Analytic Accounting support for WIP and Variances",
- "version": "14.0.1.0.0",
+ "version": "14.0.2.0.0",
"author": "Open Source Integrators, Odoo Community Association (OCA)",
"summary": "Track and report WIP and Variances based on Analytic Items",
"website": "https://github.com/OCA/account-analytic",
diff --git a/account_analytic_wip/i18n/account_analytic_wip.pot b/account_analytic_wip/i18n/account_analytic_wip.pot
index f45b9096b4..81f6034886 100644
--- a/account_analytic_wip/i18n/account_analytic_wip.pot
+++ b/account_analytic_wip/i18n/account_analytic_wip.pot
@@ -280,7 +280,6 @@ msgid ""
msgstr ""
#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product__is_cost_type
#: model:ir.model.fields,field_description:account_analytic_wip.field_product_template__is_cost_type
#: model_terms:ir.ui.view,arch_db:account_analytic_wip.product_search_form_view
msgid "Is Cost Type"
From 4f8c92f1529bcbfb1b0caef09a10b244a456ff3b Mon Sep 17 00:00:00 2001
From: Daniel Reis
Date: Fri, 30 Jul 2021 23:10:44 +0100
Subject: [PATCH 06/36] [REF] account_analytic_wip: simplify dependencies
---
account_analytic_wip/README.rst | 26 ++-
account_analytic_wip/__manifest__.py | 7 +-
account_analytic_wip/demo/product_demo.xml | 37 +++++
.../i18n/account_analytic_wip.pot | 146 ++---------------
account_analytic_wip/models/__init__.py | 6 +-
.../models/account_analytic.py | 16 +-
.../models/account_analytic_line.py | 145 +++++------------
.../models/account_analytic_tracked.py | 95 -----------
.../models/account_analytic_tracking.py | 134 +++++++++++----
account_analytic_wip/models/account_move.py | 7 +
.../models/activity_cost_rule.py | 23 ---
.../models/product_template.py | 38 +----
account_analytic_wip/readme/CONFIGURATION.rst | 12 +-
account_analytic_wip/readme/DESCRIPTION.rst | 6 +-
account_analytic_wip/readme/USAGE.rst | 20 +--
.../security/ir.model.access.csv | 2 -
.../static/description/index.html | 14 +-
account_analytic_wip/tests/test_analytic.py | 153 ++++++++++++++++--
.../views/account_analytic_line.xml | 27 +---
.../views/account_analytic_tracking.xml | 17 +-
.../views/activity_cost_rule_views.xml | 54 -------
.../views/product_template.xml | 90 -----------
22 files changed, 404 insertions(+), 671 deletions(-)
create mode 100644 account_analytic_wip/demo/product_demo.xml
delete mode 100644 account_analytic_wip/models/account_analytic_tracked.py
delete mode 100644 account_analytic_wip/models/activity_cost_rule.py
delete mode 100644 account_analytic_wip/views/activity_cost_rule_views.xml
delete mode 100644 account_analytic_wip/views/product_template.xml
diff --git a/account_analytic_wip/README.rst b/account_analytic_wip/README.rst
index fcc0a02faa..4df61313e2 100644
--- a/account_analytic_wip/README.rst
+++ b/account_analytic_wip/README.rst
@@ -45,9 +45,9 @@ to generate the corresponding accounting moves.
Products can be seen as cost drivers, driving consumption of other items.
For example a machine work time can drive consumptions of Labor and Overhead.
-This feature models cost driver usage as Analytic Items.
-When an Analytic Item is created, it may then generate additional Analytic Items for the corresponding indirect costs.
-For example, each timesheet hour logged could generate a quantity and amount of overhead assigned to that activity.
+When an Analytic Item is created for a cost driver,
+additional Analytic Items are generated for the corresponding indirect costs.
+For example, each timesheet hour logged could generate a overhead amount related to that activity.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
@@ -62,20 +62,17 @@ For example, each timesheet hour logged could generate a quantity and amount of
Usage
=====
-The "Analytic Tracking Items" holds planned amounts, and track their WIP and variances.
+The "Analytic Tracking Items" holds planned amounts, and tracks their WIP and variances.
These must be automatically created by specific logic in the Apps supporting them.
With this module alone the Tracking Item creation can be done manually:
* Navigate to ''Invoicing/Accounting > Reporting > Management > Analytic Tracking''
-
* Create an Analytic Tracking Item:
* Set the Analytic Account.
-
* Set the Product, use one that has a non-zero cost
and belongs to a category with the "Costing" section configured.
-
* Set the Planned Amount.
@@ -87,12 +84,9 @@ Analytic Items are used to record the actual costs:
* Create an Analytic Item:
* Set the Analytic Account, Description and Date.
-
* Set the Product, use one that has a non-zero cost
and belongs to a category with the "Costing" section configured.
-
* Set the quantity consumed.
-
* The Amount field should be automatically computed, with a negative amount.
@@ -108,9 +102,7 @@ and the comparison with the planned amounts. This can be used for analysis:
WIP and variances journal entries are generated by a scheduled job:
* Navigate to *Setting > Technical > Automation > Scheduled Actions*.
-
* Locate and open the *Account: Process WIP and Variances* record, and click on the RUN MANUALLY button.
-
* Check the generated journal entries, at *Accounting > Miscellaneous > Journal Entries*.
@@ -118,10 +110,12 @@ When creating Analytic Items, if a configuration is in place, the corresponding
* When an Analytic Item is created, an automatic process checks the Activity Based Cost Rules to identify the ones that apply.
* Each triggered rule created a new Analytic Item, with a copy of the original one, and:
- * Product: is the rule Cost Type Product. A validation error prevents this from being the same as the source Analytic Item Product, to avoid infinite loops.
- * Quantity: is the original quantity multiplied by the rule's Factor
- * Amount: is -1 * Quantity * Product Standard Price
- * Parent Analytic Item (new field): set with the original Analytic Item
+
+ * Product: is the rule Cost Type Product. A validation error prevents this from being the same as the source Analytic Item Product, to avoid infinite loops.
+ * Quantity: is the original quantity multiplied by the rule's Factor
+ * Amount: is -1 * Quantity * Product Standard Price
+ * Parent Analytic Item (new field): set with the original Analytic Item
+
* An update on the Quantity triggers a recalculation of the quantity and amount of the child Analytic Items.
* A delete cascades to the child Analytic Items, causing them to also be deleted.
diff --git a/account_analytic_wip/__manifest__.py b/account_analytic_wip/__manifest__.py
index 5f0ef9472d..9342bcdb7b 100644
--- a/account_analytic_wip/__manifest__.py
+++ b/account_analytic_wip/__manifest__.py
@@ -8,18 +8,19 @@
"summary": "Track and report WIP and Variances based on Analytic Items",
"website": "https://github.com/OCA/account-analytic",
"license": "AGPL-3",
- "depends": ["stock_account"],
+ "depends": ["stock_account", "analytic_activity_based_cost"],
"category": "Accounting/Accounting",
"data": [
"security/ir.model.access.csv",
"data/ir_cron_data.xml",
- "views/activity_cost_rule_views.xml",
"views/product_category.xml",
- "views/product_template.xml",
"views/account_move.xml",
"views/account_analytic_line.xml",
"views/account_analytic_tracking.xml",
],
+ "demo": [
+ "demo/product_demo.xml",
+ ],
"development_status": "Alpha",
"maintainers": ["dreispt"],
"installable": True,
diff --git a/account_analytic_wip/demo/product_demo.xml b/account_analytic_wip/demo/product_demo.xml
new file mode 100644
index 0000000000..f4c650e190
--- /dev/null
+++ b/account_analytic_wip/demo/product_demo.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ WIP Journal
+ general
+ WIP
+
+
+
+ Costing Consumed
+ 600010
+
+
+
+ Costing WIP
+ 600011
+
+
+
+ Costing Variance
+ 600012
+
+
+
+
+ standard
+ real_time
+
+
+
+
+
+
diff --git a/account_analytic_wip/i18n/account_analytic_wip.pot b/account_analytic_wip/i18n/account_analytic_wip.pot
index 81f6034886..777242023c 100644
--- a/account_analytic_wip/i18n/account_analytic_wip.pot
+++ b/account_analytic_wip/i18n/account_analytic_wip.pot
@@ -41,33 +41,10 @@ msgid "Accounted amount incurred below the planned amount limit."
msgstr ""
#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__active
-msgid "Active"
-msgstr ""
-
-#. module: account_analytic_wip
-#: model:ir.actions.act_window,name:account_analytic_wip.action_activity_cost_rule
-#: model:ir.ui.menu,name:account_analytic_wip.menu_activity_cost_rule
-msgid "Activity Based Cost Rules"
-msgstr ""
-
-#. module: account_analytic_wip
-#: model:ir.model,name:account_analytic_wip.model_activity_cost_rule
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__activity_cost_id
msgid "Activity Cost Rule"
msgstr ""
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product__activity_cost_ids
-#: model_terms:ir.ui.view,arch_db:account_analytic_wip.view_analytic_line_form
-#: model_terms:ir.ui.view,arch_db:account_analytic_wip.view_product_product_form
-msgid "Activity Costs"
-msgstr ""
-
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__parent_id
-msgid "Activity Product"
-msgstr ""
-
#. module: account_analytic_wip
#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
msgid "Actual"
@@ -137,12 +114,6 @@ msgstr ""
msgid "Analytic Tracking Items"
msgstr ""
-#. module: account_analytic_wip
-#: code:addons/account_analytic_wip/models/product_template.py:0
-#, python-format
-msgid "Can't have Activity Costs set if it is not a Cost Type."
-msgstr ""
-
#. module: account_analytic_wip
#: model:ir.model.fields.selection,name:account_analytic_wip.selection__account_analytic_tracking_item__state__cancel
msgid "Cancelled"
@@ -166,66 +137,23 @@ msgstr ""
msgid "Company"
msgstr ""
-#. module: account_analytic_wip
-#: model_terms:ir.ui.view,arch_db:account_analytic_wip.view_form_activity_cost_rule
-msgid "Conditions"
-msgstr ""
-
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__standard_price
-msgid "Cost"
-msgstr ""
-
-#. module: account_analytic_wip
-#: model_terms:ir.ui.view,arch_db:account_analytic_wip.view_form_activity_cost_rule
-msgid "Cost Generated"
-msgstr ""
-
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__product_id
msgid "Cost Product"
msgstr ""
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__activity_cost_id
-msgid "Cost Rule Applied"
-msgstr ""
-
-#. module: account_analytic_wip
-#: model:ir.model,name:account_analytic_wip.model_account_analytic_tracked_mixin
-msgid "Cost Tracked Mixin"
-msgstr ""
-
#. module: account_analytic_wip
#: model:ir.model,name:account_analytic_wip.model_account_analytic_tracking_item
msgid "Cost Tracking Item"
msgstr ""
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__product_id
-msgid "Cost Type Product"
-msgstr ""
-
-#. module: account_analytic_wip
-#: model:ir.actions.act_window,name:account_analytic_wip.product_product_action_cost_type
-#: model:ir.ui.menu,name:account_analytic_wip.product_product_menu_cost_type
-msgid "Cost Types"
-msgstr ""
-
-#. module: account_analytic_wip
-#: model_terms:ir.actions.act_window,help:account_analytic_wip.product_product_action_cost_type
-msgid "Create a new cost type product"
-msgstr ""
-
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__create_uid
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__create_uid
msgid "Created by"
msgstr ""
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__create_date
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__create_date
msgid "Created on"
msgstr ""
@@ -234,20 +162,13 @@ msgstr ""
msgid "Date"
msgstr ""
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__name
-msgid "Description"
-msgstr ""
-
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_account__display_name
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__display_name
-#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracked_mixin__display_name
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__display_name
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move__display_name
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__display_name
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move_line__display_name
#: model:ir.model.fields,field_description:account_analytic_wip.field_product_category__display_name
-#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product__display_name
#: model:ir.model.fields,field_description:account_analytic_wip.field_product_template__display_name
msgid "Display Name"
msgstr ""
@@ -260,29 +181,17 @@ msgstr ""
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_account__id
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__id
-#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracked_mixin__id
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__id
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move__id
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__id
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move_line__id
#: model:ir.model.fields,field_description:account_analytic_wip.field_product_category__id
-#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product__id
#: model:ir.model.fields,field_description:account_analytic_wip.field_product_template__id
msgid "ID"
msgstr ""
#. module: account_analytic_wip
-#: model:ir.model.fields,help:account_analytic_wip.field_activity_cost_rule__standard_price
-msgid ""
-"In Standard Price & AVCO: value of the product (automatically computed in AVCO).\n"
-" In FIFO: value of the last unit that left the stock (automatically computed).\n"
-" Used to value the product when the purchase cost is not known (e.g. inventory adjustment).\n"
-" Used to compute margins on sale orders."
-msgstr ""
-
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_product_template__is_cost_type
-#: model_terms:ir.ui.view,arch_db:account_analytic_wip.product_search_form_view
-msgid "Is Cost Type"
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move_line__is_wip_line
+msgid "Is Wip Line"
msgstr ""
#. module: account_analytic_wip
@@ -295,28 +204,29 @@ msgstr ""
msgid "Journal Entry"
msgstr ""
+#. module: account_analytic_wip
+#: model:ir.model,name:account_analytic_wip.model_account_move_line
+msgid "Journal Item"
+msgstr ""
+
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_account____last_update
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line____last_update
-#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracked_mixin____last_update
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item____last_update
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move____last_update
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule____last_update
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move_line____last_update
#: model:ir.model.fields,field_description:account_analytic_wip.field_product_category____last_update
-#: model:ir.model.fields,field_description:account_analytic_wip.field_product_product____last_update
#: model:ir.model.fields,field_description:account_analytic_wip.field_product_template____last_update
msgid "Last Modified on"
msgstr ""
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__write_uid
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__write_uid
msgid "Last Updated by"
msgstr ""
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__write_date
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__write_date
msgid "Last Updated on"
msgstr ""
@@ -343,11 +253,6 @@ msgid ""
" are done and posted, no more actions to do."
msgstr ""
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__parent_id
-msgid "Parent Analytic Item"
-msgstr ""
-
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__parent_id
msgid "Parent Tracking Item"
@@ -369,23 +274,23 @@ msgid "Planned Amount"
msgstr ""
#. module: account_analytic_wip
-#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
-msgid "Post"
+#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__planned_qty
+msgid "Planned Qty"
msgstr ""
#. module: account_analytic_wip
#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
-msgid "Posted"
+msgid "Post"
msgstr ""
#. module: account_analytic_wip
#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_form
-msgid "Process"
+msgid "Post WIP and Variances"
msgstr ""
#. module: account_analytic_wip
-#: model:ir.model,name:account_analytic_wip.model_product_product
-msgid "Product"
+#: model_terms:ir.ui.view,arch_db:account_analytic_wip.account_analytic_tracking_tree
+msgid "Posted"
msgstr ""
#. module: account_analytic_wip
@@ -399,16 +304,6 @@ msgstr ""
msgid "Product Template"
msgstr ""
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_activity_cost_rule__factor
-msgid "Qty. Factor"
-msgstr ""
-
-#. module: account_analytic_wip
-#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__child_ids
-msgid "Related Analytic Items"
-msgstr ""
-
#. module: account_analytic_wip
#: model:ir.model.fields,help:account_analytic_wip.field_account_analytic_tracking_item__analytic_line_ids
msgid "Related analytic items with the project actuals."
@@ -446,12 +341,6 @@ msgstr ""
msgid "State"
msgstr ""
-#. module: account_analytic_wip
-#: model:ir.model.fields,help:account_analytic_wip.field_product_product__activity_cost_ids
-msgid ""
-"This product will also generate analytic items for these Activity Costs"
-msgstr ""
-
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracking_item__to_calculate
msgid "To Calculate"
@@ -472,7 +361,6 @@ msgstr ""
#. module: account_analytic_wip
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_line__analytic_tracking_item_id
-#: model:ir.model.fields,field_description:account_analytic_wip.field_account_analytic_tracked_mixin__analytic_tracking_item_id
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_bank_statement_line__analytic_tracking_item_id
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_move__analytic_tracking_item_id
#: model:ir.model.fields,field_description:account_analytic_wip.field_account_payment__analytic_tracking_item_id
diff --git a/account_analytic_wip/models/__init__.py b/account_analytic_wip/models/__init__.py
index 8276638480..e855c42d21 100644
--- a/account_analytic_wip/models/__init__.py
+++ b/account_analytic_wip/models/__init__.py
@@ -1,11 +1,9 @@
# Copyright (C) 2021 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from . import product_category
-from . import product_template
from . import account_move
from . import account_analytic
from . import account_analytic_line
from . import account_analytic_tracking
-from . import account_analytic_tracked
-from . import activity_cost_rule
+from . import product_category
+from . import product_template
diff --git a/account_analytic_wip/models/account_analytic.py b/account_analytic_wip/models/account_analytic.py
index bb26db5cdd..3e1a3533a3 100644
--- a/account_analytic_wip/models/account_analytic.py
+++ b/account_analytic_wip/models/account_analytic.py
@@ -2,7 +2,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import api, fields, models
+from odoo import fields, models
class AccountAnalytic(models.Model):
@@ -15,15 +15,7 @@ class AccountAnalytic(models.Model):
_inherit = "account.analytic.account"
analytic_tracking_item_ids = fields.One2many(
- "account.analytic.tracking.item", "analytic_id", string="Tracking Items"
+ "account.analytic.tracking.item",
+ "analytic_id",
+ string="Tracking Items",
)
-
- @api.model
- def create(self, vals):
- """
- A default Tracking Item is automatically created.
- It will collect Atual Amounts not linked to a specific Tracking Item.
- """
- new = super().create(vals)
- self.env["account.analytic.tracking.item"].create({"analytic_id": new.id})
- return new
diff --git a/account_analytic_wip/models/account_analytic_line.py b/account_analytic_wip/models/account_analytic_line.py
index bb5c6d675f..1bb75d7826 100644
--- a/account_analytic_wip/models/account_analytic_line.py
+++ b/account_analytic_wip/models/account_analytic_line.py
@@ -2,119 +2,62 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-import logging
-
from odoo import api, fields, models
-_logger = logging.getLogger(__name__)
-
class AnalyticLine(models.Model):
- """
- Analytic Lines should keep a link to the corresponding Tracking Item,
- so that it can report the corresponding WIP amounts.
- """
-
_inherit = "account.analytic.line"
analytic_tracking_item_id = fields.Many2one(
"account.analytic.tracking.item", string="Tracking Item"
)
- parent_id = fields.Many2one(
- "account.analytic.line", "Parent Analytic Item", ondelete="cascade"
- )
- child_ids = fields.One2many(
- "account.analytic.line", "parent_id", string="Related Analytic Items"
- )
- activity_cost_id = fields.Many2one(
- "activity.cost.rule", "Cost Rule Applied", ondelete="restrict"
- )
-
- @api.onchange("product_id", "product_uom_id", "unit_amount", "currency_id")
- def on_change_unit_amount(self):
- """ Do not set Amount on cost parent Analytic Items """
- super().on_change_unit_amount()
- if self.child_ids:
- self.amount = 0
- def _prepare_activity_cost_data(self, cost_type):
- """
- Return a dict with the values to create
- a new Analytic item for a Cost Type.
- """
- self.ensure_one()
- values = {
- "name": "{} / {}".format(
- self.name, cost_type.product_id.display_name or cost_type.name
- ),
- "activity_cost_id": cost_type.id,
- "product_id": cost_type.product_id.id,
- "unit_amount": self.unit_amount * cost_type.factor,
+ def _prepare_tracking_item_values(self):
+ return {
+ "analytic_id": self.account_id.id,
+ "product_id": self.product_id.id,
}
- # Remove the link the the Project Task,
- # otherwise the cost lines would wrongly show as Timesheet
- if hasattr(self, "project_id"):
- values["project_id"] = None
- if hasattr(self, "task_id"):
- values["task_id"] = None
- return values
- def _set_tracking_item(self):
- """
- When creating a child Analytic Item,
- find the correct matching child Tracking Item
- """
- for analytic_item in self.filtered("parent_id.analytic_tracking_item_id"):
- tracking_items = analytic_item.parent_id.analytic_tracking_item_id.child_ids
- tracking_item = tracking_items.filtered(
- lambda x: x.product_id == analytic_item.product_id
- )
- analytic_item.analytic_tracking_item_id = tracking_item
- if not tracking_item:
- _logger.error(
- "Analytic Item %s: could not find related Tracked Item",
- analytic_item.display_name,
- )
-
- def _create_child_lines(self):
- """
- Find applicable Activity Cost Rules
- and create Analytic Lines for each of them.
-
- This is done copying the original Analytic Item
- to ensure all other fields are preserved on the new Item.
- """
- for analytic_parent in self.filtered("product_id.activity_cost_ids"):
- cost_ids = analytic_parent.product_id.activity_cost_ids
- if cost_ids:
- # Parent Cost Type amount must be zero
- # to avoid duplication with child cost type amounts
- analytic_parent.amount = 0
- for cost_product in cost_ids:
- cost_vals = analytic_parent._prepare_activity_cost_data(
- cost_type=cost_product
- )
- cost_vals["parent_id"] = analytic_parent.id
- analytic_child = analytic_parent.copy(cost_vals)
- analytic_child.on_change_unit_amount()
+ def _get_tracking_item(self):
+ self.ensure_one()
+ all_tracking = self.account_id.analytic_tracking_item_ids
+ tracking = all_tracking.filtered(
+ lambda x: x.product_id == self.product_id
+ or (not self.product_id and not x.product_id)
+ )
+ return tracking
+
+ def _get_set_tracking_item(self):
+ """
+ Given an Analytic Item,
+ locate the corresponding Tracking Item
+ and set it on the record.
+ If the (parent level) Tracking Item does not exist, it is created.
+ """
+ tracking = self._get_tracking_item()
+ if tracking:
+ self.analytic_tracking_item_id = tracking
+ elif not self.parent_id:
+ # New Tracking Item created for parent Analytic Item only
+ # This trigger automatic creation of child breakdown,
+ # and we need to ensure these childs are also mapped to tracking items
+ vals = self._prepare_tracking_item_values()
+ tracking = self.env["account.analytic.tracking.item"].create(vals)
+ self.analytic_tracking_item_id = tracking
+ for item in self.child_ids:
+ item._get_set_tracking_item()
+ return tracking
+
+ def populate_tracking_items(self):
+ """
+ When creating an Analytic Item,
+ link it to a Tracking Item, the may have to be created if it doesn't exist.
+ """
+ for item in self.filtered(lambda x: not x.analytic_tracking_item_id):
+ item._get_set_tracking_item()
@api.model
def create(self, vals):
- res = super().create(vals)
- res._set_tracking_item()
- res._create_child_lines()
- return res
-
- def write(self, vals):
- """
- If Units are updated, also update the related cost Analytic Items
- """
- res = super().write(vals)
- if vals.get("unit_amount"):
- for analytic_child in self.mapped("child_ids"):
- cost_vals = analytic_child._prepare_activity_cost_data(
- cost_type=analytic_child.activity_cost_id
- )
- analytic_child.write(cost_vals)
- analytic_child.on_change_unit_amount()
- return res
+ new = super().create(vals)
+ new.populate_tracking_items()
+ return new
diff --git a/account_analytic_wip/models/account_analytic_tracked.py b/account_analytic_wip/models/account_analytic_tracked.py
deleted file mode 100644
index 706e670ba2..0000000000
--- a/account_analytic_wip/models/account_analytic_tracked.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# Copyright (C) 2021 Open Source Integrators
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from odoo import api, fields, models
-
-
-class AnalyticTrackedItem(models.AbstractModel):
- """
- Mixin to use on Models that generate WIp Analytic Items,
- and should be linked to Tracking Items.
- """
-
- _name = "account.analytic.tracked.mixin"
- _description = "Cost Tracked Mixin"
-
- analytic_tracking_item_id = fields.Many2one(
- "account.analytic.tracking.item",
- string="Tracking Item",
- ondelete="cascade",
- copy=False,
- )
-
- def _prepare_tracking_item_values(self):
- """
- To be implemented by inheriting models.
- Return a dict with the values to create the related Tracking Item.
- """
- self.ensure_one()
- return {}
-
- def _get_tracking_planned_qty(self):
- """
- Get the initial planned quantity.
- To be extended by inheriting Model
- """
- return 0.0
-
- def set_tracking_item(self, update_planned=False):
- """
- Create and set the related Tracking Item, where actuals will be accumulated to.
- The _prepare_tracking_item_values() provides the values used to create it.
-
- If the update_planned flag is set, the planned amount is updated.
- The _get_tracking_planned_qty() method provides the planned quantity.
-
- By default is is not set, and will be zero for new tracking items.
-
- Returns one Tracking Item record or an empty recordset.
- """
- TrackingItem = self.env["account.analytic.tracking.item"]
- for item in self:
- if not item.analytic_tracking_item_id:
- vals = item._prepare_tracking_item_values()
- if vals:
- tracking_item = TrackingItem.create(vals)
- item.analytic_tracking_item_id = tracking_item
- # FIXME: remove this code, is is ABC logic, not WIP logic!
- # The Product my be a Cost Type with child Products
- cost_rules = (
- item.analytic_tracking_item_id.product_id.activity_cost_ids
- )
- for cost_type in cost_rules.product_id:
- child_vals = dict(vals)
- child_vals.update(
- {
- "parent_id": item.analytic_tracking_item_id.id,
- "product_id": cost_type.id,
- }
- )
- TrackingItem.create(child_vals)
-
- if update_planned and item.analytic_tracking_item_id:
- planned_qty = item._get_tracking_planned_qty()
- tracking_item = item.analytic_tracking_item_id
- for subitem in tracking_item | tracking_item.child_ids:
- qty = planned_qty if subitem.to_calculate else 0.0
- unit_cost = subitem.product_id.standard_price
- subitem.planned_amount = qty * unit_cost
-
- @api.model
- def create(self, vals):
- """
- New tracked records automatically create Tracking Items, if possible.
- """
- new = super().create(vals)
- new.set_tracking_item()
- return new
-
- def write(self, vals):
- """
- Tracked records automatically create Tracking Items, if possible.
- """
- res = super().write(vals)
- self.set_tracking_item()
- return res
diff --git a/account_analytic_wip/models/account_analytic_tracking.py b/account_analytic_wip/models/account_analytic_tracking.py
index ffd5f5716e..db58126227 100644
--- a/account_analytic_wip/models/account_analytic_tracking.py
+++ b/account_analytic_wip/models/account_analytic_tracking.py
@@ -3,6 +3,7 @@
from odoo import api, fields, models
+from odoo.tools import float_is_zero
class AnalyticTrackingItem(models.Model):
@@ -27,6 +28,7 @@ class AnalyticTrackingItem(models.Model):
product_id = fields.Many2one(
"product.product", string="Cost Product", ondelete="restrict"
)
+ activity_cost_id = fields.Many2one("activity.cost.rule", "Activity Cost Rule")
# Related calculated data
company_id = fields.Many2one(
@@ -71,6 +73,7 @@ class AnalyticTrackingItem(models.Model):
)
# Planned Amount
+ planned_qty = fields.Float()
planned_amount = fields.Float()
# Actual Amounts
@@ -123,11 +126,12 @@ def _compute_name(self):
@api.depends("state", "child_ids")
def _compute_to_calculate(self):
for item in self:
- item.to_calculate = item.state != "cancel" and not item.child_ids
+ item.to_calculate = (
+ item.state != "cancel" and not item.child_ids and item.product_id
+ )
@api.depends(
"analytic_line_ids.amount",
- "analytic_line_ids.unit_amount",
"parent_id.analytic_line_ids.amount",
"planned_amount",
"accounted_amount",
@@ -140,18 +144,23 @@ def _compute_actual_amounts(self):
item.actual_amount = 0
else:
all_actuals = item.analytic_line_ids or item.parent_id.analytic_line_ids
+ all_actuals |= all_actuals.child_ids
product_actuals = all_actuals.filtered(
lambda x: x.product_id == item.product_id
)
- item.actual_amount = -sum(product_actuals.mapped("amount")) or 0.0
+ item.actual_amount = (
+ -sum(product_actuals.mapped("amount_abcost")) or 0.0
+ )
item.pending_amount = item.actual_amount - item.accounted_amount
- item.wip_actual_amount = min(item.actual_amount, item.planned_amount)
+ if item.planned_amount:
+ item.wip_actual_amount = min(item.actual_amount, item.planned_amount)
+ else:
+ item.wip_actual_amount = item.actual_amount
- if not item.to_calculate:
+ if not item.to_calculate or not item.planned_amount:
item.remaining_actual_amount = 0
item.variance_actual_amount = 0
- item.pending_amount = 0
elif item.state == "open":
# Negative variances show in the Remaining column
item.remaining_actual_amount = (
@@ -176,16 +185,18 @@ def _prepare_account_move_head(self, journal):
"analytic_tracking_item_id": self.id,
}
- def _prepare_account_move_line(self, account, amount):
+ def _prepare_account_move_line(self, account, amount, account_role=None):
+ # Note: do not set analytic_Account_id,
+ # as that triggers a (repeated) Analytic Item
return {
"name": self.display_name,
"product_id": self.product_id.id,
"product_uom_id": self.product_id.uom_id.id,
- "analytic_account_id": self.analytic_id.id,
"ref": self.display_name,
"account_id": account.id,
"debit": amount if amount > 0.0 else 0.0,
"credit": -amount if amount < 0.0 else 0.0,
+ "is_wip_line": account_role == "stock_wip",
}
def _get_accounting_data_for_valuation(self):
@@ -213,34 +224,58 @@ def _get_journal_entries_wip(self, wip_amount=0.0, variance_amount=0.0):
"stock_variance": variance_amount,
}
- def _get_journal_entries_done(self, wip_amount=0.0, variance_amount=0.0):
- """
- Extension hook to set the journal items for WIP records
- once the WIP Order is done
- """
- return {"stock_wip": -wip_amount, "stock_output": wip_amount}
-
def _create_journal_entry_from_map(self, je_map):
"""
Given a journal entry map, create the Journal Entry record
"""
accounts = self._get_accounting_data_for_valuation()
- wip_journal = accounts["wip_journal"]
+ wip_journal = accounts.get("wip_journal") or accounts["stock_journal"]
posted = False
- if wip_journal:
- move_lines = [
- self._prepare_account_move_line(accounts[account], amount=amount)
- for account, amount in je_map.items()
- if amount
- ]
- if move_lines:
- je_vals = self._prepare_account_move_head(wip_journal)
- je_vals["line_ids"] = [(0, 0, x) for x in move_lines if x]
- je_new = self.env["account.move"].sudo().create(je_vals)
- je_new._post()
- posted = True
+ move_lines = [
+ self._prepare_account_move_line(accounts[account], amount, account)
+ for account, amount in je_map.items()
+ if amount
+ ]
+ if move_lines:
+ je_vals = self._prepare_account_move_head(wip_journal)
+ je_vals["line_ids"] = [(0, 0, x) for x in move_lines if x]
+ je_new = self.env["account.move"].sudo().create(je_vals)
+ je_new._post()
+ posted = True
return posted
+ def clear_wip_journal_entries(self):
+ """
+ Clear the WIP accounts so that their balance is zero
+ For non-stockable products, only WIP moves are cleared.
+ For stockable products, all moves are reverted, as this
+ is known to be needed for Manufacturing cases.
+ """
+ per_account_wip = {}
+ total_wip = 0.0
+ for je_line in self.mapped("account_move_ids.line_ids"):
+ if je_line.product_id.type == "product" or je_line.is_wip_line:
+ per_account_wip.setdefault(je_line.account_id, 0.0)
+ per_account_wip[je_line.account_id] += je_line.balance
+ total_wip += je_line.balance
+
+ move_lines = [
+ self._prepare_account_move_line(acc, -bal, "stock_wip")
+ for acc, bal in per_account_wip.items()
+ if not float_is_zero(bal, 6)
+ ]
+ if not float_is_zero(total_wip, 6):
+ accounts = self._get_accounting_data_for_valuation()
+ move_lines.append(
+ self._prepare_account_move_line(accounts["stock_output"], total_wip)
+ )
+ if move_lines:
+ wip_journal = accounts.get("wip_journal") or accounts["stock_journal"]
+ je_vals = self._prepare_account_move_head(wip_journal)
+ je_vals["line_ids"] = [(0, 0, x) for x in move_lines]
+ je_new = self.env["account.move"].sudo().create(je_vals)
+ je_new._post()
+
def process_wip_and_variance(self, close=False):
"""
For each Analytic Tracking Item with a Pending Amount different from zero,
@@ -254,9 +289,7 @@ def process_wip_and_variance(self, close=False):
self.process_wip_and_variance(close=False)
for item in all_tracking:
if close:
- wip_pending = item.wip_actual_amount
- var_pending = item.variance_actual_amount
- je_map = item._get_journal_entries_done(wip_pending, var_pending)
+ je_map = item.clear_wip_journal_entries()
else:
wip_pending = round(
item.wip_actual_amount - item.wip_accounted_amount, 6
@@ -265,7 +298,7 @@ def process_wip_and_variance(self, close=False):
item.variance_actual_amount - item.variance_accounted_amount, 6
)
je_map = item._get_journal_entries_wip(wip_pending, var_pending)
- is_posted = item._create_journal_entry_from_map(je_map)
+ is_posted = item._create_journal_entry_from_map(je_map or {})
if is_posted:
# Update accounted amount to equal actual amounts
item.accounted_amount = item.actual_amount
@@ -282,3 +315,40 @@ def action_cancel(self):
# TODO: what to do if there are JEs done?
all_tracking = self | self.child_ids
all_tracking.write({"state": "cancel"})
+
+ def _populate_abcost_tracking_item(self):
+ to_calculate_with_childs = (self | self.child_ids).filtered("to_calculate")
+ for tracking in to_calculate_with_childs:
+ cost_rules = tracking.product_id.activity_cost_ids
+ # Calculate Planned Amount if no ABC an only qty provided
+ # or when a ABC tracking (sub)item is created
+ if not tracking.planned_amount and not cost_rules:
+ factor = tracking.activity_cost_id.factor or 1.0
+ unit_cost = tracking.product_id.price_compute(
+ "standard_price", uom=tracking.product_id.uom_id
+ )[tracking.product_id.id]
+ qty = factor * (tracking.planned_qty or tracking.parent_id.planned_qty)
+ tracking.planned_amount = qty * unit_cost
+ # Generate ABC (sub)tracking items
+ if cost_rules and not tracking.child_ids:
+ for cost_rule in cost_rules:
+ vals = {
+ "parent_id": tracking.id,
+ "product_id": cost_rule.product_id.id,
+ "activity_cost_id": cost_rule.id,
+ "planned_qty": 0.0,
+ }
+ tracking.copy(vals)
+
+ @api.model
+ def create(self, vals):
+ new = super().create(vals)
+ new._populate_abcost_tracking_item()
+ return new
+
+ def write(self, vals):
+ res = super().write(vals)
+ # Write on planned_qty to update the planned amounts
+ if vals.get("planned_qty"):
+ self._populate_abcost_tracking_item()
+ return res
diff --git a/account_analytic_wip/models/account_move.py b/account_analytic_wip/models/account_move.py
index 6061b4e0df..2b9fd25b36 100644
--- a/account_analytic_wip/models/account_move.py
+++ b/account_analytic_wip/models/account_move.py
@@ -16,5 +16,12 @@ class AccountMove(models.Model):
analytic_tracking_item_id = fields.Many2one(
"account.analytic.tracking.item",
string="Tracking Item",
+ ondelete="set null",
help="Tracking item generating this journal entry",
)
+
+
+class AccountMoveLine(models.Model):
+ _inherit = "account.move.line"
+
+ is_wip_line = fields.Boolean()
diff --git a/account_analytic_wip/models/activity_cost_rule.py b/account_analytic_wip/models/activity_cost_rule.py
deleted file mode 100644
index 94e5740eee..0000000000
--- a/account_analytic_wip/models/activity_cost_rule.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (C) 2020 Open Source Integrators
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from odoo import fields, models
-
-
-class ActivityCostRule(models.Model):
- _name = "activity.cost.rule"
- _description = "Activity Cost Rule"
-
- name = fields.Char("Description")
- active = fields.Boolean(default=True)
- parent_id = fields.Many2one("product.product", "Activity Product")
- product_id = fields.Many2one(
- "product.product",
- string="Cost Type Product",
- domain=[("is_cost_type", "=", True)],
- )
- factor = fields.Float("Qty. Factor", default=1)
- standard_price = fields.Float(
- related="product_id.standard_price",
- readonly=False,
- )
diff --git a/account_analytic_wip/models/product_template.py b/account_analytic_wip/models/product_template.py
index ec056de330..bc2608528c 100644
--- a/account_analytic_wip/models/product_template.py
+++ b/account_analytic_wip/models/product_template.py
@@ -2,14 +2,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import _, api, exceptions, fields, models
+from odoo import models
class ProductTemplate(models.Model):
_inherit = "product.template"
- is_cost_type = fields.Boolean()
-
def _get_product_accounts(self):
"""
Add the Variance account, used to post WIP amount exceeding the expected.
@@ -27,32 +25,10 @@ def get_product_accounts(self, fiscal_pos=None):
Add the journal to use for WIP journal entries, 'wip_journal'
"""
accounts = super().get_product_accounts(fiscal_pos=fiscal_pos)
- accounts.update({"wip_journal": self.categ_id.property_wip_journal_id or False})
+ accounts.update(
+ {
+ "wip_journal": self.categ_id.property_wip_journal_id
+ or self.categ_id.property_stock_journal
+ }
+ )
return accounts
-
-
-class Product(models.Model):
- _inherit = "product.product"
-
- activity_cost_ids = fields.One2many(
- "activity.cost.rule",
- "parent_id",
- string="Activity Costs",
- help="This product will also generate analytic items for these Activity Costs",
- )
-
- @api.constrains("is_cost_type", "activity_cost_ids")
- def constrains_is_cost_type(self):
- for product in self:
- if not product.is_cost_type and product.activity_cost_ids:
- raise exceptions.ValidationError(
- _("Can't have Activity Costs set if it is not a Cost Type.")
- )
-
- @api.onchange("activity_cost_ids")
- def onchange_for_standard_price(self):
- "Rollup Activity Costs to parent Cost Type"
- for product in self.filtered("is_cost_type").filtered("activity_cost_ids"):
- product.standard_price = sum(
- product.mapped("activity_cost_ids.standard_price")
- )
diff --git a/account_analytic_wip/readme/CONFIGURATION.rst b/account_analytic_wip/readme/CONFIGURATION.rst
index e0911ceab6..4a39717cf9 100644
--- a/account_analytic_wip/readme/CONFIGURATION.rst
+++ b/account_analytic_wip/readme/CONFIGURATION.rst
@@ -8,11 +8,11 @@ On the Product Category, "Costing" section, configure the accounts to use:
Enable the Analytic Accounting setting.
-Create the Products representing the Cost Types to use:
+Create the Products representing the Cost Drivers to use:
-* Go to Invoicing/Accounting > ... > Products, and create Products representing the Cost Types to use.
- On a Cost Type product, set:
+* Navigate to *Invoicing/Accounting > Configuration > Analytic Accounting > Cost Drivers*,
+ and create Products representing the Cost Types to use. Set:
- * "Is Cost Type" flag: checked. This will make the "Activity Costs" tab visible
- * In the "Activity Costs" tab, add a line for each cost line to generate, such as overhead, etc.
- Set the standard cost to use for each unit used. Thesw will roll up to the parent cost type standard cost.
+ * *Is Cost Type* flag: checked. This will make the "Activity Costs" tab visible
+ * *Activity Driven Costs* tab, add a line for each cost line to generate, such as overhead, etc.
+ Set the standard cost to use for each unit used. This will roll up to the parent standard cost.
diff --git a/account_analytic_wip/readme/DESCRIPTION.rst b/account_analytic_wip/readme/DESCRIPTION.rst
index bdf797974e..66018719e5 100644
--- a/account_analytic_wip/readme/DESCRIPTION.rst
+++ b/account_analytic_wip/readme/DESCRIPTION.rst
@@ -18,6 +18,6 @@ to generate the corresponding accounting moves.
Products can be seen as cost drivers, driving consumption of other items.
For example a machine work time can drive consumptions of Labor and Overhead.
-This feature models cost driver usage as Analytic Items.
-When an Analytic Item is created, it may then generate additional Analytic Items for the corresponding indirect costs.
-For example, each timesheet hour logged could generate a quantity and amount of overhead assigned to that activity.
+When an Analytic Item is created for a cost driver,
+additional Analytic Items are generated for the corresponding indirect costs.
+For example, each timesheet hour logged could generate a overhead amount related to that activity.
diff --git a/account_analytic_wip/readme/USAGE.rst b/account_analytic_wip/readme/USAGE.rst
index 3c31a6f52c..f575df2f86 100644
--- a/account_analytic_wip/readme/USAGE.rst
+++ b/account_analytic_wip/readme/USAGE.rst
@@ -1,17 +1,14 @@
-The "Analytic Tracking Items" holds planned amounts, and track their WIP and variances.
+The "Analytic Tracking Items" holds planned amounts, and tracks their WIP and variances.
These must be automatically created by specific logic in the Apps supporting them.
With this module alone the Tracking Item creation can be done manually:
* Navigate to ''Invoicing/Accounting > Reporting > Management > Analytic Tracking''
-
* Create an Analytic Tracking Item:
* Set the Analytic Account.
-
* Set the Product, use one that has a non-zero cost
and belongs to a category with the "Costing" section configured.
-
* Set the Planned Amount.
@@ -23,12 +20,9 @@ Analytic Items are used to record the actual costs:
* Create an Analytic Item:
* Set the Analytic Account, Description and Date.
-
* Set the Product, use one that has a non-zero cost
and belongs to a category with the "Costing" section configured.
-
* Set the quantity consumed.
-
* The Amount field should be automatically computed, with a negative amount.
@@ -44,9 +38,7 @@ and the comparison with the planned amounts. This can be used for analysis:
WIP and variances journal entries are generated by a scheduled job:
* Navigate to *Setting > Technical > Automation > Scheduled Actions*.
-
* Locate and open the *Account: Process WIP and Variances* record, and click on the RUN MANUALLY button.
-
* Check the generated journal entries, at *Accounting > Miscellaneous > Journal Entries*.
@@ -54,9 +46,11 @@ When creating Analytic Items, if a configuration is in place, the corresponding
* When an Analytic Item is created, an automatic process checks the Activity Based Cost Rules to identify the ones that apply.
* Each triggered rule created a new Analytic Item, with a copy of the original one, and:
- * Product: is the rule Cost Type Product. A validation error prevents this from being the same as the source Analytic Item Product, to avoid infinite loops.
- * Quantity: is the original quantity multiplied by the rule's Factor
- * Amount: is -1 * Quantity * Product Standard Price
- * Parent Analytic Item (new field): set with the original Analytic Item
+
+ * Product: is the rule Cost Type Product. A validation error prevents this from being the same as the source Analytic Item Product, to avoid infinite loops.
+ * Quantity: is the original quantity multiplied by the rule's Factor
+ * Amount: is -1 * Quantity * Product Standard Price
+ * Parent Analytic Item (new field): set with the original Analytic Item
+
* An update on the Quantity triggers a recalculation of the quantity and amount of the child Analytic Items.
* A delete cascades to the child Analytic Items, causing them to also be deleted.
diff --git a/account_analytic_wip/security/ir.model.access.csv b/account_analytic_wip/security/ir.model.access.csv
index 081bae21c5..09c8a97f49 100644
--- a/account_analytic_wip/security/ir.model.access.csv
+++ b/account_analytic_wip/security/ir.model.access.csv
@@ -1,5 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_analytic_tracking_item_user,Analytic Tracking Item: User,model_account_analytic_tracking_item,base.group_user,1,1,1,1
access_account_analytic_tracking_item_mngr,Analytic Tracking Item: Manager,model_account_analytic_tracking_item,account.group_account_manager,1,1,1,1
-access_activity_cost_rule_user,access_activity_cost_rule_user,model_activity_cost_rule,base.group_user,1,0,0,0
-access_activity_cost_rule_mngr,access_activity_cost_rule_mngr,model_activity_cost_rule,account.group_account_manager,1,1,1,1
diff --git a/account_analytic_wip/static/description/index.html b/account_analytic_wip/static/description/index.html
index 0f45b160ec..50ecd6eb0c 100644
--- a/account_analytic_wip/static/description/index.html
+++ b/account_analytic_wip/static/description/index.html
@@ -382,9 +382,9 @@
Analytic Accounting support for WIP and Variances
to generate the corresponding accounting moves.
Products can be seen as cost drivers, driving consumption of other items.
For example a machine work time can drive consumptions of Labor and Overhead.
-
This feature models cost driver usage as Analytic Items.
-When an Analytic Item is created, it may then generate additional Analytic Items for the corresponding indirect costs.
-For example, each timesheet hour logged could generate a quantity and amount of overhead assigned to that activity.
+
When an Analytic Item is created for a cost driver,
+additional Analytic Items are generated for the corresponding indirect costs.
+For example, each timesheet hour logged could generate a overhead amount related to that activity.
Important
This is an alpha version, the data model and design can change at any time without warning.
@@ -406,7 +406,7 @@
The “Analytic Tracking Items” holds planned amounts, and track their WIP and variances.
+
The “Analytic Tracking Items” holds planned amounts, and tracks their WIP and variances.
These must be automatically created by specific logic in the Apps supporting them.
With this module alone the Tracking Item creation can be done manually:
When creating Analytic Items, if a configuration is in place, the corresponding Analytic Items for indirect cost are generated.
When an Analytic Item is created, an automatic process checks the Activity Based Cost Rules to identify the ones that apply.
-
-
Each triggered rule created a new Analytic Item, with a copy of the original one, and:
-
+
Each triggered rule created a new Analytic Item, with a copy of the original one, and:
Product: is the rule Cost Type Product. A validation error prevents this from being the same as the source Analytic Item Product, to avoid infinite loops.
Quantity: is the original quantity multiplied by the rule’s Factor
Amount: is -1 * Quantity * Product Standard Price
Parent Analytic Item (new field): set with the original Analytic Item
-
-
An update on the Quantity triggers a recalculation of the quantity and amount of the child Analytic Items.
A delete cascades to the child Analytic Items, causing them to also be deleted.