summaryrefslogtreecommitdiff
path: root/addons/mrp_account/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/mrp_account/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mrp_account/models')
-rw-r--r--addons/mrp_account/models/__init__.py6
-rw-r--r--addons/mrp_account/models/mrp_production.py92
-rw-r--r--addons/mrp_account/models/mrp_workcenter.py11
-rw-r--r--addons/mrp_account/models/product.py87
-rw-r--r--addons/mrp_account/models/stock_move.py29
5 files changed, 225 insertions, 0 deletions
diff --git a/addons/mrp_account/models/__init__.py b/addons/mrp_account/models/__init__.py
new file mode 100644
index 00000000..520901ce
--- /dev/null
+++ b/addons/mrp_account/models/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import mrp_workcenter
+from . import mrp_production
+from . import product
+from . import stock_move
diff --git a/addons/mrp_account/models/mrp_production.py b/addons/mrp_account/models/mrp_production.py
new file mode 100644
index 00000000..460e5a6d
--- /dev/null
+++ b/addons/mrp_account/models/mrp_production.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from ast import literal_eval
+
+from odoo import api, fields, models
+from odoo.tools import float_is_zero
+
+
+class MrpProductionWorkcenterLineTime(models.Model):
+ _inherit = 'mrp.workcenter.productivity'
+
+ cost_already_recorded = fields.Boolean('Cost Recorded', help="Technical field automatically checked when a ongoing production posts journal entries for its costs. This way, we can record one production's cost multiple times and only consider new entries in the work centers time lines.")
+
+
+class MrpProduction(models.Model):
+ _inherit = 'mrp.production'
+
+ extra_cost = fields.Float(copy=False, help='Extra cost per produced unit')
+ show_valuation = fields.Boolean(compute='_compute_show_valuation')
+
+ def _compute_show_valuation(self):
+ for order in self:
+ order.show_valuation = any(m.state == 'done' for m in order.move_finished_ids)
+
+ def _cal_price(self, consumed_moves):
+ """Set a price unit on the finished move according to `consumed_moves`.
+ """
+ super(MrpProduction, self)._cal_price(consumed_moves)
+ work_center_cost = 0
+ finished_move = self.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state not in ('done', 'cancel') and x.quantity_done > 0)
+ if finished_move:
+ finished_move.ensure_one()
+ for work_order in self.workorder_ids:
+ time_lines = work_order.time_ids.filtered(lambda x: x.date_end and not x.cost_already_recorded)
+ duration = sum(time_lines.mapped('duration'))
+ time_lines.write({'cost_already_recorded': True})
+ work_center_cost += (duration / 60.0) * work_order.workcenter_id.costs_hour
+ if finished_move.product_id.cost_method in ('fifo', 'average'):
+ 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
+ finished_move.price_unit = (sum([-m.stock_valuation_layer_ids.value for m in consumed_moves.sudo()]) + work_center_cost + extra_cost) / qty_done
+ return True
+
+ def _prepare_wc_analytic_line(self, wc_line):
+ wc = wc_line.workcenter_id
+ hours = wc_line.duration / 60.0
+ value = hours * wc.costs_hour
+ account = wc.costs_hour_account_id.id
+ return {
+ 'name': wc_line.name + ' (H)',
+ 'amount': -value,
+ 'account_id': account,
+ 'ref': wc.code,
+ 'unit_amount': hours,
+ 'company_id': self.company_id.id,
+ }
+
+ def _costs_generate(self):
+ """ Calculates total costs at the end of the production.
+ """
+ self.ensure_one()
+ AccountAnalyticLine = self.env['account.analytic.line'].sudo()
+ for wc_line in self.workorder_ids.filtered('workcenter_id.costs_hour_account_id'):
+ vals = self._prepare_wc_analytic_line(wc_line)
+ precision_rounding = (wc_line.workcenter_id.costs_hour_account_id.currency_id or self.company_id.currency_id).rounding
+ if not float_is_zero(vals.get('amount', 0.0), precision_rounding=precision_rounding):
+ # we use SUPERUSER_ID as we do not guarantee an mrp user
+ # has access to account analytic lines but still should be
+ # able to produce orders
+ AccountAnalyticLine.create(vals)
+
+ def _get_backorder_mo_vals(self):
+ res = super()._get_backorder_mo_vals()
+ res['extra_cost'] = self.extra_cost
+ return res
+
+ def button_mark_done(self):
+ res = super(MrpProduction, self).button_mark_done()
+ for order in self:
+ order._costs_generate()
+ return res
+
+ def action_view_stock_valuation_layers(self):
+ self.ensure_one()
+ domain = [('id', 'in', (self.move_raw_ids + self.move_finished_ids + self.scrap_ids.move_id).stock_valuation_layer_ids.ids)]
+ action = self.env["ir.actions.actions"]._for_xml_id("stock_account.stock_valuation_layer_action")
+ context = literal_eval(action['context'])
+ context.update(self.env.context)
+ context['no_at_date'] = True
+ context['search_default_group_by_product_id'] = False
+ return dict(action, domain=domain, context=context)
diff --git a/addons/mrp_account/models/mrp_workcenter.py b/addons/mrp_account/models/mrp_workcenter.py
new file mode 100644
index 00000000..20c9fcd0
--- /dev/null
+++ b/addons/mrp_account/models/mrp_workcenter.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class MrpWorkcenter(models.Model):
+ _inherit = 'mrp.workcenter'
+
+ costs_hour_account_id = fields.Many2one('account.analytic.account', string='Analytic Account',
+ help="Fill this only if you want automatic analytic accounting entries on production orders.")
diff --git a/addons/mrp_account/models/product.py b/addons/mrp_account/models/product.py
new file mode 100644
index 00000000..8eb1e010
--- /dev/null
+++ b/addons/mrp_account/models/product.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models, _
+from odoo.exceptions import UserError
+
+
+class ProductTemplate(models.Model):
+ _name = 'product.template'
+ _inherit = 'product.template'
+
+ def action_bom_cost(self):
+ templates = self.filtered(lambda t: t.product_variant_count == 1 and t.bom_count > 0)
+ if templates:
+ return templates.mapped('product_variant_id').action_bom_cost()
+
+ def button_bom_cost(self):
+ templates = self.filtered(lambda t: t.product_variant_count == 1 and t.bom_count > 0)
+ if templates:
+ return templates.mapped('product_variant_id').button_bom_cost()
+
+
+class ProductProduct(models.Model):
+ _name = 'product.product'
+ _inherit = 'product.product'
+ _description = 'Product'
+
+ def button_bom_cost(self):
+ self.ensure_one()
+ self._set_price_from_bom()
+
+ def action_bom_cost(self):
+ boms_to_recompute = self.env['mrp.bom'].search(['|', ('product_id', 'in', self.ids), '&', ('product_id', '=', False), ('product_tmpl_id', 'in', self.mapped('product_tmpl_id').ids)])
+ for product in self:
+ product._set_price_from_bom(boms_to_recompute)
+
+ def _set_price_from_bom(self, boms_to_recompute=False):
+ self.ensure_one()
+ bom = self.env['mrp.bom']._bom_find(product=self)
+ if bom:
+ self.standard_price = self._compute_bom_price(bom, boms_to_recompute=boms_to_recompute)
+
+ def _compute_average_price(self, qty_invoiced, qty_to_invoice, stock_moves):
+ self.ensure_one()
+ if stock_moves.product_id == self:
+ return super()._compute_average_price(qty_invoiced, qty_to_invoice, stock_moves)
+ bom = self.env['mrp.bom']._bom_find(product=self, company_id=stock_moves.company_id.id, bom_type='phantom')
+ if not bom:
+ return super()._compute_average_price(qty_invoiced, qty_to_invoice, stock_moves)
+ dummy, bom_lines = bom.explode(self, 1)
+ bom_lines = {line: data for line, data in bom_lines}
+ value = 0
+ for move in stock_moves:
+ bom_line = move.bom_line_id
+ if bom_line:
+ bom_line_data = bom_lines[bom_line]
+ line_qty = bom_line_data['qty']
+ else:
+ # bom was altered (i.e. bom line removed) after being used
+ line_qty = move.product_qty
+ value += line_qty * move.product_id._compute_average_price(qty_invoiced * line_qty, qty_to_invoice * line_qty, move)
+ return value
+
+ def _compute_bom_price(self, bom, boms_to_recompute=False):
+ self.ensure_one()
+ if not bom:
+ return 0
+ if not boms_to_recompute:
+ boms_to_recompute = []
+ total = 0
+ for opt in bom.operation_ids:
+ duration_expected = (
+ opt.workcenter_id.time_start +
+ opt.workcenter_id.time_stop +
+ opt.time_cycle)
+ total += (duration_expected / 60) * opt.workcenter_id.costs_hour
+ for line in bom.bom_line_ids:
+ if line._skip_bom_line(self):
+ continue
+
+ # Compute recursive if line has `child_line_ids`
+ if line.child_bom_id and line.child_bom_id in boms_to_recompute:
+ child_total = line.product_id._compute_bom_price(line.child_bom_id, boms_to_recompute=boms_to_recompute)
+ total += line.product_id.uom_id._compute_price(child_total, line.product_uom_id) * line.product_qty
+ else:
+ total += line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * line.product_qty
+ return bom.product_uom_id._compute_price(total / bom.product_qty, self.uom_id)
diff --git a/addons/mrp_account/models/stock_move.py b/addons/mrp_account/models/stock_move.py
new file mode 100644
index 00000000..86c19e23
--- /dev/null
+++ b/addons/mrp_account/models/stock_move.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo import api, fields, models, _
+
+
+class StockMove(models.Model):
+ _inherit = "stock.move"
+
+ def _is_returned(self, valued_type):
+ if self.unbuild_id:
+ return True
+ return super()._is_returned(valued_type)
+
+ def _get_src_account(self, accounts_data):
+ if not self.unbuild_id:
+ return super()._get_src_account(accounts_data)
+ else:
+ return self.location_dest_id.valuation_out_account_id.id or accounts_data['stock_input'].id
+
+ def _get_dest_account(self, accounts_data):
+ if not self.unbuild_id:
+ return super()._get_dest_account(accounts_data)
+ else:
+ return self.location_id.valuation_in_account_id.id or accounts_data['stock_output'].id
+
+ def _filter_anglo_saxon_moves(self, product):
+ res = super(StockMove, self)._filter_anglo_saxon_moves(product)
+ res += self.filtered(lambda m: m.bom_line_id.bom_id.product_tmpl_id.id == product.product_tmpl_id.id)
+ return res