# Copyright 2022-2025 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import api, fields, models class StockMove(models.Model): _inherit = "stock.move" task_id = fields.Many2one( comodel_name="project.task", string="Related Task", check_company=True, ) raw_material_task_id = fields.Many2one( comodel_name="project.task", string="Task for material", check_company=True ) @api.onchange("product_id") def _onchange_product_id(self): """It is necessary to overwrite the name to prevent set product name from being auto-defined.""" res = super()._onchange_product_id() if self.raw_material_task_id: self.name = self.raw_material_task_id.name return res def _prepare_analytic_line_from_task(self): product = self.product_id company_id = self.env.company task = self.task_id or self.raw_material_task_id analytic_account = ( task.stock_analytic_account_id or task.project_id.analytic_account_id ) if not analytic_account: return False # Apply sudo() in case there is any rule that does not allow access to # the analytic account, for example with analytic_hr_department_restriction analytic_account = analytic_account.sudo() res = { "date": ( task.stock_analytic_date or task.project_id.stock_analytic_date or fields.date.today() ), "name": task.name + ": " + product.name, "unit_amount": self.quantity_done, "account_id": analytic_account.id, "user_id": self._uid, "product_uom_id": self.product_uom.id, "company_id": analytic_account.company_id.id or self.env.company.id, "partner_id": task.partner_id.id or task.project_id.partner_id.id or False, "stock_task_id": task.id, } amount_unit = product.with_context(uom=self.product_uom.id).price_compute( "standard_price" )[product.id] amount = amount_unit * self.quantity_done or 0.0 result = round(amount, company_id.currency_id.decimal_places) * -1 vals = {"amount": result} analytic_line_fields = self.env["account.analytic.line"]._fields # Extra fields added in account addon if "ref" in analytic_line_fields: vals["ref"] = task.name if "product_id" in analytic_line_fields: vals["product_id"] = product.id # Prevent incoherence when hr_timesheet addon is installed. if "project_id" in analytic_line_fields: vals["project_id"] = False # distributions if task.stock_analytic_distribution: new_amount = 0 for distribution in task.stock_analytic_distribution.values(): new_amount -= (amount / 100) * distribution vals["amount"] = new_amount res.update(vals) return res @api.model def default_get(self, fields_list): defaults = super().default_get(fields_list) if self.env.context.get("default_raw_material_task_id"): task = self.env["project.task"].browse( self.env.context.get("default_raw_material_task_id") ) if not task.group_id: task.group_id = self.env["procurement.group"].create( task._prepare_procurement_group_vals() ) defaults.update( { "group_id": task.group_id.id, "location_id": ( task.location_id.id or task.project_id.location_id.id ), "location_dest_id": ( task.location_dest_id.id or task.project_id.location_dest_id.id ), "picking_type_id": ( task.picking_type_id.id or task.project_id.picking_type_id.id ), } ) return defaults def _action_done(self, cancel_backorder=False): """Create the analytical notes for stock movements linked to tasks.""" moves_todo = super()._action_done(cancel_backorder) # Use sudo to avoid error for users with no access to analytic analytic_line_model = self.env["account.analytic.line"].sudo() for move in moves_todo.filtered(lambda x: x.raw_material_task_id): vals = move._prepare_analytic_line_from_task() if vals: analytic_line_model.create(vals) return moves_todo def action_task_product_forecast_report(self): self.ensure_one() action = self.product_id.action_product_forecast_report() action["context"] = { "active_id": self.product_id.id, "active_model": "product.product", "move_to_match_ids": self.ids, } warehouse = self.warehouse_id if warehouse: action["context"]["warehouse"] = warehouse.id return action class StockMoveLine(models.Model): _inherit = "stock.move.line" task_id = fields.Many2one( comodel_name="project.task", string="Task", compute="_compute_task_id", store=True, ) @api.depends("move_id.raw_material_task_id", "move_id.task_id") def _compute_task_id(self): for item in self: task = ( item.move_id.raw_material_task_id if item.move_id.raw_material_task_id else item.move_id.task_id ) item.task_id = task if task else False