19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:07 +01:00
parent ba20ce7443
commit 768b70e05e
2357 changed files with 1057103 additions and 712486 deletions

View file

@ -1,12 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import analytic_account
from . import mrp_bom
from . import mrp_workcenter
from . import mrp_workorder
from . import mrp_production
from . import mrp_routing
from . import product
from . import stock_move
from . import stock_rule
from . import account_move

View file

@ -1,11 +1,50 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo import api, fields, models, _
from collections import defaultdict
class AccountMove(models.Model):
_inherit = 'account.move'
wip_production_ids = fields.Many2many(
'mrp.production', 'wip_move_production_rel', 'move_id', 'production_id', string="Relevant WIP MOs",
copy=False,
help="The MOs that this WIP entry was based on. Expected to be set at time of WIP entry creation.")
wip_production_count = fields.Integer("Manufacturing Orders Count", compute='_compute_wip_production_count')
def copy(self, default=None):
records = super().copy(default)
for record, source in zip(records.sudo(), self.sudo()):
record.wip_production_ids = source.wip_production_ids
return records
@api.depends('wip_production_ids')
def _compute_wip_production_count(self):
for account in self:
account.wip_production_count = len(account.wip_production_ids)
def action_view_wip_production(self):
self.ensure_one()
action = {
'res_model': 'mrp.production',
'type': 'ir.actions.act_window',
}
if len(self.wip_production_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': self.wip_production_ids.id,
})
else:
action.update({
'name': _("WIP MOs of %s", self.name),
'domain': [('id', 'in', self.wip_production_ids.ids)],
'view_mode': 'list,form',
})
return action
class AccountMoveLine(models.Model):
_inherit = "account.move.line"

View file

@ -8,11 +8,11 @@ class AccountAnalyticAccount(models.Model):
_inherit = 'account.analytic.account'
_description = 'Analytic Account'
production_ids = fields.One2many('mrp.production', 'analytic_account_id', string='Manufacturing Orders')
production_ids = fields.Many2many('mrp.production')
production_count = fields.Integer("Manufacturing Orders Count", compute='_compute_production_count')
bom_ids = fields.One2many('mrp.bom', 'analytic_account_id', string='Bills of Materials')
bom_ids = fields.Many2many('mrp.bom')
bom_count = fields.Integer("BoM Count", compute='_compute_bom_count')
workcenter_ids = fields.One2many('mrp.workcenter', 'costs_hour_account_id', string='Workcenters')
workcenter_ids = fields.Many2many('mrp.workcenter')
workorder_count = fields.Integer("Work Order Count", compute='_compute_workorder_count')
@api.depends('production_ids')
@ -37,7 +37,7 @@ class AccountAnalyticAccount(models.Model):
"res_model": "mrp.production",
"domain": [['id', 'in', self.production_ids.ids]],
"name": _("Manufacturing Orders"),
'view_mode': 'tree,form',
'view_mode': 'list,form',
"context": {'default_analytic_account_id': self.id},
}
if len(self.production_ids) == 1:
@ -52,7 +52,7 @@ class AccountAnalyticAccount(models.Model):
"res_model": "mrp.bom",
"domain": [['id', 'in', self.bom_ids.ids]],
"name": _("Bills of Materials"),
'view_mode': 'tree,form',
'view_mode': 'list,form',
"context": {'default_analytic_account_id': self.id},
}
if self.bom_count == 1:
@ -68,7 +68,7 @@ class AccountAnalyticAccount(models.Model):
"domain": [['id', 'in', (self.workcenter_ids.order_ids | self.production_ids.workorder_ids).ids]],
"context": {"create": False},
"name": _("Work Orders"),
'view_mode': 'tree',
'view_mode': 'list',
}
return result
@ -77,3 +77,15 @@ class AccountAnalyticLine(models.Model):
_inherit = 'account.analytic.line'
category = fields.Selection(selection_add=[('manufacturing_order', 'Manufacturing Order')])
class AccountAnalyticApplicability(models.Model):
_inherit = 'account.analytic.applicability'
_description = "Analytic Plan's Applicabilities"
business_domain = fields.Selection(
selection_add=[
('manufacturing_order', 'Manufacturing Order'),
],
ondelete={'manufacturing_order': 'cascade'},
)

