Files
odoo_source/addons/purchase_stock/models/stock.py
T
Martin TrigauxandXavier Morel 49838338e0 [FIX] *: sanitize action content reading
The open_action_with_context was not using _for_xml_id, producing an
error when reading the action content.

In open_action, the action_name was taken from the context and needs
sanity checks. Ensure only actions from the account module can be
read and only if the user has access to the target model.
This is a limitation of the previous behaviour but, at the moment, all
known calls are made refering to an action from the account module.
Limit the scope of this method while the 14.0 is still early to avoid
having a door open to ready any action, and difficult to close later.

Remove old action fetching from the context in create_move that is no
longer used.

closes odoo/odoo#61602

X-original-commit: a39e94f7e4dc74e850650ac90bbc5f85af130bc8
Related: odoo/enterprise#14695
Signed-off-by: Martin Trigaux (mat) <mat@odoo.com>
Co-authored-by: Xavier Morel <xmo@odoo.com>
2020-11-10 16:04:29 +00:00

285 lines
13 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
class StockPicking(models.Model):
_inherit = 'stock.picking'
purchase_id = fields.Many2one('purchase.order', related='move_lines.purchase_line_id.order_id',
string="Purchase Orders", readonly=True)
class StockMove(models.Model):
_inherit = 'stock.move'
purchase_line_id = fields.Many2one('purchase.order.line',
'Purchase Order Line', ondelete='set null', index=True, readonly=True)
created_purchase_line_id = fields.Many2one('purchase.order.line',
'Created Purchase Order Line', ondelete='set null', readonly=True, copy=False)
@api.model
def _prepare_merge_moves_distinct_fields(self):
distinct_fields = super(StockMove, self)._prepare_merge_moves_distinct_fields()
distinct_fields += ['purchase_line_id', 'created_purchase_line_id']
return distinct_fields
@api.model
def _prepare_merge_move_sort_method(self, move):
move.ensure_one()
keys_sorted = super(StockMove, self)._prepare_merge_move_sort_method(move)
keys_sorted += [move.purchase_line_id.id, move.created_purchase_line_id.id]
return keys_sorted
def _get_price_unit(self):
""" Returns the unit price for the move"""
self.ensure_one()
if self.purchase_line_id and self.product_id.id == self.purchase_line_id.product_id.id:
line = self.purchase_line_id
order = line.order_id
price_unit = line.price_unit
if line.taxes_id:
price_unit = line.taxes_id.with_context(round=False).compute_all(price_unit, currency=line.order_id.currency_id, quantity=1.0)['total_void']
if line.product_uom.id != line.product_id.uom_id.id:
price_unit *= line.product_uom.factor / line.product_id.uom_id.factor
if order.currency_id != order.company_id.currency_id:
# The date must be today, and not the date of the move since the move move is still
# in assigned state. However, the move date is the scheduled date until move is
# done, then date of actual move processing. See:
# https://github.com/odoo/odoo/blob/2f789b6863407e63f90b3a2d4cc3be09815f7002/addons/stock/models/stock_move.py#L36
price_unit = order.currency_id._convert(
price_unit, order.company_id.currency_id, order.company_id, fields.Date.context_today(self), round=False)
return price_unit
return super(StockMove, self)._get_price_unit()
def _generate_valuation_lines_data(self, partner_id, qty, debit_value, credit_value, debit_account_id, credit_account_id, description):
""" Overridden from stock_account to support amount_currency on valuation lines generated from po
"""
self.ensure_one()
rslt = super(StockMove, self)._generate_valuation_lines_data(partner_id, qty, debit_value, credit_value, debit_account_id, credit_account_id, description)
if self.purchase_line_id:
purchase_currency = self.purchase_line_id.currency_id
if purchase_currency != self.company_id.currency_id:
# Do not use price_unit since we want the price tax excluded. And by the way, qty
# is in the UOM of the product, not the UOM of the PO line.
purchase_price_unit = (
self.purchase_line_id.price_subtotal / self.purchase_line_id.product_uom_qty
if self.purchase_line_id.product_uom_qty
else self.purchase_line_id.price_unit
)
currency_move_valuation = purchase_currency.round(purchase_price_unit * abs(qty))
rslt['credit_line_vals']['amount_currency'] = rslt['credit_line_vals']['credit'] and -currency_move_valuation or currency_move_valuation
rslt['credit_line_vals']['currency_id'] = purchase_currency.id
rslt['debit_line_vals']['amount_currency'] = rslt['debit_line_vals']['credit'] and -currency_move_valuation or currency_move_valuation
rslt['debit_line_vals']['currency_id'] = purchase_currency.id
return rslt
def _prepare_extra_move_vals(self, qty):
vals = super(StockMove, self)._prepare_extra_move_vals(qty)
vals['purchase_line_id'] = self.purchase_line_id.id
return vals
def _prepare_move_split_vals(self, uom_qty):
vals = super(StockMove, self)._prepare_move_split_vals(uom_qty)
vals['purchase_line_id'] = self.purchase_line_id.id
return vals
def _clean_merged(self):
super(StockMove, self)._clean_merged()
self.write({'created_purchase_line_id': False})
def _get_upstream_documents_and_responsibles(self, visited):
if self.created_purchase_line_id and self.created_purchase_line_id.state not in ('done', 'cancel'):
return [(self.created_purchase_line_id.order_id, self.created_purchase_line_id.order_id.user_id, visited)]
elif self.purchase_line_id and self.purchase_line_id.state not in ('done', 'cancel'):
return[(self.purchase_line_id.order_id, self.purchase_line_id.order_id.user_id, visited)]
else:
return super(StockMove, self)._get_upstream_documents_and_responsibles(visited)
def _get_related_invoices(self):
""" Overridden to return the vendor bills related to this stock move.
"""
rslt = super(StockMove, self)._get_related_invoices()
rslt += self.mapped('picking_id.purchase_id.invoice_ids').filtered(lambda x: x.state == 'posted')
return rslt
def _get_source_document(self):
res = super()._get_source_document()
return self.purchase_line_id.order_id or res
class StockWarehouse(models.Model):
_inherit = 'stock.warehouse'
buy_to_resupply = fields.Boolean('Buy to Resupply', default=True,
help="When products are bought, they can be delivered to this warehouse")
buy_pull_id = fields.Many2one('stock.rule', 'Buy rule')
def _get_global_route_rules_values(self):
rules = super(StockWarehouse, self)._get_global_route_rules_values()
location_id = self.in_type_id.default_location_dest_id
rules.update({
'buy_pull_id': {
'depends': ['reception_steps', 'buy_to_resupply'],
'create_values': {
'action': 'buy',
'picking_type_id': self.in_type_id.id,
'group_propagation_option': 'none',
'company_id': self.company_id.id,
'route_id': self._find_global_route('purchase_stock.route_warehouse0_buy', _('Buy')).id,
'propagate_cancel': self.reception_steps != 'one_step',
},
'update_values': {
'active': self.buy_to_resupply,
'name': self._format_rulename(location_id, False, 'Buy'),
'location_id': location_id.id,
'propagate_cancel': self.reception_steps != 'one_step',
}
}
})
return rules
def _get_all_routes(self):
routes = super(StockWarehouse, self)._get_all_routes()
routes |= self.filtered(lambda self: self.buy_to_resupply and self.buy_pull_id and self.buy_pull_id.route_id).mapped('buy_pull_id').mapped('route_id')
return routes
def get_rules_dict(self):
result = super(StockWarehouse, self).get_rules_dict()
for warehouse in self:
result[warehouse.id].update(warehouse._get_receive_rules_dict())
return result
def _get_routes_values(self):
routes = super(StockWarehouse, self)._get_routes_values()
routes.update(self._get_receive_routes_values('buy_to_resupply'))
return routes
def _update_name_and_code(self, name=False, code=False):
res = super(StockWarehouse, self)._update_name_and_code(name, code)
warehouse = self[0]
#change the buy stock rule name
if warehouse.buy_pull_id and name:
warehouse.buy_pull_id.write({'name': warehouse.buy_pull_id.name.replace(warehouse.name, name, 1)})
return res
class ReturnPicking(models.TransientModel):
_inherit = "stock.return.picking"
def _prepare_move_default_values(self, return_line, new_picking):
vals = super(ReturnPicking, self)._prepare_move_default_values(return_line, new_picking)
vals['purchase_line_id'] = return_line.move_id.purchase_line_id.id
return vals
class Orderpoint(models.Model):
_inherit = "stock.warehouse.orderpoint"
show_supplier = fields.Boolean('Show supplier column', compute='_compute_show_suppplier')
supplier_id = fields.Many2one(
'product.supplierinfo', string='Product Supplier', check_company=True,
domain="['|', ('product_id', '=', product_id), '&', ('product_id', '=', False), ('product_tmpl_id', '=', product_tmpl_id)]")
vendor_id = fields.Many2one(related='supplier_id.name', string="Vendor", store=True)
@api.depends('product_id.purchase_order_line_ids', 'product_id.purchase_order_line_ids.state')
def _compute_qty(self):
""" Extend to add more depends values """
return super()._compute_qty()
@api.depends('route_id')
def _compute_show_suppplier(self):
buy_route = []
for res in self.env['stock.rule'].search_read([('action', '=', 'buy')], ['route_id']):
buy_route.append(res['route_id'][0])
for orderpoint in self:
orderpoint.show_supplier = orderpoint.route_id.id in buy_route
def action_view_purchase(self):
""" This function returns an action that display existing
purchase orders of given orderpoint.
"""
result = self.env['ir.actions.act_window']._for_xml_id('purchase.purchase_rfq')
# Remvove the context since the action basically display RFQ and not PO.
result['context'] = {}
order_line_ids = self.env['purchase.order.line'].search([('orderpoint_id', '=', self.id)])
purchase_ids = order_line_ids.mapped('order_id')
result['domain'] = "[('id','in',%s)]" % (purchase_ids.ids)
return result
def _get_replenishment_order_notification(self):
self.ensure_one()
order = self.env['purchase.order.line'].search([
('orderpoint_id', 'in', self.ids)
], limit=1).order_id
if order:
action = self.env.ref('purchase.action_rfq_form')
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('The following replenishment order has been generated'),
'message': '%s',
'links': [{
'label': order.display_name,
'url': f'#action={action.id}&id={order.id}&model=purchase.order',
}],
'sticky': False,
}
}
return super()._get_replenishment_order_notification()
def _prepare_procurement_values(self, date=False, group=False):
values = super()._prepare_procurement_values(date=date, group=group)
values['supplierinfo_id'] = self.supplier_id
return values
def _quantity_in_progress(self):
res = super()._quantity_in_progress()
qty_by_product_location, dummy = self.product_id._get_quantity_in_progress(self.location_id.ids)
for orderpoint in self:
product_qty = qty_by_product_location.get((orderpoint.product_id.id, orderpoint.location_id.id), 0.0)
product_uom_qty = orderpoint.product_id.uom_id._compute_quantity(product_qty, orderpoint.product_uom, round=False)
res[orderpoint.id] += product_uom_qty
return res
def _set_default_route_id(self):
route_id = self.env['stock.rule'].search([
('action', '=', 'buy')
]).route_id
orderpoint_wh_supplier = self.filtered(lambda o: o.product_id.seller_ids)
if route_id and orderpoint_wh_supplier:
orderpoint_wh_supplier.route_id = route_id[0].id
return super()._set_default_route_id()
class ProductionLot(models.Model):
_inherit = 'stock.production.lot'
purchase_order_ids = fields.Many2many('purchase.order', string="Purchase Orders", compute='_compute_purchase_order_ids', readonly=True, store=False)
purchase_order_count = fields.Integer('Purchase order count', compute='_compute_purchase_order_ids')
@api.depends('name')
def _compute_purchase_order_ids(self):
for lot in self:
stock_moves = self.env['stock.move.line'].search([
('lot_id', '=', lot.id),
('state', '=', 'done')
]).mapped('move_id')
stock_moves = stock_moves.search([('id', 'in', stock_moves.ids)]).filtered(
lambda move: move.picking_id.location_id.usage == 'supplier' and move.state == 'done')
lot.purchase_order_ids = stock_moves.mapped('purchase_line_id.order_id')
lot.purchase_order_count = len(lot.purchase_order_ids)
def action_view_po(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_form_action")
action['domain'] = [('id', 'in', self.mapped('purchase_order_ids.id'))]
action['context'] = dict(self._context, create=False)
return action