diff --git a/mrp_account_analytic/models/mrp_workorder.py b/mrp_account_analytic/models/mrp_workorder.py
index 39828861325..7dbe4989132 100644
--- a/mrp_account_analytic/models/mrp_workorder.py
+++ b/mrp_account_analytic/models/mrp_workorder.py
@@ -34,10 +34,11 @@ def generate_mrp_work_analytic_line(self):
@api.model_create_multi
def create(self, vals_list):
- timelog = super().create(vals_list)
- timelog_with_date_end = timelog.filtered("date_end")
- timelog_with_date_end.generate_mrp_work_analytic_line()
- return timelog
+ timelogs = super().create(vals_list)
+ for i, timelog in enumerate(timelogs):
+ if vals_list[i].get("date_end"):
+ timelog.generate_mrp_work_analytic_line()
+ return timelogs
def write(self, vals):
res = super().write(vals)
diff --git a/mrp_account_analytic/models/stock_move.py b/mrp_account_analytic/models/stock_move.py
index 7f6a0e60585..7f4f1243820 100644
--- a/mrp_account_analytic/models/stock_move.py
+++ b/mrp_account_analytic/models/stock_move.py
@@ -57,24 +57,27 @@ def write(self, vals):
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
- sm_has_qty_done = res.filtered("quantity_done")
- sm_has_qty_done.generate_mrp_raw_analytic_line()
+ for i, move in enumerate(res):
+ if vals_list[i].get("quantity_done"):
+ move.generate_mrp_raw_analytic_line()
return res
class StockMoveLine(models.Model):
_inherit = "stock.move.line"
- def write(self, vals):
- qty_done = vals.get("qty_done")
- res = super().write(vals)
- if qty_done:
- self.mapped("move_id").generate_mrp_raw_analytic_line()
- return res
+ # def write(self, vals):
+ # qty_done = vals.get("qty_done")
+ # for rec in self:
+ # if qty_done:
+ # rec.mapped("move_id").generate_mrp_raw_analytic_line()
+ # res = super().write(vals)
+ # return res
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
- sml_has_qty_done = res.filtered("qty_done")
- sml_has_qty_done.mapped("move_id").generate_mrp_raw_analytic_line()
+ for i, line in enumerate(res):
+ if vals_list[i].get("qty_done"):
+ line.mapped("move_id").generate_mrp_raw_analytic_line()
return res
diff --git a/mrp_account_analytic_wip/__init__.py b/mrp_account_analytic_wip/__init__.py
index bb83730e956..426f7ea72a5 100644
--- a/mrp_account_analytic_wip/__init__.py
+++ b/mrp_account_analytic_wip/__init__.py
@@ -2,3 +2,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
+# from . import wizards
diff --git a/mrp_account_analytic_wip/__manifest__.py b/mrp_account_analytic_wip/__manifest__.py
index f02e1b50087..15db50e20b7 100644
--- a/mrp_account_analytic_wip/__manifest__.py
+++ b/mrp_account_analytic_wip/__manifest__.py
@@ -12,6 +12,8 @@
"depends": [
"mrp_account_analytic",
"account_analytic_wip",
+ # TODO: abusive dependency, modularity should be improved
+ # "stock_inventory_revaluation_mrp",
],
"data": [
"views/mrp_production_views.xml",
diff --git a/mrp_account_analytic_wip/models/__init__.py b/mrp_account_analytic_wip/models/__init__.py
index 1f6e804ab80..144b2347b12 100644
--- a/mrp_account_analytic_wip/models/__init__.py
+++ b/mrp_account_analytic_wip/models/__init__.py
@@ -1,9 +1,9 @@
from . import account_analytic_line
from . import account_analytic_tracking
-from . import mrp_bom
+#from . import mrp_bom
from . import mrp_production
from . import mrp_workcenter
from . import mrp_workorder
from . import stock_move
from . import stock_location
-from . import product
+#from . import product
diff --git a/mrp_account_analytic_wip/models/account_analytic_tracking.py b/mrp_account_analytic_wip/models/account_analytic_tracking.py
index 25c6421573c..1b278f661d4 100644
--- a/mrp_account_analytic_wip/models/account_analytic_tracking.py
+++ b/mrp_account_analytic_wip/models/account_analytic_tracking.py
@@ -115,13 +115,14 @@ def _get_unit_cost(self):
"product_id.standard_price",
"actual_stock_move_ids",
"actual_workorder_ids",
+ "stock_move_id"
)
def _compute_actual_amount(self):
currency = self.env.company.currency_id
for item in self:
if item.state == "cancel" or item.child_ids:
item.actual_amount = 0.0
- elif item.state == "done":
+ elif item.state == 'done':
return
elif not item.production_id:
super(AnalyticTrackingItem, item)._compute_actual_amount()
@@ -129,6 +130,11 @@ def _compute_actual_amount(self):
# Specific Actuals calculation on MOs, using current cost
# instead of the historical cost stored in Anaytic Items
unit_cost = item.product_id.standard_price
+ if item.stock_move_id.product_id.tracking=='serial' and \
+ item.stock_move_id.raw_material_production_id:
+ # Calculate actual cost based on real price of SN instead of standard.
+ unit_cost = sum(item.stock_move_id.mapped("move_line_ids").mapped("lot_id").mapped("real_price"))
+ workcenter = item.workcenter_id or item.workorder_id.workcenter_id
items = item | item.parent_id
raw_qty = sum(items.actual_stock_move_ids.mapped("quantity_done"))
ops_qty = sum(items.actual_workorder_ids.mapped("duration")) / 60
diff --git a/mrp_account_analytic_wip/models/mrp_bom.py b/mrp_account_analytic_wip/models/mrp_bom.py
index a08d52ed469..7cbf090bab0 100644
--- a/mrp_account_analytic_wip/models/mrp_bom.py
+++ b/mrp_account_analytic_wip/models/mrp_bom.py
@@ -1,6 +1,7 @@
# Copyright (C) 2023 Open Source Integrators
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+## this can be deleted. not needed with FIFO
from odoo import api, fields, models
@@ -14,8 +15,8 @@ def _prepare_raw_tracking_item_values(self, product_uom_qty):
self.ensure_one()
product = self.product_tmpl_id or self.product_id
bom_qty = self.product_uom_id._compute_quantity(
- self.product_qty, product.uom_id
- )
+ self.product_qty, product.uom_id
+ )
factor = product_uom_qty / bom_qty
# Each distinct Product will be one Tracking Item
# So multiple BOM lines for the same Product need to be aggregated
@@ -25,8 +26,7 @@ def _prepare_raw_tracking_item_values(self, product_uom_qty):
"product_id": product.id,
"planned_qty": sum(
x.product_qty for x in lines if x.product_id == product
- )
- * factor,
+ ) * factor,
}
for product in lines.product_id
]
@@ -37,8 +37,8 @@ def _prepare_ops_tracking_item_values(self, product_uom_qty):
# So multiple BOM lines for the same Work Center need to be aggregated
product = self.product_tmpl_id or self.product_id
bom_qty = self.product_uom_id._compute_quantity(
- self.product_qty, product.uom_id
- )
+ self.product_qty, product.uom_id
+ )
factor = product_uom_qty / bom_qty
lines = self.operation_ids
return [
@@ -47,8 +47,7 @@ def _prepare_ops_tracking_item_values(self, product_uom_qty):
"workcenter_id": workcenter.id,
"planned_qty": sum(
x.time_cycle for x in lines if x.workcenter_id == workcenter
- )
- * factor
+ ) * factor
/ 60,
}
for workcenter in lines.workcenter_id
diff --git a/mrp_account_analytic_wip/models/mrp_production.py b/mrp_account_analytic_wip/models/mrp_production.py
index 034da345791..481e5a74684 100644
--- a/mrp_account_analytic_wip/models/mrp_production.py
+++ b/mrp_account_analytic_wip/models/mrp_production.py
@@ -4,7 +4,8 @@
import logging
from odoo import _, api, exceptions, fields, models
-from odoo.tools import float_round
+from odoo.tools import float_is_zero, float_round
+from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
@@ -66,13 +67,14 @@ def _get_tracking_items(self):
Returns a recordset with the related Ttacking Items
"""
return (
- self.bom_analytic_tracking_item_ids
- | self.bom_analytic_tracking_item_ids.child_ids
+ self.mapped("move_raw_ids.analytic_tracking_item_id")
+ | self.mapped("workorder_ids.analytic_tracking_item_id")
+ | self.mapped("workorder_ids.analytic_tracking_item_id.child_ids")
)
@api.depends(
- "bom_analytic_tracking_item_ids",
- "bom_analytic_tracking_item_ids.child_ids",
+ "move_raw_ids.analytic_tracking_item_id",
+ "workorder_ids.analytic_tracking_item_id",
)
def _compute_analytic_tracking_item(self):
for mo in self:
@@ -104,7 +106,11 @@ def _cal_price(self, consumed_moves):
)
extra_cost = self.extra_cost * qty_done
total_cost = (
- -sum(consumed_moves.sudo().stock_valuation_layer_ids.mapped("value"))
+ -sum(
+ consumed_moves.sudo()
+ .stock_valuation_layer_ids.filtered(lambda svl: svl.quantity < 0)
+ .mapped("value")
+ )
+ work_center_cost
+ extra_cost
)
@@ -165,8 +171,9 @@ def action_post_inventory_wip(self, cancel_backorder=False):
"""
for order in self:
moves_all = order.move_raw_ids
- for move in moves_all.filtered(lambda m: m.quantity_done):
- move.product_uom_qty = move.quantity_done
+ # comment code as if move having more then 1 qty in then if update the cousume qty it change the The cousume Qty
+ # for move in moves_all.filtered(lambda m: m.quantity_done):
+ # move.product_uom_qty = move.quantity_done
# Raw Material Consumption, closely following _post_inventory()
moves_not_to_do = order.move_raw_ids.filtered(lambda x: x.state == "done")
moves_to_do = order.move_raw_ids.filtered(
@@ -211,7 +218,7 @@ def _prepare_clear_wip_account_line(self, account, amount):
# "analytic_account_id": self.analytic_account_id.id,
}
- def clear_wip_final_old(self):
+ def clear_wip_final(self):
"""
Add final Clear WIP JE journal entry.
Looks up the WIP account balance and clears it using the Variance account.
@@ -283,7 +290,7 @@ def _prepare_clear_wip_account_move_line(self, product, account, amount):
"credit": -amount if amount < 0.0 else 0.0,
}
- def clear_wip_final(self):
+ def clear_wip_final_boak(self):
"""
Add final Clear WIP JE journal entry using tracked items.
Looks up the WIP account balance and clears it using the Variance account.
@@ -301,9 +308,7 @@ def clear_wip_final(self):
move_lines.extend(
[
prod._prepare_clear_wip_account_move_line(
- product,
- acc_wip_prod,
- prod.product_uom_qty * product.standard_price,
+ product, acc_wip_prod, prod.product_uom_qty * product.standard_price
)
]
)
@@ -323,7 +328,7 @@ def clear_wip_final(self):
prod._prepare_clear_wip_account_move_line(
item.product_id,
accounts["stock_wip"],
- -round(item.actual_amount, 2),
+ -round(item.actual_amount,2),
)
]
)
@@ -334,7 +339,7 @@ def clear_wip_final(self):
prod._prepare_clear_wip_account_move_line(
item.product_id,
accounts["stock_variance"],
- round(item.difference_actual_amount, 2),
+ round(item.difference_actual_amount,2)
)
]
)
@@ -348,7 +353,7 @@ def clear_wip_final(self):
prod._prepare_clear_wip_account_move_line(
item.product_id,
accounts["stock_wip"],
- -round(item.actual_amount, 2),
+ -round(item.actual_amount,2),
)
]
)
@@ -387,15 +392,15 @@ def clear_wip_final(self):
debit = 0.0
credit = 0.0
for line in move_lines:
- debit += line["debit"]
- credit += line["credit"]
+ debit += line['debit']
+ credit += line['credit']
if credit - debit:
move_lines.extend(
[
prod._prepare_clear_wip_account_move_line(
prod.product_id,
accounts["stock_variance"],
- -round(debit - credit, 2),
+ -round(debit-credit, 2),
)
]
)
@@ -433,7 +438,8 @@ def action_confirm(self):
just after MO confirmation.
"""
res = super().action_confirm()
- self.populate_ref_bom_tracking_items()
+ self.mapped("move_raw_ids").populate_tracking_items(set_planned=True)
+ self.mapped("workorder_ids").populate_tracking_items(set_planned=True)
return res
def _get_matching_tracking_item(self, vals, new_tracking_items=None):
@@ -530,12 +536,8 @@ def populate_ref_bom_tracking_items(self):
reference_bom = production.product_id.cost_reference_bom_id
if not reference_bom:
continue
- ref_raw_vals = reference_bom._prepare_raw_tracking_item_values(
- production.product_uom_qty
- )
- ref_ops_vals = reference_bom._prepare_ops_tracking_item_values(
- production.product_uom_qty
- )
+ ref_raw_vals = reference_bom._prepare_raw_tracking_item_values(production.product_uom_qty)
+ ref_ops_vals = reference_bom._prepare_ops_tracking_item_values(production.product_uom_qty)
ref_items = production._populate_ref_bom_tracking_items(
ref_raw_vals + ref_ops_vals
)
@@ -577,7 +579,70 @@ def button_mark_done(self):
# tracking.clear_wip_journal_entries()
# Raw Material - clear final WIP and post Variances
mfg_done.clear_wip_final()
+
+ # Below code will fix the FIFO SN costing for Raw material, FG and By product
+ #NOT NEEDED FOR CABINOTCH
+ # if self.product_id.cost_method =='fifo':
+ # # recalculate all JE for last MO
+ # finished_move = self.move_finished_ids.filtered(
+ # lambda x: x.product_id == self.product_id and x.state == 'done' and x.quantity_done > 0)
+ # consumed_moves = self.move_raw_ids
+ # if finished_move:
+ # work_center_cost = 0
+ # finished_move.ensure_one()
+ # fg_svl_ids = finished_move.sudo().stock_valuation_layer_ids
+ # for work_order in self.workorder_ids:
+ # time_lines = work_order.time_ids.filtered(lambda t: t.date_end and not t.cost_already_recorded)
+ # work_center_cost += work_order._cal_cost(times=time_lines)
+ # time_lines.write({'cost_already_recorded': True})
+ # qty_done = finished_move.product_uom._compute_quantity(
+ # finished_move.quantity_done, finished_move.product_id.uom_id)
+ # extra_cost = self.extra_cost * qty_done
+ # total_cost = - sum(consumed_moves.sudo().stock_valuation_layer_ids.mapped('value')) + work_center_cost + extra_cost
+ # byproduct_moves = self.move_byproduct_ids.filtered(lambda m: m.state == 'done' and m.quantity_done > 0)
+ # byproduct_cost_share = 0
+ # for byproduct in byproduct_moves:
+ # if byproduct.cost_share == 0:
+ # continue
+ # byproduct_cost_share += byproduct.cost_share
+ # if byproduct.product_id.cost_method in ('fifo', 'average'):
+ # byproduct.price_unit = total_cost * byproduct.cost_share / 100 / byproduct.product_uom._compute_quantity(byproduct.quantity_done, byproduct.product_id.uom_id)
+ # by_product_svl = byproduct.sudo().stock_valuation_layer_ids
+ # self._correct_svl_je(by_product_svl, byproduct, byproduct.price_unit)
+ # if finished_move.product_id.cost_method in ('fifo', 'average'):
+ # finished_move.price_unit = total_cost * float_round(1 - byproduct_cost_share / 100, precision_rounding=0.0001) / qty_done
+ # total_cost = finished_move.price_unit
+ # self.lot_producing_id.real_price = total_cost
+ # fg_svl = finished_move.stock_valuation_layer_ids and finished_move.stock_valuation_layer_ids[0] or []
+ # self._correct_svl_je(fg_svl, finished_move, total_cost)
+ if self.analytic_account_id:
+ self.analytic_account_id.line_ids.write({'manufacturing_order_id':self.id})
return res
+
+ # NOT NEEDED FOR CABINOTCH
+ # def _correct_svl_je(self, svl, stock_move, total_cost):
+ # account_move_id = svl.account_move_id
+ # svl.unit_cost = total_cost / (svl.quantity if svl.quantity>0 else 1)
+ # svl.value = svl.unit_cost * svl.quantity
+ # svl.remaining_value = svl.unit_cost * svl.quantity
+ #
+ # if not account_move_id:
+ # svl._validate_accounting_entries()
+ # else:
+ # # Change the SVl with correct cost
+ # account_move_id.button_draft()
+ # # The Valuation Layer has been changed,
+ # # now we have to edit the STJ Entry
+ # for ji_id in account_move_id.line_ids:
+ # if ji_id.credit != 0:
+ # ji_id.with_context(check_move_validity=False).write(
+ # {"credit": total_cost}
+ # )
+ # else:
+ # ji_id.with_context(check_move_validity=False).write(
+ # {"debit": total_cost}
+ # )
+ # account_move_id.action_post()
def action_cancel(self):
res = super().action_cancel()
@@ -611,11 +676,82 @@ def write(self, vals):
)
if "analytic_account_id" in vals or is_workcenter_change:
+ # From BOAK Code
+ # confirmed_mos = self.filtered(lambda x: x.state == "confirmed")
+ # confirmed_mos.populate_ref_bom_tracking_items()
confirmed_mos = self.filtered(lambda x: x.state == "confirmed")
- confirmed_mos.populate_ref_bom_tracking_items()
+ confirmed_mos.move_raw_ids.populate_tracking_items()
+ confirmed_mos.workorder_ids.populate_tracking_items()
return True
def copy_data(self, default=None):
default = dict(default or {})
default["bom_analytic_tracking_item_ids"] = False
return super(MRPProduction, self).copy_data(default=default)
+
+ def _create_workorder(self):
+ res = super()._create_workorder()
+ for production in self:
+ for workorder in production.workorder_ids:
+ workorder.duration_planned = workorder.duration_expected
+ return res
+
+ def _check_sn_uniqueness(self):
+ """ Alert the user if the serial number as already been consumed/produced
+ WIP Module is also creating other JE with Virtual / Production Location.
+ We need to bypass the check for Current Production, there can be multiple moves with Virtual Production for same SN in WIP module.
+ """
+ if self.product_tracking == 'serial' and self.lot_producing_id:
+ if self._is_finished_sn_already_produced(self.lot_producing_id):
+ raise UserError(_('This serial number for product %s has already been produced', self.product_id.name))
+
+ for move in self.move_finished_ids:
+ if move.has_tracking != 'serial' or move.product_id == self.product_id:
+ continue
+ for move_line in move.move_line_ids:
+ if self._is_finished_sn_already_produced(move_line.lot_id, excluded_sml=move_line):
+ raise UserError(_('The serial number %(number)s used for byproduct %(product_name)s has already been produced',
+ number=move_line.lot_id.name, product_name=move_line.product_id.name))
+
+ for move in self.move_raw_ids:
+ if move.has_tracking != 'serial':
+ continue
+ for move_line in move.move_line_ids:
+ if float_is_zero(move_line.qty_done, precision_rounding=move_line.product_uom_id.rounding):
+ continue
+ message = _('The serial number %(number)s used for component %(component)s has already been consumed',
+ number=move_line.lot_id.name,
+ component=move_line.product_id.name)
+ co_prod_move_lines = self.move_raw_ids.move_line_ids
+
+ # Check presence of same sn in previous productions
+ duplicates = self.env['stock.move.line'].search_count([
+ ('lot_id', '=', move_line.lot_id.id),
+ ('qty_done', '=', 1),
+ ('state', '=', 'done'),
+ ('location_dest_id.usage', '=', 'production'),
+ ('production_id', '!=', False),
+ ('production_id', '!=',self.id) #In this core odoo method only this change has been added.
+ ])
+ if duplicates:
+ # Maybe some move lines have been compensated by unbuild
+ duplicates_returned = move.product_id._count_returned_sn_products(move_line.lot_id)
+ removed = self.env['stock.move.line'].search_count([
+ ('lot_id', '=', move_line.lot_id.id),
+ ('state', '=', 'done'),
+ ('location_dest_id.scrap_location', '=', True)
+ ])
+ unremoved = self.env['stock.move.line'].search_count([
+ ('lot_id', '=', move_line.lot_id.id),
+ ('state', '=', 'done'),
+ ('location_id.scrap_location', '=', True),
+ ('location_dest_id.scrap_location', '=', False),
+ ])
+ # Either removed or unbuild
+ if not ((duplicates_returned or removed) and duplicates - duplicates_returned - removed + unremoved == 0):
+ raise UserError(message)
+ # Check presence of same sn in current production
+ duplicates = co_prod_move_lines.filtered(lambda ml: ml.qty_done and ml.lot_id == move_line.lot_id) - move_line
+ if duplicates:
+ raise UserError(message)
+
diff --git a/mrp_account_analytic_wip/models/mrp_workorder.py b/mrp_account_analytic_wip/models/mrp_workorder.py
index 9c42d036cca..5fc39bc7c1b 100644
--- a/mrp_account_analytic_wip/models/mrp_workorder.py
+++ b/mrp_account_analytic_wip/models/mrp_workorder.py
@@ -12,18 +12,66 @@ class MRPWorkOrder(models.Model):
analytic_tracking_item_id = fields.Many2one(
"account.analytic.tracking.item", string="Tracking Item", copy=False
)
+ # Operations added after MO confirmation have expected qty zero
+ duration_expected = fields.Float(default=0.0)
# Make MO lock status available for views
is_locked = fields.Boolean(related="production_id.is_locked")
duration_planned = fields.Float(string="Planned Duration")
- @api.model_create_multi
- def create(self, vals_list):
- new_workorders = super().create(vals_list)
- new_workorders.production_id.populate_ref_bom_tracking_items()
- return new_workorders
+ # From BOAK Code
+ # @api.model_create_multi
+ #def create(self, vals_list):
+ # new_workorders = super().create(vals_list)
+ # new_workorders.production_id.populate_ref_bom_tracking_items()
+ # return new_workorders
# FIXME: manual time entry on Wokr Order does not generate analytic items!
+ def _prepare_tracking_item_values(self):
+ analytic = self.production_id.analytic_account_id
+ planned_qty = self.duration_planned / 60
+ return analytic and {
+ "analytic_id": analytic.id,
+ "product_id": self.workcenter_id.analytic_product_id.id,
+ "workorder_id": self.id,
+ "planned_qty": planned_qty,
+ "production_id" : self.production_id.id
+ }
+
+ def populate_tracking_items(self, set_planned=False):
+ """
+ When creating a Work Order link it to a Tracking Item.
+ It may be an existing Tracking Item,
+ or a new one my be created if it doesn't exist yet.
+ """
+ TrackingItem = self.env["account.analytic.tracking.item"]
+ to_populate = self.filtered(
+ lambda x: x.production_id.analytic_account_id
+ and x.production_id.state not in ("draft", "done", "cancel")
+ )
+ all_tracking = to_populate.production_id.analytic_tracking_item_ids
+ for item in to_populate:
+ tracking = all_tracking.filtered(lambda x: x.workorder_id == self)[:1]
+ vals = item._prepare_tracking_item_values()
+ not set_planned and vals.pop("planned_qty")
+ if tracking:
+ tracking.write(vals)
+ else:
+ tracking = TrackingItem.create(vals)
+ item.analytic_tracking_item_id = tracking
+
+ @api.model_create_multi
+ def create(self, vals):
+ new_workorder = super().create(vals)
+ new_workorder.populate_tracking_items()
+ return new_workorder
+
+
+ # def write(self, vals):
+ # res = super().write(vals)
+ # for timelog in self.time_ids:
+ # timelog.generate_mrp_work_analytic_line()
+ # return res
class MrpWorkcenterProductivity(models.Model):
_inherit = "mrp.workcenter.productivity"
@@ -31,6 +79,7 @@ class MrpWorkcenterProductivity(models.Model):
def _prepare_mrp_workorder_analytic_item(self):
values = super()._prepare_mrp_workorder_analytic_item()
# Ensure the related Tracking Item is populated
+
workorder = self.workorder_id
if not workorder.analytic_tracking_item_id:
item_vals = {
@@ -44,20 +93,25 @@ def _prepare_mrp_workorder_analytic_item(self):
values["product_id"] = workorder.workcenter_id.analytic_product_id.id
return values
-
-class MrpWorkcenterProductivityLoss(models.Model):
- _inherit = "mrp.workcenter.productivity.loss"
-
- def _convert_to_duration(self, date_start, date_stop, workcenter=False):
- """Convert a date range into a duration in minutes.
- If the productivity type is not from an employee (extra hours are allow)
- and the workcenter has a calendar, convert the dates into a duration based on
- working hours.
- """
- duration = super()._convert_to_duration(date_start, date_stop, workcenter)
- if workcenter and workcenter.resource_calendar_id:
- r = workcenter._get_work_days_data_batch(date_start, date_stop)[
- workcenter.id
- ]["hours"]
- duration = r * 60
- return duration
+ def generate_mrp_work_analytic_line(self):
+ res = super().generate_mrp_work_analytic_line()
+ # When recording actuals, consider posting WIp immedately
+ mos_to_post = self.production_id.filtered("is_post_wip_automatic")
+ mos_to_post.action_post_inventory_wip()
+ return res
+
+# class MrpWorkcenterProductivityLoss(models.Model):
+# _inherit = "mrp.workcenter.productivity.loss"
+#
+# def _convert_to_duration(self, date_start, date_stop, workcenter=False):
+# """ Convert a date range into a duration in minutes.
+# If the productivity type is not from an employee (extra hours are allow)
+# and the workcenter has a calendar, convert the dates into a duration based on
+# working hours.
+# """
+# duration = super()._convert_to_duration(date_start, date_stop, workcenter)
+# for productivity_loss in self:
+# if workcenter and workcenter.resource_calendar_id:
+# r = workcenter._get_work_days_data_batch(date_start, date_stop)[workcenter.id]['hours']
+# duration = r * 60
+# return duration
diff --git a/mrp_account_analytic_wip/models/product.py b/mrp_account_analytic_wip/models/product.py
index 01a51e8064f..bffebf1f8ff 100644
--- a/mrp_account_analytic_wip/models/product.py
+++ b/mrp_account_analytic_wip/models/product.py
@@ -1,5 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+## this can be deleted. not needed with FIFO
+
from odoo import fields, models
diff --git a/mrp_account_analytic_wip/models/stock_move.py b/mrp_account_analytic_wip/models/stock_move.py
index 7657257b999..04f685ad63d 100644
--- a/mrp_account_analytic_wip/models/stock_move.py
+++ b/mrp_account_analytic_wip/models/stock_move.py
@@ -10,6 +10,15 @@ class StockMove(models.Model):
qty_planned = fields.Float()
+ # Store related Tracking Item, for computation efficiency
+ analytic_tracking_item_id = fields.Many2one(
+ "account.analytic.tracking.item",
+ string="Tracking Item",
+ copy=True
+ # Copy Tracking item, so that when a move is split,
+ # it still related to the same Tracking Item
+ )
+
# Improve the unconsume descrition on SVL and JE
# (originally "Correction of False (modification of past move)")
# and add link to the MO Tracking Items
@@ -56,15 +65,6 @@ def _compute_should_consume_qty(self):
move.should_consume_qty = 0
return res
- # Store related Tracking Item, for computation efficiency
- analytic_tracking_item_id = fields.Many2one(
- "account.analytic.tracking.item",
- string="Tracking Item",
- copy=True
- # Copy Tracking item, so that when a move is split,
- # it still related to the same Tracking Item
- )
-
def _prepare_mrp_raw_material_analytic_line(self):
values = super()._prepare_mrp_raw_material_analytic_line()
# Ensure the related Tracking Item is populated
@@ -81,19 +81,72 @@ def _prepare_mrp_raw_material_analytic_line(self):
return values
def generate_mrp_raw_analytic_line(self):
+ # From Boak Code
res = super().generate_mrp_raw_analytic_line()
# When recording actuals, consider posting WIP immediately
mos_to_post = self.raw_material_production_id.filtered("is_post_wip_automatic")
mos_to_post.action_post_inventory_wip()
return res
- @api.model
- def create(self, vals):
- new_moves = super().create(vals)
- new_moves.raw_material_production_id.populate_ref_bom_tracking_items()
- return new_moves
+ def _prepare_tracking_item_values(self):
+ analytic = self.raw_material_production_id.analytic_account_id
+ planned_qty = self.qty_planned
+ return {
+ "analytic_id": analytic.id,
+ "product_id": self.product_id.id,
+ "stock_move_id": self.id,
+ "planned_qty": self.product_uom_qty,
+ "production_id" : self.raw_material_production_id.id
+ }
+
+ def populate_tracking_items(self, set_planned=False):
+ """
+ When creating an Analytic Item,
+ link it to a Tracking Item, the may have to be created if it doesn't exist.
+ """
+ TrackingItem = self.env["account.analytic.tracking.item"]
+ to_populate = self.filtered(
+ lambda x: x.raw_material_production_id.analytic_account_id
+ and x.raw_material_production_id.state not in ("draft", "done", "cancel")
+ )
+ all_tracking = to_populate.raw_material_production_id.analytic_tracking_item_ids
+ for item in to_populate:
+ tracking = all_tracking.filtered(
+ lambda x: x.stock_move_id and x.product_id == item.product_id
+ )[:1]
+ vals = item._prepare_tracking_item_values()
+ not set_planned and vals.pop("planned_qty")
+ if self._context.get("from_create"):
+ tracking = TrackingItem.create(vals)
+ else:
+ if tracking:
+ tracking.write(vals)
+ else:
+ tracking = TrackingItem.create(vals)
+ item.analytic_tracking_item_id = tracking
+ @api.model_create_multi
+ def create(self, vals_list):
+ new_moves = super().create(vals_list)
+ # From BOAK Code
+ # new_moves.raw_material_production_id.populate_ref_bom_tracking_items()
+ new_moves.with_context(from_create=True).populate_tracking_items()
+ return new_moves
+
def write(self, vals):
res = super().write(vals)
- self.raw_material_production_id.populate_ref_bom_tracking_items()
+ if self.raw_material_production_id.analytic_tracking_item_ids:
+ self.raw_material_production_id.analytic_tracking_item_ids._compute_actual_amount()
+ # From Boak Code
+ # self.raw_material_production_id.populate_ref_bom_tracking_items()
+ # return res
+
+ if not self.env.context.get("flag_write_tracking"):
+ moves = self.filtered(
+ lambda x: x.raw_material_production_id.analytic_account_id
+ and not x.analytic_tracking_item_id
+ )
+ moves and moves.with_context(
+ flag_write_tracking=True
+ ).populate_tracking_items()
return res
diff --git a/mrp_account_analytic_wip/views/mrp_production_views.xml b/mrp_account_analytic_wip/views/mrp_production_views.xml
index 8e6a36f9a5b..eb4ab1c2b35 100644
--- a/mrp_account_analytic_wip/views/mrp_production_views.xml
+++ b/mrp_account_analytic_wip/views/mrp_production_views.xml
@@ -4,7 +4,7 @@
mrp.production
-
diff --git a/mrp_account_analytic_wip/wizards/__init__.py b/mrp_account_analytic_wip/wizards/__init__.py
new file mode 100644
index 00000000000..e779b25cdca
--- /dev/null
+++ b/mrp_account_analytic_wip/wizards/__init__.py
@@ -0,0 +1 @@
+from . import mrp_confirmation
diff --git a/mrp_account_analytic_wip/wizards/mrp_confirmation.py b/mrp_account_analytic_wip/wizards/mrp_confirmation.py
new file mode 100644
index 00000000000..c5b3fba0d6d
--- /dev/null
+++ b/mrp_account_analytic_wip/wizards/mrp_confirmation.py
@@ -0,0 +1,10 @@
+from odoo import models
+
+class MrpConfirmation(models.TransientModel):
+ _inherit = "mrp.confirmation"
+
+ def do_confirm(self):
+ for record in self:
+ if record.working_duration == 0.0:
+ record.date_end = record.date_start
+ super().do_confirm()