View file

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class MrpBom(models.Model):
_inherit = 'mrp.bom'
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', company_dependent=True, copy=True,
help="Analytic account in which cost and revenue entries will take place for financial management of the manufacturing order.")

View file

@ -1,19 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from ast import literal_eval
from collections import defaultdict
from odoo import api, fields, models, _
from odoo.tools import float_is_zero, float_round
class MrpProductionWorkcenterLineTime(models.Model):
_inherit = 'mrp.workcenter.productivity'
# 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."
cost_already_recorded = fields.Boolean('Cost Recorded')
from odoo.tools import float_round
class MrpProduction(models.Model):
@ -21,96 +12,122 @@ class MrpProduction(models.Model):
extra_cost = fields.Float(copy=False, string='Extra Unit Cost')
show_valuation = fields.Boolean(compute='_compute_show_valuation')
analytic_account_id = fields.Many2one(
'account.analytic.account', 'Analytic Account', copy=True,
help="Analytic account in which cost and revenue entries will take\
place for financial management of the manufacturing order.",
compute='_compute_analytic_account_id', store=True, readonly=False)
wip_move_ids = fields.Many2many('account.move', 'wip_move_production_rel', 'production_id', 'move_id')
wip_move_count = fields.Integer("WIP Journal Entry Count", compute='_compute_wip_move_count')
def _compute_show_valuation(self):
for order in self:
order.show_valuation = any(m.state == 'done' for m in order.move_finished_ids)
@api.depends('bom_id')
def _compute_analytic_account_id(self):
for order in self:
if order.bom_id.analytic_account_id:
order.analytic_account_id = order.bom_id.analytic_account_id
@api.depends('wip_move_ids')
def _compute_wip_move_count(self):
for account in self:
account.wip_move_count = len(account.wip_move_ids)
def write(self, vals):
origin_analytic_account = {production: production.analytic_account_id for production in self}
res = super().write(vals)
for production in self:
if vals.get('name'):
production.move_raw_ids.analytic_account_line_id.ref = production.display_name
production.move_raw_ids.analytic_account_line_ids.ref = production.display_name
for workorder in production.workorder_ids:
workorder.mo_analytic_account_line_id.ref = production.display_name
workorder.mo_analytic_account_line_id.name = _("[WC] %s", workorder.display_name)
if 'analytic_account_id' in vals and production.state != 'draft':
if vals['analytic_account_id'] and origin_analytic_account[production]:
# Link the account analytic lines to the new AA
production.move_raw_ids.analytic_account_line_id.write({'account_id': vals['analytic_account_id']})
production.workorder_ids.mo_analytic_account_line_id.write({'account_id': vals['analytic_account_id']})
elif vals['analytic_account_id'] and not origin_analytic_account[production]:
# Create the account analytic lines if no AA is set in the MO
production.move_raw_ids._account_analytic_entry_move()
production.workorder_ids._create_or_update_analytic_entry()
else:
production.move_raw_ids.analytic_account_line_id.unlink()
production.workorder_ids.mo_analytic_account_line_id.unlink()
workorder.mo_analytic_account_line_ids.ref = production.display_name
workorder.mo_analytic_account_line_ids.name = _("[WC] %s", workorder.display_name)
return res
def action_view_stock_valuation_layers(self):
def action_view_move_wip(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)
def action_view_analytic_account(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"res_model": "account.analytic.account",
'res_id': self.analytic_account_id.id,
"context": {"create": False},
"name": _("Analytic Account"),
'view_mode': 'form',
action = {
'res_model': 'account.move',
'type': 'ir.actions.act_window',
}
if len(self.wip_move_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': self.wip_move_ids.id,
})
else:
action.update({
'name': _("WIP Entries of %s", self.name),
'domain': [('id', 'in', self.wip_move_ids.ids)],
'view_mode': 'list,form',
'views': [(self.env.ref('account.view_move_tree').id, 'list')],
})
return action
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)
super()._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)
lambda x: x.product_id == self.product_id and x.state not in ('done', 'cancel') and x.quantity > 0)
if finished_move:
if finished_move.product_id.cost_method not in ('fifo', 'average'):
finished_move.price_unit = finished_move.product_id.standard_price
return True
finished_move.ensure_one()
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 not in ('done', 'cancel') and m.quantity_done > 0)
work_center_cost += work_order._cal_cost()
quantity = finished_move.product_uom._compute_quantity(
finished_move.quantity, finished_move.product_id.uom_id)
extra_cost = self.extra_cost * quantity
total_cost = sum(move.value for move in consumed_moves) + work_center_cost + extra_cost
byproduct_moves = self.move_byproduct_ids.filtered(lambda m: m.state not in ('done', 'cancel') and m.quantity > 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)
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
byproduct.price_unit = total_cost * byproduct.cost_share / 100 / byproduct.product_uom._compute_quantity(byproduct.quantity, byproduct.product_id.uom_id)
finished_move.price_unit = total_cost * float_round(1 - byproduct_cost_share / 100, precision_rounding=0.0001) / quantity
return True
def _get_backorder_mo_vals(self):
res = super()._get_backorder_mo_vals()
res['extra_cost'] = self.extra_cost
return res
def _post_labour(self):
for mo in self:
production_location = self.product_id.with_company(self.company_id).property_stock_production
if mo.with_company(mo.company_id).product_id.valuation != 'real_time' or not production_location.valuation_account_id:
continue
product_accounts = mo.product_id.product_tmpl_id.get_product_accounts()
labour_amounts = defaultdict(float)
workorders = defaultdict(self.env['mrp.workorder'].browse)
for wo in mo.workorder_ids:
account = wo.workcenter_id.expense_account_id or product_accounts['expense']
labour_amounts[account] += wo.company_id.currency_id.round(wo._cal_cost())
workorders[account] |= wo
workcenter_cost = sum(labour_amounts.values())
if mo.company_id.currency_id.is_zero(workcenter_cost):
continue
desc = _('%s - Labour', mo.name)
account = production_location.valuation_account_id
labour_amounts[account] -= workcenter_cost
account_move = self.env['account.move'].sudo().create({
'journal_id': product_accounts['stock_journal'].id,
'date': fields.Date.context_today(self),
'ref': desc,
'move_type': 'entry',
'line_ids': [(0, 0, {
'name': desc,
'ref': desc,
'balance': -amt,
'account_id': acc.id,
}) for acc, amt in labour_amounts.items()]
})
account_move._post()
for line in account_move.line_ids[:-1]:
workorders[line.account_id].time_ids.write({'account_move_line_id': line.id})
def _post_inventory(self, cancel_backorder=False):
res = super()._post_inventory(cancel_backorder=cancel_backorder)
self.filtered(lambda mo: mo.state == 'done')._post_labour()
return res

