mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-28 04:52:06 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import api, fields, models, Command
|
||||
from odoo.tools.sql import column_exists, create_column
|
||||
|
||||
|
||||
|
|
@ -16,9 +16,76 @@ class StockMove(models.Model):
|
|||
_inherit = "stock.move"
|
||||
sale_line_id = fields.Many2one('sale.order.line', 'Sale Line', index='btree_not_null')
|
||||
|
||||
@api.depends('sale_line_id', 'sale_line_id.product_uom_id')
|
||||
def _compute_packaging_uom_id(self):
|
||||
super()._compute_packaging_uom_id()
|
||||
for move in self:
|
||||
if move.sale_line_id:
|
||||
move.packaging_uom_id = move.sale_line_id.product_uom_id
|
||||
|
||||
@api.depends('sale_line_id')
|
||||
def _compute_description_picking(self):
|
||||
super()._compute_description_picking()
|
||||
for move in self:
|
||||
if move.sale_line_id and not move.description_picking_manual:
|
||||
sale_line_id = move.sale_line_id.with_context(lang=move.sale_line_id.order_id.partner_id.lang)
|
||||
if move.description_picking == move.product_id.display_name:
|
||||
move.description_picking = ''
|
||||
move.description_picking = (sale_line_id._get_sale_order_line_multiline_description_variants() + '\n' + move.description_picking).strip()
|
||||
|
||||
def _action_synch_order(self):
|
||||
sale_order_lines_vals = []
|
||||
for move in self:
|
||||
sale_order = move.picking_id.sale_id
|
||||
# Creates new SO line only when pickings linked to a sale order and
|
||||
# for moves with qty. done and not already linked to a SO line.
|
||||
if not sale_order or move.sale_line_id or not move.picked or not (
|
||||
(move.location_dest_id.usage in ['customer', 'transit'] and not move.move_dest_ids)
|
||||
or (move.location_id.usage == 'customer' and move.to_refund)
|
||||
):
|
||||
continue
|
||||
|
||||
product = move.product_id
|
||||
|
||||
if line := sale_order.order_line.filtered(lambda l: l.product_id == product):
|
||||
move.sale_line_id = line[:1]
|
||||
continue
|
||||
|
||||
quantity = move.quantity
|
||||
if move.location_id.usage in ['customer', 'transit']:
|
||||
quantity *= -1
|
||||
|
||||
so_line_vals = {
|
||||
'move_ids': [(4, move.id, 0)],
|
||||
'name': product.display_name,
|
||||
'order_id': sale_order.id,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 0,
|
||||
'qty_delivered': quantity,
|
||||
'product_uom_id': move.product_uom.id,
|
||||
}
|
||||
so_line = sale_order.order_line.filtered(lambda sol: sol.product_id == product)
|
||||
if product.invoice_policy == 'delivery':
|
||||
# Check if there is already a SO line for this product to get
|
||||
# back its unit price (in case it was manually updated).
|
||||
so_line = sale_order.order_line.filtered(lambda sol: sol.product_id == product)
|
||||
if so_line:
|
||||
so_line_vals['price_unit'] = so_line[0].price_unit
|
||||
elif product.invoice_policy == 'order':
|
||||
# No unit price if the product is invoiced on the ordered qty.
|
||||
so_line_vals['price_unit'] = 0
|
||||
# New lines should be added at the bottom of the SO (higher sequence number)
|
||||
if not so_line:
|
||||
so_line_vals['sequence'] = max(sale_order.order_line.mapped('sequence')) + len(sale_order_lines_vals) + 1
|
||||
sale_order_lines_vals.append(so_line_vals)
|
||||
|
||||
if sale_order_lines_vals:
|
||||
self.env['sale.order.line'].with_context(skip_procurement=True).create(sale_order_lines_vals)
|
||||
return super()._action_synch_order()
|
||||
|
||||
@api.model
|
||||
def _prepare_merge_moves_distinct_fields(self):
|
||||
distinct_fields = super(StockMove, self)._prepare_merge_moves_distinct_fields()
|
||||
distinct_fields = super()._prepare_merge_moves_distinct_fields()
|
||||
distinct_fields.append('sale_line_id')
|
||||
return distinct_fields
|
||||
|
||||
|
|
@ -34,7 +101,12 @@ class StockMove(models.Model):
|
|||
|
||||
def _get_source_document(self):
|
||||
res = super()._get_source_document()
|
||||
return self.sudo().sale_line_id.order_id or res
|
||||
return self.sale_line_id.order_id or res
|
||||
|
||||
def _get_sale_order_lines(self):
|
||||
""" Return all possible sale order lines for one stock move. """
|
||||
self.ensure_one()
|
||||
return (self + self.browse(self._rollup_move_origs() | self._rollup_move_dests())).sale_line_id
|
||||
|
||||
def _assign_picking_post_process(self, new=False):
|
||||
super(StockMove, self)._assign_picking_post_process(new=new)
|
||||
|
|
@ -42,18 +114,57 @@ class StockMove(models.Model):
|
|||
picking_id = self.mapped('picking_id')
|
||||
sale_order_ids = self.mapped('sale_line_id.order_id')
|
||||
for sale_order_id in sale_order_ids:
|
||||
picking_id.message_post_with_view(
|
||||
picking_id.message_post_with_source(
|
||||
'mail.message_origin_link',
|
||||
values={'self': picking_id, 'origin': sale_order_id},
|
||||
subtype_id=self.env.ref('mail.mt_note').id)
|
||||
render_values={'self': picking_id, 'origin': sale_order_id},
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
|
||||
def _get_all_related_sm(self, product):
|
||||
return super()._get_all_related_sm(product) | self.filtered(lambda m: m.sale_line_id.product_id == product)
|
||||
|
||||
class ProcurementGroup(models.Model):
|
||||
_inherit = 'procurement.group'
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
if 'product_id' in vals:
|
||||
for move in self:
|
||||
if move.sale_line_id and move.product_id != move.sale_line_id.product_id:
|
||||
move.sale_line_id = False
|
||||
return res
|
||||
|
||||
sale_id = fields.Many2one('sale.order', 'Sale Order')
|
||||
def _prepare_procurement_values(self):
|
||||
res = super()._prepare_procurement_values()
|
||||
# to pass sale_line_id fom SO to MO in mto
|
||||
if self.sale_line_id:
|
||||
res['sale_line_id'] = self.sale_line_id.id
|
||||
return res
|
||||
|
||||
def _reassign_sale_lines(self, sale_order):
|
||||
current_order = self.sale_line_id.order_id
|
||||
if len(current_order) <= 1 and current_order != sale_order:
|
||||
ids_to_reset = set()
|
||||
if not sale_order:
|
||||
ids_to_reset.update(self.ids)
|
||||
else:
|
||||
line_ids_by_product = dict(self.env['sale.order.line']._read_group(
|
||||
domain=[('order_id', '=', sale_order.id), ('product_id', 'in', self.product_id.ids)],
|
||||
aggregates=['id:array_agg'],
|
||||
groupby=['product_id']
|
||||
))
|
||||
for move in self:
|
||||
if line_id := line_ids_by_product.get(move.product_id, [])[:1]:
|
||||
move.sale_line_id = line_id[0]
|
||||
else:
|
||||
ids_to_reset.add(move.id)
|
||||
|
||||
if ids_to_reset:
|
||||
self.env['stock.move'].browse(ids_to_reset).sale_line_id = False
|
||||
|
||||
|
||||
class StockMoveLine(models.Model):
|
||||
_inherit = "stock.move.line"
|
||||
|
||||
def _should_show_lot_in_invoice(self):
|
||||
return 'customer' in {self.location_id.usage, self.location_dest_id.usage} or self.env.ref('stock.stock_location_inter_company') in (self.location_id, self.location_dest_id)
|
||||
|
||||
|
||||
class StockRule(models.Model):
|
||||
|
|
@ -68,7 +179,43 @@ class StockRule(models.Model):
|
|||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
sale_id = fields.Many2one(related="group_id.sale_id", string="Sales Order", store=True, readonly=False, index='btree_not_null')
|
||||
sale_id = fields.Many2one('sale.order', compute="_compute_sale_id", inverse="_set_sale_id", string="Sales Order", store=True, index='btree_not_null')
|
||||
|
||||
@api.depends('reference_ids.sale_ids', 'move_ids.sale_line_id.order_id')
|
||||
def _compute_sale_id(self):
|
||||
for picking in self:
|
||||
# picking and move should have a link to the SO to see the picking on the stat button.
|
||||
# This will filter the move chain to the delivery moves only.
|
||||
sales_order = picking.move_ids.sale_line_id.order_id
|
||||
picking.sale_id = sales_order[0] if sales_order else False
|
||||
|
||||
@api.depends('move_ids.sale_line_id')
|
||||
def _compute_move_type(self):
|
||||
super()._compute_move_type()
|
||||
for picking in self:
|
||||
sale_orders = picking.move_ids.sale_line_id.order_id
|
||||
if sale_orders:
|
||||
if any(so.picking_policy == "direct" for so in sale_orders):
|
||||
picking.move_type = "direct"
|
||||
else:
|
||||
picking.move_type = "one"
|
||||
|
||||
def _set_sale_id(self):
|
||||
if self.reference_ids:
|
||||
if self.sale_id:
|
||||
self.reference_ids.sale_ids = [Command.link(self.sale_id.id)]
|
||||
else:
|
||||
sale_order = self.move_ids.sale_line_id.order_id
|
||||
if len(sale_order) == 1:
|
||||
self.reference_ids.sale_ids = [Command.unlink(sale_order.id)]
|
||||
else:
|
||||
if self.sale_id:
|
||||
reference = self.env['stock.reference'].create({
|
||||
'sale_ids': [Command.link(self.sale_id.id)],
|
||||
'name': self.sale_id.name,
|
||||
})
|
||||
self._add_reference(reference)
|
||||
self.move_ids._reassign_sale_lines(self.sale_id)
|
||||
|
||||
def _auto_init(self):
|
||||
"""
|
||||
|
|
@ -86,30 +233,41 @@ class StockPicking(models.Model):
|
|||
res = super()._action_done()
|
||||
sale_order_lines_vals = []
|
||||
for move in self.move_ids:
|
||||
sale_order = move.picking_id.sale_id
|
||||
ref_sale = move.picking_id.reference_ids.sale_ids
|
||||
sale_order = ref_sale and ref_sale[0] or move.sale_line_id.order_id
|
||||
# Creates new SO line only when pickings linked to a sale order and
|
||||
# for moves with qty. done and not already linked to a SO line.
|
||||
if not sale_order or move.location_dest_id.usage != 'customer' or move.sale_line_id or not move.quantity_done:
|
||||
if not sale_order or move.sale_line_id or not move.picked or not (
|
||||
(move.location_dest_id.usage in ['customer', 'transit'] and not move.move_dest_ids)
|
||||
or (move.location_id.usage == 'customer' and move.to_refund)
|
||||
):
|
||||
continue
|
||||
product = move.product_id
|
||||
quantity = move.quantity
|
||||
if move.location_id.usage in ['customer', 'transit']:
|
||||
quantity *= -1
|
||||
|
||||
so_line_vals = {
|
||||
'move_ids': [(4, move.id, 0)],
|
||||
'name': product.display_name,
|
||||
'order_id': sale_order.id,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 0,
|
||||
'qty_delivered': move.quantity_done,
|
||||
'product_uom': move.product_uom.id,
|
||||
'qty_delivered': quantity,
|
||||
'product_uom_id': move.product_uom.id,
|
||||
}
|
||||
so_line = sale_order.order_line.filtered(lambda sol: sol.product_id == product)
|
||||
if product.invoice_policy == 'delivery':
|
||||
# Check if there is already a SO line for this product to get
|
||||
# back its unit price (in case it was manually updated).
|
||||
so_line = sale_order.order_line.filtered(lambda sol: sol.product_id == product)
|
||||
if so_line:
|
||||
so_line_vals['price_unit'] = so_line[0].price_unit
|
||||
elif product.invoice_policy == 'order':
|
||||
# No unit price if the product is invoiced on the ordered qty.
|
||||
so_line_vals['price_unit'] = 0
|
||||
# New lines should be added at the bottom of the SO (higher sequence number)
|
||||
if not so_line:
|
||||
so_line_vals['sequence'] = max(sale_order.order_line.mapped('sequence')) + len(sale_order_lines_vals) + 1
|
||||
sale_order_lines_vals.append(so_line_vals)
|
||||
|
||||
if sale_order_lines_vals:
|
||||
|
|
@ -154,6 +312,11 @@ class StockPicking(models.Model):
|
|||
|
||||
return super(StockPicking, self)._log_less_quantities_than_expected(moves)
|
||||
|
||||
def _can_return(self):
|
||||
self.ensure_one()
|
||||
return super()._can_return() or self.sale_id
|
||||
|
||||
|
||||
class StockLot(models.Model):
|
||||
_inherit = 'stock.lot'
|
||||
|
||||
|
|
@ -162,18 +325,25 @@ class StockLot(models.Model):
|
|||
|
||||
@api.depends('name')
|
||||
def _compute_sale_order_ids(self):
|
||||
sale_orders = defaultdict(lambda: self.env['sale.order'])
|
||||
for move_line in self.env['stock.move.line'].search([('lot_id', 'in', self.ids), ('state', '=', 'done')]):
|
||||
move = move_line.move_id
|
||||
if move.picking_id.location_dest_id.usage == 'customer' and move.sale_line_id.order_id:
|
||||
sale_orders[move_line.lot_id.id] |= move.sale_line_id.order_id
|
||||
sale_orders = defaultdict(set)
|
||||
move_lines = self.env['stock.move.line'].search([
|
||||
('lot_id', 'in', self.ids),
|
||||
('state', '=', 'done'),
|
||||
('move_id.sale_line_id.order_id', '!=', False),
|
||||
('move_id.picking_id.location_dest_id.usage', 'in', ('customer', 'transit')),
|
||||
])
|
||||
for ml in move_lines:
|
||||
so = ml.move_id.sale_line_id.order_id
|
||||
if so.with_user(self.env.user).has_access('read'):
|
||||
sale_orders[ml.lot_id.id].add(so.id)
|
||||
for lot in self:
|
||||
lot.sale_order_ids = sale_orders[lot.id]
|
||||
lot.sale_order_count = len(lot.sale_order_ids)
|
||||
so_ids = sale_orders.get(lot.id, set())
|
||||
lot.sale_order_ids = [Command.set(list(so_ids))]
|
||||
lot.sale_order_count = len(so_ids)
|
||||
|
||||
def action_view_so(self):
|
||||
self.ensure_one()
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("sale.action_orders")
|
||||
action['domain'] = [('id', 'in', self.mapped('sale_order_ids.id'))]
|
||||
action['context'] = dict(self._context, create=False)
|
||||
action['context'] = dict(self.env.context, create=False)
|
||||
return action
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue