mirror of
https://github.com/bringout/oca-ocb-accounting.git
synced 2026-04-23 17:42:02 +02:00
19.0 vanilla
This commit is contained in:
parent
ba20ce7443
commit
768b70e05e
2357 changed files with 1057103 additions and 712486 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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'},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.""")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue