如題:銷售訂單在報價單
狀態(tài)點擊確認(rèn)
按鈕,狀態(tài)變更為銷售訂單
,同時生成一個銷售出庫單
-----------------------------------------------分割線-----------------------------------------
代碼過程
1.點擊 確認(rèn)
按鈕
主要執(zhí)行兩個邏輯:
一是將單據(jù)狀態(tài)改變,由報價單改為銷售訂單
二是通過推拉規(guī)則生成調(diào)撥單
以下為sale
模塊的代碼:
@api.multi
def action_confirm(self):
if self._get_forbidden_state_confirm() & set(self.mapped('state')):
raise UserError(_(
'It is not allowed to confirm an order in the following states: %s'
) % (', '.join(self._get_forbidden_state_confirm())))
for order in self.filtered(lambda order: order.partner_id not in order.message_partner_ids):
order.message_subscribe([order.partner_id.id])
self.write({
'state': 'sale',
'confirmation_date': fields.Datetime.now()
})
self._action_confirm()
if self.env['ir.config_parameter'].sudo().get_param('sale.auto_done_setting'):
self.action_done()
return True
重點代碼是self._action_confirm()
,
@api.multi
def _action_confirm(self):
""" Implementation of additionnal mecanism of Sales Order confirmation.
This method should be extended when the confirmation should generated
other documents. In this method, the SO are in 'sale' state (not yet 'done').
"""
if self.env.context.get('send_email'):
self.force_quotation_send()
# create an analytic account if at least an expense product
for order in self:
if any([expense_policy not in [False, 'no'] for expense_policy in order.order_line.mapped('product_id.expense_policy')]):
if not order.analytic_account_id:
order._create_analytic_account()
return True
通過代碼注釋,我們可以確定這是確認(rèn)訂單的附加機制,生成其他單據(jù)時候應(yīng)拓展此方法。
2.生成調(diào)撥單
繼續(xù)跟蹤代碼
以下為sale_stock
模塊的代碼:
@api.multi
def _action_confirm(self):
for order in self:
order.order_line._action_launch_stock_rule()
super(SaleOrder, self)._action_confirm()
不難發(fā)現(xiàn),再次拓展了_action_confirm方法。
重點代碼:order.order_line._action_launch_stock_rule()
繼續(xù)跟蹤sale.order.line的_action_launch_stock_rule
的方法
@api.multi
def _action_launch_stock_rule(self):
"""
Launch procurement group run method with required/custom fields genrated by a
sale order line. procurement group will launch '_run_pull', '_run_buy' or '_run_manufacture'
depending on the sale order line product rule.
"""
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
errors = []
for line in self:
if line.state != 'sale' or not line.product_id.type in ('consu','product'):
continue
qty = line._get_qty_procurement()
if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0:
continue
group_id = line.order_id.procurement_group_id
if not group_id:
group_id = self.env['procurement.group'].create({
'name': line.order_id.name, 'move_type': line.order_id.picking_policy,
'sale_id': line.order_id.id,
'partner_id': line.order_id.partner_shipping_id.id,
})
line.order_id.procurement_group_id = group_id
else:
# In case the procurement group is already created and the order was
# cancelled, we need to update certain values of the group.
updated_vals = {}
if group_id.partner_id != line.order_id.partner_shipping_id:
updated_vals.update({'partner_id': line.order_id.partner_shipping_id.id})
if group_id.move_type != line.order_id.picking_policy:
updated_vals.update({'move_type': line.order_id.picking_policy})
if updated_vals:
group_id.write(updated_vals)
values = line._prepare_procurement_values(group_id=group_id)
product_qty = line.product_uom_qty - qty
procurement_uom = line.product_uom
quant_uom = line.product_id.uom_id
get_param = self.env['ir.config_parameter'].sudo().get_param
if procurement_uom.id != quant_uom.id and get_param('stock.propagate_uom') != '1':
product_qty = line.product_uom._compute_quantity(product_qty, quant_uom, rounding_method='HALF-UP')
procurement_uom = quant_uom
try:
self.env['procurement.group'].run(line.product_id, product_qty, procurement_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, values)
except UserError as error:
errors.append(error.name)
if errors:
raise UserError('\n'.join(errors))
return True
主要代碼self.env['procurement.group'].run(line.product_id, product_qty, procurement_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, values)
在此之前,主要是計算生成調(diào)撥單的數(shù)據(jù)。
繼續(xù)跟蹤run
方法。
以下為stock
模塊的代碼:
@api.model
def run(self, product_id, product_qty, product_uom, location_id, name, origin, values):
""" Method used in a procurement case. The purpose is to supply the
product passed as argument in the location also given as an argument.
In order to be able to find a suitable location that provide the product
it will search among stock.rule.
"""
values.setdefault('company_id', self.env['res.company']._company_default_get('procurement.group'))
values.setdefault('priority', '1')
values.setdefault('date_planned', fields.Datetime.now())
rule = self._get_rule(product_id, location_id, values)
if not rule:
raise UserError(_('No procurement rule found in location "%s" for product "%s".\n Check routes configuration.') % (location_id.display_name, product_id.display_name))
action = 'pull' if rule.action == 'pull_push' else rule.action
if hasattr(rule, '_run_%s' % action):
getattr(rule, '_run_%s' % action)(product_id, product_qty, product_uom, location_id, name, origin, values)
else:
_logger.error("The method _run_%s doesn't exist on the procument rules" % action)
return True
重點代碼:getattr(rule, '_run_%s' % action)(product_id, product_qty, product_uom, location_id, name, origin, values)
發(fā)現(xiàn)是根據(jù)推拉規(guī)則
,通過反射
的方式執(zhí)行的代碼。
執(zhí)行_run_pull
或者_run_push
....
def _run_pull(self, product_id, product_qty, product_uom, location_id, name, origin, values):
if not self.location_src_id:
msg = _('No source location defined on stock rule: %s!') % (self.name, )
raise UserError(msg)
# create the move as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
# Search if picking with move for it exists already:
group_id = False
if self.group_propagation_option == 'propagate':
group_id = values.get('group_id', False) and values['group_id'].id
elif self.group_propagation_option == 'fixed':
group_id = self.group_id.id
data = self._get_stock_move_values(product_id, product_qty, product_uom, location_id, name, origin, values, group_id)
# Since action_confirm launch following procurement_group we should activate it.
move = self.env['stock.move'].sudo().with_context(force_company=data.get('company_id', False)).create(data)
move._action_confirm()
return True
重點代碼move._action_confirm()
此時是先生成庫存移動move單,通過_action_confirm()生成picking單
def _action_confirm(self, merge=True, merge_into=False):
""" Confirms stock move or put it in waiting if it's linked to another move.
:param: merge: According to this boolean, a newly confirmed move will be merged
in another move of the same picking sharing its characteristics.
"""
move_create_proc = self.env['stock.move']
move_to_confirm = self.env['stock.move']
move_waiting = self.env['stock.move']
to_assign = {}
for move in self:
# if the move is preceeded, then it's waiting (if preceeding move is done, then action_assign has been called already and its state is already available)
if move.move_orig_ids:
move_waiting |= move
else:
if move.procure_method == 'make_to_order':
move_create_proc |= move
else:
move_to_confirm |= move
if move._should_be_assigned():
key = (move.group_id.id, move.location_id.id, move.location_dest_id.id)
if key not in to_assign:
to_assign[key] = self.env['stock.move']
to_assign[key] |= move
# create procurements for make to order moves
for move in move_create_proc:
values = move._prepare_procurement_values()
origin = (move.group_id and move.group_id.name or (move.origin or move.picking_id.name or "/"))
self.env['procurement.group'].run(move.product_id, move.product_uom_qty, move.product_uom, move.location_id, move.rule_id and move.rule_id.name or "/", origin,
values)
move_to_confirm.write({'state': 'confirmed'})
(move_waiting | move_create_proc).write({'state': 'waiting'})
# assign picking in batch for all confirmed move that share the same details
for moves in to_assign.values():
moves._assign_picking()
self._push_apply()
if merge:
return self._merge_moves(merge_into=merge_into)
return self
重點代碼:moves._assign_picking()
def _assign_picking(self):
""" Try to assign the moves to an existing picking that has not been
reserved yet and has the same procurement group, locations and picking
type (moves should already have them identical). Otherwise, create a new
picking to assign them to. """
Picking = self.env['stock.picking']
for move in self:
recompute = False
picking = move._search_picking_for_assignation()
if picking:
if picking.partner_id.id != move.partner_id.id or picking.origin != move.origin:
# If a picking is found, we'll append `move` to its move list and thus its
# `partner_id` and `ref` field will refer to multiple records. In this
# case, we chose to wipe them.
picking.write({
'partner_id': False,
'origin': False,
})
else:
recompute = True
picking = Picking.create(move._get_new_picking_values())
move.write({'picking_id': picking.id})
move._assign_picking_post_process(new=recompute)
# If this method is called in batch by a write on a one2many and
# at some point had to create a picking, some next iterations could
# try to find back the created picking. As we look for it by searching
# on some computed fields, we have to force a recompute, else the
# record won't be found.
if recompute:
move.recompute()
return True
在此處生成的是確認(rèn)狀態(tài)的picking單以及move單。
但是為什么我們在交貨單中看到的是就緒狀態(tài)的單據(jù)呢?
別急,我們繼續(xù)查找代碼。
在此,先插一句,必須了解什么是jit即時調(diào)度。(我不知道,別問我了!)
以下代碼為procurement_jit
模塊
@api.multi
def _action_launch_stock_rule(self):
res = super(SaleOrderLine, self)._action_launch_stock_rule()
orders = list(set(x.order_id for x in self))
for order in orders:
reassign = order.picking_ids.filtered(lambda x: x.state=='confirmed' or (x.state in ['waiting', 'assigned'] and not x.printed))
if reassign:
reassign.action_assign()
return res
聰明的你,已經(jīng)發(fā)現(xiàn)玄機所在了,不錯,就是執(zhí)行了一個關(guān)鍵代碼reassign.action_assign()
以上。
備注:
花費了一天時間,研究邏輯,可能是本人也不太熟悉這部分業(yè)務(wù)的過程。時間耽誤很長。建議各位研究業(yè)務(wù)過程,業(yè)務(wù)了解,代碼也就好找了。
============================================================================================================================