View file

@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
class MrpRoutingWorkcenter(models.Model):
_inherit = 'mrp.routing.workcenter'
def _total_cost_per_hour(self):
self.ensure_one()
return self.workcenter_id.costs_hour

View file

@ -1,12 +1,26 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
from odoo import api, models, fields
class MrpWorkcenter(models.Model):
_inherit = 'mrp.workcenter'
_name = 'mrp.workcenter'
_inherit = ['mrp.workcenter', 'analytic.mixin']
costs_hour_account_id = fields.Many2one(
'account.analytic.account', string='Analytic Account',
help="Posts analytical accounting entries in real time for both component and operational costs.")
costs_hour_account_ids = fields.Many2many('account.analytic.account', compute="_compute_costs_hour_account_ids", store=True)
expense_account_id = fields.Many2one('account.account', string="Expense Account", check_company=True,
help="The expense is accounted for when the manufacturing order is marked as done. If not set, it is the expense account of the final product that will be used instead.")
@api.depends('analytic_distribution')
def _compute_costs_hour_account_ids(self):
for record in self:
record.costs_hour_account_ids = bool(record.analytic_distribution) and self.env['account.analytic.account'].browse(
list({int(account_id) for ids in record.analytic_distribution for account_id in ids.split(",")})
).exists()
class MrpWorkcenterProductivity(models.Model):
_inherit = 'mrp.workcenter.productivity'
account_move_line_id = fields.Many2one('account.move.line')

