19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:47 +01:00
parent accf5918df
commit 6e65e8c877
688 changed files with 225434 additions and 199401 deletions

View file

@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import stock_picking
from . import stock_move
from . import account_move_line
from . import product_product
from . import purchase_order
from . import stock_move
from . import stock_picking
from . import stock_rule

View file

@ -0,0 +1,30 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
def _get_price_unit_val_dif_and_relevant_qty(self):
price_unit_val_dif, relevant_qty = super()._get_price_unit_val_dif_and_relevant_qty()
if self.product_id.cost_method == 'standard' and self.purchase_line_id:
components_cost = 0
subcontract_production = self.purchase_line_id.move_ids._get_subcontract_production()
valuation_date = subcontract_production.move_raw_ids and max(subcontract_production.move_raw_ids.mapped('date')) or self.date
components_cost = self.company_currency_id._convert(
sum(subcontract_production.move_raw_ids.mapped('value')),
self.currency_id, self.company_id, valuation_date, round=False
)
qty = sum(mo.product_uom_id._compute_quantity(mo.qty_producing, self.product_uom_id) for mo in subcontract_production if mo.state == 'done')
if not self.product_uom_id.is_zero(qty):
price_unit_val_dif = price_unit_val_dif + components_cost / qty
return price_unit_val_dif, relevant_qty
def _get_stock_moves(self):
moves = super()._get_stock_moves()
finished_moves = set()
for m in moves:
if mo := m._get_subcontract_production():
finished_moves |= set(mo.move_finished_ids.filtered(lambda mf: mf.product_id == m.product_id).ids)
return moves | self.env['stock.move'].browse(finished_moves)

View file

@ -0,0 +1,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
from odoo.fields import Domain
class ProductProduct(models.Model):
_inherit = 'product.product'
@api.model
def _get_monthly_demand_moves_location_domain(self):
subcontracting_location_ids = self.env.companies.subcontracting_location_id.child_internal_location_ids.ids
domain = Domain.AND([
Domain.OR([
super()._get_monthly_demand_moves_location_domain(),
[('location_dest_id', 'in', subcontracting_location_ids)],
]),
[('location_id', 'not in', subcontracting_location_ids)],
])
return domain

View file

@ -10,3 +10,29 @@ class StockMove(models.Model):
def _is_purchase_return(self):
res = super()._is_purchase_return()
return res or self._is_subcontract_return()
def _get_value_from_account_move(self, quantity, at_date=None):
valuation_data = super()._get_value_from_account_move(quantity, at_date=at_date)
last_subcontract_done_receipt = self.move_dest_ids.filtered(
lambda m: m.state == 'done' and m.is_subcontract and m.purchase_line_id
).sorted('create_date', reverse=True)[:1]
if not self.production_id or not last_subcontract_done_receipt:
return valuation_data
bill_data = last_subcontract_done_receipt._get_value_from_account_move(quantity)
po_data = last_subcontract_done_receipt._get_value_from_quotation(quantity - bill_data['quantity'])
if not bill_data['value'] and not po_data['value']:
return valuation_data
old_extra = self.production_id.extra_cost
new_extra_cost = (bill_data['value'] + po_data['value']) / quantity
# Recompute finished_move price based on quotation and invoice
value = (self.price_unit - old_extra + new_extra_cost) * self.quantity
return {
'value': value,
'quantity': quantity,
'description': self.env._('%(value)s for %(quantity)s %(unit)s from %(production)s',
value=self.company_currency_id.format(self.value), quantity=quantity, unit=self.product_id.uom_id.name,
production=self.production_id.display_name),
}

View file

@ -31,10 +31,15 @@ class StockPicking(models.Model):
action.update({
'name': _("Source PO of %s", self.name),
'domain': [('id', 'in', purchase_order_ids)],
'view_mode': 'tree,form',
'view_mode': 'list,form',
})
return action
def _get_subcontracting_source_purchase(self):
moves_subcontracted = self.move_ids.move_dest_ids.raw_material_production_id.move_finished_ids.move_dest_ids.filtered(lambda m: m.is_subcontract)
return moves_subcontracted.purchase_line_id.order_id
def _get_subcontract_mo_confirmation_ctx(self):
res = super()._get_subcontract_mo_confirmation_ctx()
res['po_to_notify'] = self.move_ids.purchase_line_id.order_id
return res

View file

@ -0,0 +1,60 @@
from odoo import models, _
class StockRule(models.Model):
_inherit = 'stock.rule'
def _get_lead_days(self, product, **values):
"""For subcontracting, we need to consider both vendor lead time and
manufacturing lead time, and DTPMO (Days To Prepare MO).
Subcontracting delay =
max(Vendor lead time, Manufacturing lead time + DTPMO) + Days to Purchase
"""
bypass_delay_description = self.env.context.get('bypass_delay_description')
buy_rule = self.filtered(lambda r: r.action == 'buy')
seller = 'supplierinfo' in values and values['supplierinfo'] or product.with_company(buy_rule.company_id)._select_seller(quantity=None)
if not buy_rule or not seller:
return super()._get_lead_days(product, **values)
seller = seller[0]
bom = self.env['mrp.bom'].sudo()._bom_subcontract_find(
product,
company_id=buy_rule.picking_type_id.company_id.id,
bom_type='subcontract',
subcontractor=seller.partner_id)
if not bom:
return super()._get_lead_days(product, **values)
delays, delay_description = super(StockRule, self - buy_rule)._get_lead_days(product, **values)
extra_delays, extra_delay_description = super(StockRule, buy_rule.with_context(ignore_vendor_lead_time=True, global_horizon_days=0))._get_lead_days(product, **values)
if seller.delay >= bom.produce_delay + bom.days_to_prepare_mo:
delays['total_delay'] += seller.delay
delays['purchase_delay'] += seller.delay
if not bypass_delay_description:
delay_description.append((_('Receipt Date'), int(seller.delay)))
delay_description.append((_('Vendor Lead Time'), _('+ %d day(s)', seller.delay)))
else:
manufacture_delay = bom.produce_delay
delays['total_delay'] += manufacture_delay
# set manufacture_delay to purchase_delay so that PO can be created with correct date
delays['purchase_delay'] += manufacture_delay
if not bypass_delay_description:
delay_description.append((_('Receipt Date'), manufacture_delay))
delay_description.append((_('Manufacturing Lead Time'), _('+ %d day(s)', manufacture_delay)))
days_to_order = bom.days_to_prepare_mo
delays['total_delay'] += days_to_order
# add dtpmo to purchase_delay so that PO can be created with correct date
delays['purchase_delay'] += days_to_order
if not bypass_delay_description:
delay_description.append((_('Production Start Date'), days_to_order))
delay_description.append((_('Days to Supply Components'), _('+ %d day(s)', days_to_order)))
for key, value in extra_delays.items():
delays[key] += value
return delays, delay_description + extra_delay_description
def _notify_responsible(self, procurement):
super()._notify_responsible(procurement)
origin_order = self.env.context.get('po_to_notify')
if origin_order:
notified_users = procurement.product_id.responsible_id.partner_id | origin_order.user_id.partner_id
self._post_vendor_notification(origin_order, notified_users, procurement.product_id)