View file

@ -2,14 +2,13 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, _
from odoo.tools import float_is_zero
class MrpWorkorder(models.Model):
_inherit = 'mrp.workorder'
mo_analytic_account_line_id = fields.Many2one('account.analytic.line', copy=False)
wc_analytic_account_line_id = fields.Many2one('account.analytic.line', copy=False)
mo_analytic_account_line_ids = fields.Many2many('account.analytic.line', 'mrp_workorder_mo_analytic_rel', copy=False)
wc_analytic_account_line_ids = fields.Many2many('account.analytic.line', 'mrp_workorder_wc_analytic_rel', copy=False)
def _compute_duration(self):
res = super()._compute_duration()
@ -22,15 +21,15 @@ class MrpWorkorder(models.Model):
return res
def action_cancel(self):
(self.mo_analytic_account_line_id | self.wc_analytic_account_line_id).unlink()
(self.mo_analytic_account_line_ids | self.wc_analytic_account_line_ids).unlink()
return super().action_cancel()
def _prepare_analytic_line_values(self, account, unit_amount, amount):
def _prepare_analytic_line_values(self, account_field_values, amount, unit_amount):
self.ensure_one()
return {
'name': _("[WC] %s", self.display_name),
'amount': amount,
'account_id': account.id,
**account_field_values,
'unit_amount': unit_amount,
'product_id': self.product_id.id,
'product_uom_id': self.env.ref('uom.product_uom_hour').id,
@ -40,44 +39,20 @@ class MrpWorkorder(models.Model):
}
def _create_or_update_analytic_entry(self):
wo_to_link_mo_analytic_line = self.env['mrp.workorder']
wo_to_link_wc_analytic_line = self.env['mrp.workorder']
mo_analytic_line_vals_list = []
wc_analytic_line_vals_list = []
for wo in self.filtered(lambda wo: wo.production_id.analytic_account_id or wo.workcenter_id.costs_hour_account_id):
for wo in self:
if not wo.id:
continue
hours = wo.duration / 60.0
value = -hours * wo.workcenter_id.costs_hour
mo_account = wo.production_id.analytic_account_id
wc_account = wo.workcenter_id.costs_hour_account_id
if mo_account:
mo_currency = mo_account.currency_id or wo.company_id.currency_id
is_zero = float_is_zero(value, precision_rounding=mo_currency.rounding)
if wo.mo_analytic_account_line_id:
wo.mo_analytic_account_line_id.write({
'unit_amount': hours,
'amount': value if not is_zero else 0,
})
elif not is_zero:
wo_to_link_mo_analytic_line += wo
mo_analytic_line_vals_list.append(wo._prepare_analytic_line_values(mo_account, hours, value))
if wc_account and wc_account != mo_account:
wc_currency = wc_account.currency_id or wo.company_id.currency_id
is_zero = float_is_zero(value, precision_rounding=wc_currency.rounding)
if wo.wc_analytic_account_line_id:
wo.wc_analytic_account_line_id.write({
'unit_amount': hours,
'amount': value if not is_zero else 0,
})
elif not is_zero:
wo_to_link_wc_analytic_line += wo
wc_analytic_line_vals_list.append(wo._prepare_analytic_line_values(wc_account, hours, value))
analytic_lines = self.env['account.analytic.line'].sudo().create(mo_analytic_line_vals_list + wc_analytic_line_vals_list)
mo_analytic_lines, wc_analytic_lines = analytic_lines[:len(wo_to_link_mo_analytic_line)], analytic_lines[len(wo_to_link_mo_analytic_line):]
for wo, analytic_line in zip(wo_to_link_mo_analytic_line, mo_analytic_lines):
wo.mo_analytic_account_line_id = analytic_line
for wo, analytic_line in zip(wo_to_link_wc_analytic_line, wc_analytic_lines):
wo.wc_analytic_account_line_id = analytic_line
wo._create_or_update_analytic_entry_for_record(value, hours)
def _create_or_update_analytic_entry_for_record(self, value, hours):
self.ensure_one()
if self.workcenter_id.analytic_distribution or self.wc_analytic_account_line_ids or self.mo_analytic_account_line_ids:
wc_analytic_line_vals = self.env['account.analytic.account']._perform_analytic_distribution(self.workcenter_id.analytic_distribution, value, hours, self.wc_analytic_account_line_ids, self)
if wc_analytic_line_vals:
self.wc_analytic_account_line_ids += self.env['account.analytic.line'].sudo().create(wc_analytic_line_vals)
def unlink(self):
(self.mo_analytic_account_line_id | self.wc_analytic_account_line_id).unlink()
(self.mo_analytic_account_line_ids | self.wc_analytic_account_line_ids).unlink()
return super().unlink()

View file

@ -1,14 +1,30 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo import fields, models
from odoo.tools import float_round, groupby
class ProductTemplate(models.Model):
_name = 'product.template'
_inherit = 'product.template'
def _get_product_accounts(self):
accounts = super()._get_product_accounts()
if self.categ_id:
# If category set on the product take production account from category even if
# production account on category is False
production_account = self.categ_id.property_stock_account_production_cost_id
else:
ProductCategory = self.env['product.category']
production_account = (
self.valuation == 'real_time'
and ProductCategory._fields['property_stock_account_production_cost_id'].get_company_dependent_fallback(
ProductCategory
)
or self.env['account.account']
)
accounts['production'] = production_account
return accounts
def action_bom_cost(self):
templates = self.filtered(lambda t: t.product_variant_count == 1 and t.bom_count > 0)
if templates:
@ -21,7 +37,6 @@ class ProductTemplate(models.Model):
class ProductProduct(models.Model):
_name = 'product.product'
_inherit = 'product.product'
def button_bom_cost(self):
@ -45,30 +60,6 @@ class ProductProduct(models.Model):
if price:
self.standard_price = price
def _compute_average_price(self, qty_invoiced, qty_to_invoice, stock_moves, is_returned=False):
self.ensure_one()
if stock_moves.product_id == self:
return super()._compute_average_price(qty_invoiced, qty_to_invoice, stock_moves, is_returned=is_returned)
bom = self.env['mrp.bom']._bom_find(self, company_id=stock_moves.company_id.id, bom_type='phantom')[self]
if not bom:
return super()._compute_average_price(qty_invoiced, qty_to_invoice, stock_moves, is_returned=is_returned)
value = 0
dummy, bom_lines = bom.explode(self, 1)
bom_lines = {line: data for line, data in bom_lines}
for bom_line, moves_list in groupby(stock_moves.filtered(lambda sm: sm.state != 'cancel'), lambda sm: sm.bom_line_id):
if bom_line not in bom_lines:
for move in moves_list:
component_quantity = next(
(bml.product_qty for bml in move.product_id.bom_line_ids if bml in bom_lines),
1
)
value += component_quantity * move.product_id._compute_average_price(qty_invoiced * move.product_qty, qty_to_invoice * move.product_qty, move, is_returned=is_returned)
continue
line_qty = bom_line.product_uom_id._compute_quantity(bom_lines[bom_line]['qty'], bom_line.product_id.uom_id)
moves = self.env['stock.move'].concat(*moves_list)
value += line_qty * bom_line.product_id._compute_average_price(qty_invoiced * line_qty, qty_to_invoice * line_qty, moves, is_returned=is_returned)
return value
def _compute_bom_price(self, bom, boms_to_recompute=False, byproduct_bom=False):
self.ensure_one()
if not bom:
@ -80,10 +71,7 @@ class ProductProduct(models.Model):
if opt._skip_operation_line(self):
continue
duration_expected = (
opt.workcenter_id._get_expected_duration(self) +
opt.time_cycle * 100 / opt.workcenter_id.time_efficiency)
total += (duration_expected / 60) * opt._total_cost_per_hour()
total += opt.cost
for line in bom.bom_line_ids:
if line._skip_bom_line(self):
@ -108,3 +96,14 @@ class ProductProduct(models.Model):
if byproduct_cost_share:
total *= float_round(1 - byproduct_cost_share / 100, precision_rounding=0.0001)
return bom.product_uom_id._compute_price(total / bom.product_qty, self.uom_id)
return 0.0
class ProductCategory(models.Model):
_inherit = 'product.category'
property_stock_account_production_cost_id = fields.Many2one(
'account.account', 'Production Account', company_dependent=True, ondelete='restrict',
check_company=True,
help="""This account will be used as a valuation counterpart for both components and final products for manufacturing orders.
If there are any workcenter/employee costs, this value will remain on the account once the production is completed.""")

View file

@ -1,51 +1,44 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, models
from collections import defaultdict
from odoo import models
class StockMove(models.Model):
_inherit = "stock.move"
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
def _generate_analytic_lines_data(self, unit_amount, amount):
vals = super()._generate_analytic_lines_data(unit_amount, amount)
if self.raw_material_production_id.analytic_account_id:
vals['name'] = _('[Raw] %s', self.product_id.display_name)
vals['ref'] = self.raw_material_production_id.display_name
vals['category'] = 'manufacturing_order'
return vals
def _get_analytic_account(self):
account = self.raw_material_production_id.analytic_account_id
if account:
return account
return super()._get_analytic_account()
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 _is_returned(self, valued_type):
if self.unbuild_id:
return True
return super()._is_returned(valued_type)
def _should_force_price_unit(self):
def _get_value_from_production(self, quantity, at_date=None):
# TODO: Maybe move _cal_price here
self.ensure_one()
return self.picking_type_id.code == 'mrp_operation' or super()._should_force_price_unit()
if not self.production_id:
return super()._get_value_from_production(quantity, at_date)
value = quantity * self.price_unit
return {
'value': value,
'quantity': quantity,
'description': self.env._('%(value)s for %(quantity)s %(unit)s from %(production)s',
value=self.company_currency_id.format(value), quantity=quantity, unit=self.product_id.uom_id.name,
production=self.production_id.display_name),
}
def _ignore_automatic_valuation(self):
return bool(self.raw_material_production_id)
def _get_all_related_sm(self, product):
moves = super()._get_all_related_sm(product)
return moves | self.filtered(
lambda m:
m.bom_line_id.bom_id.type == 'phantom' and
m.bom_line_id.bom_id == moves.bom_line_id.bom_id
)
def _get_kit_price_unit(self, product, kit_bom, valuated_quantity):
""" Override the value for kit products """
_dummy, exploded_lines = kit_bom.explode(product, valuated_quantity)
total_price_unit = 0
component_qty_per_kit = defaultdict(float)
for line in exploded_lines:
component_qty_per_kit[line[0].product_id] += line[1]['qty']
for component, valuated_moves in self.grouped('product_id').items():
price_unit = super(StockMove, valuated_moves)._get_price_unit()
qty_per_kit = component_qty_per_kit[component] / kit_bom.product_qty
total_price_unit += price_unit * qty_per_kit
return total_price_unit / valuated_quantity if not product.uom_id.is_zero(valuated_quantity) else 0

View file

@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
class StockRule(models.Model):
_inherit = 'stock.rule'
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values, bom):
res = super()._prepare_mo_vals(product_id, product_qty, product_uom, location_id, name, origin, company_id, values, bom)
if not bom.analytic_account_id and values.get('analytic_account_id'):
res['analytic_account_id'] = values.get('analytic_account_id').id
return res