[IMP] stock,mrp,purchase: split _run method and move it to rules

Previously mrp, stock and purchase used _run methon on procurement
group and start with a if checking for the action type. This commit
instead launch a specific method depending the rule's action's type

Also _run method and their submethod used in procurement group
was always called with a rule. Thus we choose to move this method
on the rule object himself.
This commit is contained in:
amoyaux
2017-09-08 17:08:06 +02:00
parent 283242dbea
commit c914df5a36
15 changed files with 325 additions and 338 deletions
+39 -49
View File
@@ -11,67 +11,57 @@ class ProcurementRule(models.Model):
_inherit = 'procurement.rule'
action = fields.Selection(selection_add=[('manufacture', 'Manufacture')])
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
@api.multi
def _run_manufacture(self, product_id, product_qty, product_uom, location_id, name, origin, values):
Production = self.env['mrp.production']
ProductionSudo = Production.sudo().with_context(force_company=values['company_id'].id)
bom = self._get_matching_bom(product_id, values)
if not bom:
msg = _('No Bill of Material found for product %s.') % (product_id.display_name,)
raise UserError(msg)
# create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
production = ProductionSudo.create(self._prepare_mo_vals(product_id, product_qty, product_uom, location_id, name, origin, values, bom))
origin_production = values.get('move_dest_ids') and values['move_dest_ids'][0].raw_material_production_id or False
orderpoint = values.get('orderpoint_id')
if orderpoint:
production.message_post_with_view('mail.message_origin_link',
values={'self': production, 'origin': orderpoint.id},
subtype_id=self.env.ref('mail.mt_note').id)
if origin_production:
production.message_post_with_view('mail.message_origin_link',
values={'self': production, 'origin': origin_production},
subtype_id=self.env.ref('mail.mt_note').id)
return True
@api.multi
def _run(self, values, rule, doraise=True):
if rule.action == 'manufacture':
Production = self.env['mrp.production']
ProductionSudo = Production.sudo().with_context(force_company=values['company_id'].id)
bom = self._get_matching_bom(values, rule)
if not bom:
msg = _('No Bill of Material found for product %s.') % (values['product_id'].display_name,)
if doraise:
raise UserError(msg)
else:
self.log_next_activity(values['product_id'], msg)
return False
# create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
production = ProductionSudo.create(self._prepare_mo_vals(values, rule, bom))
origin_production = values.get('move_dest_ids') and values ['move_dest_ids'][0].raw_material_production_id or False
orderpoint = values.get('orderpoint_id')
if orderpoint:
production.message_post_with_view('mail.message_origin_link',
values={'self': production, 'origin': orderpoint.id},
subtype_id=self.env.ref('mail.mt_note').id)
if origin_production:
production.message_post_with_view('mail.message_origin_link',
values={'self': production, 'origin': origin_production},
subtype_id=self.env.ref('mail.mt_note').id)
return True
return super(ProcurementGroup, self)._run(values, rule, doraise)
@api.multi
def _get_matching_bom(self, values, rule):
def _get_matching_bom(self, product_id, values):
if values.get('bom_id', False):
return values['bom_id']
return self.env['mrp.bom'].with_context(
company_id=values['company_id'].id, force_company=values['company_id'].id
)._bom_find(product=values['product_id'], picking_type=rule.picking_type_id) # TDE FIXME: context bullshit
)._bom_find(product=product_id, picking_type=self.picking_type_id) # TDE FIXME: context bullshit
def _get_date_planned(self, values, rule):
format_date_planned = fields.Datetime.from_string(values['date_planned'])
date_planned = format_date_planned - relativedelta(days=values['product_id'].produce_delay or 0.0)
date_planned = date_planned - relativedelta(days=values['company_id'].manufacturing_lead)
return date_planned
def _prepare_mo_vals(self, values, rule, bom):
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, values, bom):
return {
'origin': values['origin'],
'product_id': values['product_id'].id,
'product_qty': values['product_qty'],
'product_uom_id': values['product_uom'].id,
'location_src_id': rule.location_src_id.id or values['location_id'].id,
'location_dest_id': values['location_id'].id,
'origin': origin,
'product_id': product_id.id,
'product_qty': product_qty,
'product_uom_id': product_uom.id,
'location_src_id': self.location_src_id.id or location_id.id,
'location_dest_id': location_id.id,
'bom_id': bom.id,
'date_planned_start': fields.Datetime.to_string(self._get_date_planned(values, rule)),
'date_planned_start': fields.Datetime.to_string(self._get_date_planned(product_id, values)),
'date_planned_finished': values['date_planned'],
'procurement_group_id': values.get('group_id').id if values.get('group_id', False) else False,
'propagate': rule.propagate,
'picking_type_id': rule.picking_type_id.id or values['warehouse_id'].manu_type_id.id,
'propagate': self.propagate,
'picking_type_id': self.picking_type_id.id or values['warehouse_id'].manu_type_id.id,
'company_id': values['company_id'].id,
'move_dest_ids': values.get('move_dest_ids') and [(4, x.id) for x in values['move_dest_ids']] or False,
}
def _get_date_planned(self, product_id, values):
format_date_planned = fields.Datetime.from_string(values['date_planned'])
date_planned = format_date_planned - relativedelta(days=product_id.produce_delay or 0.0)
date_planned = date_planned - relativedelta(days=values['company_id'].manufacturing_lead)
return date_planned
+2 -2
View File
@@ -8,8 +8,8 @@ class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
@api.multi
def _action_procurement_create(self):
res = super(SaleOrderLine, self)._action_procurement_create()
def _action_launch_procurement_rule(self):
res = super(SaleOrderLine, self)._action_launch_procurement_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 ['partially_available', 'waiting']) and not x.printed))
+86 -88
View File
@@ -747,6 +747,12 @@ class PurchaseOrderLine(models.Model):
else:
return datetime.today() + relativedelta(days=seller.delay if seller else 0)
def _merge_in_existing_line(self, product_id, product_qty, product_uom, location_id, name, origin, values):
""" This function purpose is to be override with the purpose to forbide _run_buy method
to merge a new po line in an existing one.
"""
return True
@api.onchange('product_id')
def onchange_product_id(self):
result = {}
@@ -842,62 +848,62 @@ class PurchaseOrderLine(models.Model):
self.product_qty = 1.0
class ProcurementRule(models.Model):
_inherit = 'procurement.rule'
action = fields.Selection(selection_add=[('buy', 'Buy')])
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
@api.model
def _get_exceptions_domain(self):
return super(ProcurementGroup, self)._get_exceptions_domain() + [('created_purchase_line_id', '=', False)]
class ProcurementRule(models.Model):
_inherit = 'procurement.rule'
action = fields.Selection(selection_add=[('buy', 'Buy')])
@api.multi
def _run(self, values, rule, doraise=True):
if rule.action == 'buy':
cache = {}
suppliers = values['product_id'].seller_ids\
.filtered(lambda r: (not r.company_id or r.company_id == values['company_id']) and (not r.product_id or r.product_id == values['product_id']))
if not suppliers:
msg = _('No vendor associated to product %s. Unable to generate the purchase order.') % (values['product_id'].display_name,)
if doraise:
raise UserError(msg)
def _run_buy(self, product_id, product_qty, product_uom, location_id, name, origin, values):
cache = {}
suppliers = product_id.seller_ids\
.filtered(lambda r: (not r.company_id or r.company_id == values['company_id']) and (not r.product_id or r.product_id == product_id))
if not suppliers:
msg = _('No vendor associated to product %s. Unable to generate the purchase order.') % (product_id.display_name,)
raise UserError(msg)
supplier = self._make_po_select_supplier(values, suppliers)
partner = supplier.name
domain = self._make_po_get_domain(values, partner)
if domain in cache:
po = cache[domain]
else:
po = self.env['purchase.order'].search([dom for dom in domain])
po = po[0] if po else False
cache[domain] = po
if not po:
vals = self._prepare_purchase_order(product_id, product_qty, product_uom, origin, values, partner)
po = self.env['purchase.order'].create(vals)
cache[domain] = po
elif not po.origin or origin not in po.origin.split(', '):
if po.origin:
if origin:
po.write({'origin': po.origin + ', ' + origin})
else:
self._log_next_activity(values['product_id'], msg)
return False
supplier = self._make_po_select_supplier(values, rule, suppliers)
partner = supplier.name
domain = self._make_po_get_domain(values, rule, partner)
if domain in cache:
po = cache[domain]
po.write({'origin': po.origin})
else:
po = self.env['purchase.order'].search([dom for dom in domain])
po = po[0] if po else False
cache[domain] = po
if not po:
vals = self._prepare_purchase_order(values, rule, partner)
po = self.env['purchase.order'].create(vals)
cache[domain] = po
elif not po.origin or values['origin'] not in po.origin.split(', '):
if po.origin:
if values['origin']:
po.write({'origin': po.origin + ', ' + values['origin']})
else:
po.write({'origin': po.origin})
else:
po.write({'origin': values['origin']})
po.write({'origin': origin})
# Create Line
po_line = False
for line in po.order_line:
if line.product_id == values['product_id'] and line.product_uom == values['product_id'].uom_po_id:
procurement_uom_po_qty = values['product_uom']._compute_quantity(values['product_qty'], values['product_id'].uom_po_id)
seller = values['product_id']._select_seller(
# Create Line
po_line = False
for line in po.order_line:
if line.product_id == product_id and line.product_uom == product_id.uom_po_id:
if line._merge_in_existing_line(product_id, product_qty, product_uom, location_id, name, origin, values):
procurement_uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id)
seller = product_id._select_seller(
partner_id=partner,
quantity = line.product_qty + procurement_uom_po_qty,
date = po.date_order and po.date_order[:10],
uom_id=values['product_id'].uom_po_id)
quantity=line.product_qty + procurement_uom_po_qty,
date=po.date_order and po.date_order[:10],
uom_id=product_id.uom_po_id)
price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, line.product_id.supplier_taxes_id, line.taxes_id) if seller else 0.0
if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id:
@@ -909,50 +915,48 @@ class ProcurementGroup(models.Model):
'move_dest_ids': [(4, x.id) for x in values.get('move_dest_ids', [])]
})
break
if not po_line:
vals = self._prepare_purchase_order_line(values, rule, po, supplier)
self.env['purchase.order.line'].create(vals)
return True
return super(ProcurementGroup, self)._run(values, rule, doraise=True)
if not po_line:
vals = self._prepare_purchase_order_line(product_id, product_qty, product_uom, values, po, supplier)
self.env['purchase.order.line'].create(vals)
def _get_purchase_schedule_date(self, values, rule):
def _get_purchase_schedule_date(self, values):
"""Return the datetime value to use as Schedule Date (``date_planned``) for the
Purchase Order Lines created to satisfy the given procurement. """
procurement_date_planned = datetime.strptime(values['date_planned'], DEFAULT_SERVER_DATETIME_FORMAT)
procurement_date_planned = fields.Datetime.from_string(values['date_planned'])
schedule_date = (procurement_date_planned - relativedelta(days=values['company_id'].po_lead))
return schedule_date
def _get_purchase_order_date(self, values, rule, partner, schedule_date):
def _get_purchase_order_date(self, product_id, product_qty, product_uom, values, partner, schedule_date):
"""Return the datetime value to use as Order Date (``date_order``) for the
Purchase Order created to satisfy the given procurement. """
seller = values['product_id']._select_seller(
seller = product_id._select_seller(
partner_id=partner,
quantity=values['product_qty'],
quantity=product_qty,
date=fields.Date.to_string(schedule_date),
uom_id=values['product_uom'])
uom_id=product_uom)
return schedule_date - relativedelta(days=int(seller.delay))
@api.model
def _prepare_purchase_order_line(self, values, rule, po, supplier):
procurement_uom_po_qty = values['product_uom']._compute_quantity(values['product_qty'], values['product_id'].uom_po_id)
seller = values['product_id']._select_seller(
@api.multi
def _prepare_purchase_order_line(self, product_id, product_qty, product_uom, values, po, supplier):
procurement_uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id)
seller = product_id._select_seller(
partner_id=supplier.name,
quantity=procurement_uom_po_qty,
date=po.date_order and po.date_order[:10],
uom_id=values['product_id'].uom_po_id)
uom_id=product_id.uom_po_id)
taxes = values['product_id'].supplier_taxes_id
taxes = product_id.supplier_taxes_id
fpos = po.fiscal_position_id
taxes_id = fpos.map_tax(taxes) if fpos else taxes
if taxes_id:
taxes_id = taxes_id.filtered(lambda x: x.company_id.id == values['company_id'].id)
price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, values['product_id'].supplier_taxes_id, taxes_id) if seller else 0.0
price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, product_id.supplier_taxes_id, taxes_id) if seller else 0.0
if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id:
price_unit = seller.currency_id.compute(price_unit, po.currency_id)
product_lang = values['product_id'].with_context({
product_lang = product_id.with_context({
'lang': supplier.name.lang,
'partner_id': supplier.name.id,
})
@@ -965,8 +969,8 @@ class ProcurementGroup(models.Model):
return {
'name': name,
'product_qty': procurement_uom_po_qty,
'product_id': values['product_id'].id,
'product_uom': values['product_id'].uom_po_id.id,
'product_id': product_id.id,
'product_uom': product_id.uom_po_id.id,
'price_unit': price_unit,
'date_planned': date_planned,
'orderpoint_id': values.get('orderpoint_id', False) and values.get('orderpoint_id').id,
@@ -975,56 +979,50 @@ class ProcurementGroup(models.Model):
'move_dest_ids': [(4, x.id) for x in values.get('move_dest_ids', [])],
}
def _prepare_purchase_order(self, values, rule, partner):
schedule_date = self._get_purchase_schedule_date(values, rule)
purchase_date = self._get_purchase_order_date(values, rule, partner, schedule_date)
def _prepare_purchase_order(self, product_id, product_qty, product_uom, origin, values, partner):
schedule_date = self._get_purchase_schedule_date(values)
purchase_date = self._get_purchase_order_date(product_id, product_qty, product_uom, values, partner, schedule_date)
fpos = self.env['account.fiscal.position'].with_context(company_id=values['company_id'].id).get_fiscal_position(partner.id)
gpo = rule.group_propagation_option
group = (gpo == 'fixed' and rule.group_id.id) or \
gpo = self.group_propagation_option
group = (gpo == 'fixed' and self.group_id.id) or \
(gpo == 'propagate' and values['group_id'].id) or False
return {
'partner_id': partner.id,
'picking_type_id': rule.picking_type_id.id,
'picking_type_id': self.picking_type_id.id,
'company_id': values['company_id'].id,
'currency_id': partner.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id,
'dest_address_id': values.get('partner_dest_id', False) and values['partner_dest_id'].id,
'origin': values['origin'],
'origin': origin,
'payment_term_id': partner.property_supplier_payment_term_id.id,
'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'fiscal_position_id': fpos,
'group_id': group
}
def _make_po_select_supplier(self, values, rule, suppliers):
def _make_po_select_supplier(self, values, suppliers):
""" Method intended to be overridden by customized modules to implement any logic in the
selection of supplier.
"""
return suppliers[0]
def _make_po_get_domain(self, values, rule, partner):
domain = super(ProcurementGroup, self)._make_po_get_domain(values, partner)
gpo = rule.group_propagation_option
group = (gpo == 'fixed' and rule.group_id) or \
def _make_po_get_domain(self, values, partner):
domain = super(ProcurementRule, self)._make_po_get_domain(values, partner)
gpo = self.group_propagation_option
group = (gpo == 'fixed' and self.group_id) or \
(gpo == 'propagate' and values['group_id']) or False
domain += (
('partner_id', '=', partner.id),
('state', '=', 'draft'),
('picking_type_id', '=', rule.picking_type_id.id),
('picking_type_id', '=', self.picking_type_id.id),
('company_id', '=', values['company_id'].id),
)
if group:
domain += (('group_id', '=', group.id),)
if values.get('date_planned', False):
domain += (('date_planned', '=', values.get('date_planned')),)
return domain
@api.model
def _get_exceptions_domain(self):
return super(ProcurementGroup, self)._get_exceptions_domain() + [('created_purchase_line_id', '=', False)]
class ProductTemplate(models.Model):
_name = 'product.template'
+1 -7
View File
@@ -10,17 +10,11 @@ class TestPurchase(TestStockCommon):
def _create_make_procurement(self, product, product_qty, date_planned=False):
ProcurementGroup = self.env['procurement.group']
order_values = {
'name': product.name,
'product_id': product,
'product_qty': product_qty,
'product_uom': self.uom_unit,
'warehouse_id': self.warehouse_1,
'date_planned': date_planned or fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10)), # 10 days added to current date of procurement to get future schedule date and order date of purchase order.
'location_id': self.warehouse_1.lot_stock_id,
'origin': '/',
'group_id': self.env['procurement.group'],
}
return ProcurementGroup.run(order_values)
return ProcurementGroup.run(product, product_qty, self.uom_unit, self.warehouse_1.lot_stock_id, product.name, '/', order_values)
@classmethod
def setUpClass(cls):
@@ -104,22 +104,16 @@ class TestPurchaseLeadTime(TestPurchase):
date_planned = fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10))
# Create procurement order of product_1
self.env['procurement.group'].run({
'product_id': self.product_1,
'product_qty': 5.000,
'name': 'Test scheduler for RFQ',
'product_uom': self.uom_unit,
self.env['procurement.group'].run(self.product_1, 5.000, self.uom_unit, self.warehouse_1.lot_stock_id, 'Test scheduler for RFQ', '/', {
'warehouse_id': self.warehouse_1,
'location_id': self.warehouse_1.lot_stock_id,
'date_planned': date_planned, # 10 days added to current date of procurement to get future schedule date and order date of purchase order.
'rule_id': self.warehouse_1.buy_pull_id,
'origin': '/',
'group_id': False, #self.env['procurement.group'],
'group_id': False,
'route_ids': [],
})
# Confirm purchase order
purchase = self.env['purchase.order.line'].search([('product_id', '=', self.product_1.id)], limit=1).order_id
purchase.button_confirm()
@@ -105,6 +105,20 @@ class PurchaseRequisition(models.Model):
raise UserError(_('You have to cancel or validate every RfQ before closing the purchase requisition.'))
self.write({'state': 'done'})
def _prepare_tender_values(self, product_id, product_qty, product_uom, location_id, name, origin, values):
return{
'origin': origin,
'date_end': values['date_planned'],
'warehouse_id': values.get('warehouse_id') and values['warehouse_id'].id or False,
'company_id': values['company_id'].id,
'line_ids': [(0, 0, {
'product_id': product_id.id,
'product_uom_id': product_uom.id,
'product_qty': product_qty,
'move_dest_id': values.get('move_dest_ids') and values['move_dest_ids'][0].id or False,
})],
}
class PurchaseRequisitionLine(models.Model):
_name = "purchase.requisition.line"
@@ -296,24 +310,14 @@ class ProductTemplate(models.Model):
string='Procurement', default='rfq')
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
class ProcurementRule(models.Model):
_inherit = 'procurement.rule'
@api.multi
def _run_buy(self, product_id, product_qty, product_uom, location_id, name, origin, values, log_activites=False):
def _run_buy(self, product_id, product_qty, product_uom, location_id, name, origin, values):
if product_id.purchase_requisition != 'tenders':
return super(ProcurementGroup, self)._run_buy(product_id, product_qty, product_uom, location_id, name, origin, values, log_activites)
self.env['purchase.requisition'].create({
'origin': origin,
'date_end': values['date_planned'],
'warehouse_id': values.get('warehouse_id') and values['warehouse_id'].id or False,
'company_id': values['company_id'].id,
'picking_type_id': self.picking_type_id.id,
'line_ids': [(0, 0, {
'product_id': product_id.id,
'product_uom_id': product_uom.id,
'product_qty': product_qty,
'move_dest_id': values.get('move_dest_ids') and values['move_dest_ids'][0].id or False,
})],
})
return super(ProcurementRule, self)._run_buy(product_id, product_qty, product_uom, location_id, name, origin, values)
values = self.env['purchase.requisition']._prepare_tender_values(product_id, product_qty, product_uom, location_id, name, origin, values)
values['picking_type_id'] = self.picking_type_id.id
self.env['purchase.requisition'].create(values)
return True
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import common
from odoo.tools import float_compare
from odoo import fields
@@ -48,16 +47,12 @@ class TestPurchaseRequisition(common.TransactionCase):
warehouse = self.env['stock.warehouse'].browse(self.ref('stock.warehouse0'))
product = self.env['product.product'].browse(self.product_13_id)
product.write({'route_ids': [(4, self.ref('purchase.route_warehouse0_buy'))]})
self.env['procurement.group'].run({'product_id': product,
'product_qty': 14,
'product_uom': self.env['product.uom'].browse(self.ref('product.product_uom_unit')),
'warehouse_id': warehouse,
'location_id': warehouse.lot_stock_id,
'name': '/',
'origin': '/',
'date_planned': date_planned,
})
self.env['procurement.group'].run(product, 14, self.env['product.uom'].browse(self.ref('product.product_uom_unit')), warehouse.lot_stock_id, '/', '/',
{
'warehouse_id': warehouse,
'date_planned': date_planned,
})
# Check requisition details which created after run procurement.
line = self.env['purchase.requisition.line'].search([('product_id', '=', self.product_13_id), ('product_qty', '=', 14.0)])
requisition = line[0].requisition_id
+8
View File
@@ -916,6 +916,14 @@ class SaleOrderLine(models.Model):
invoice_lines |= self.env['account.invoice.line'].create(vals)
return invoice_lines
@api.multi
def _prepare_procurement_values(self, group_id=False):
""" Prepare specific key for moves or other components that will be created from a procurement rule
comming from a sale order line. This method could be override in order to add other custom key that could
be used in move/po creation.
"""
return {}
@api.multi
def _get_display_price(self, product):
# TO DO: move me in master/saas-16 on sale.order
+2 -2
View File
@@ -66,8 +66,8 @@ class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
@api.multi
def _prepare_order_line_procurement(self, group_id):
vals = super(SaleOrderLine, self)._prepare_order_line_procurement(group_id=group_id)
def _prepare_procurement_values(self, group_id):
vals = super(SaleOrderLine, self)._prepare_procurement_values(group_id=group_id)
for line in self.filtered("order_id.requested_date"):
date_planned = fields.Datetime.from_string(line.order_id.requested_date) - timedelta(days=line.order_id.company_id.security_lead)
vals.update({
+20 -20
View File
@@ -37,7 +37,7 @@ class SaleOrder(models.Model):
def action_confirm(self):
result = super(SaleOrder, self).action_confirm()
for order in self:
order.order_line._action_procurement_create()
order.order_line._action_launch_procurement_rule()
return result
@api.depends('picking_ids')
@@ -89,7 +89,7 @@ class SaleOrderLine(models.Model):
def create(self, values):
line = super(SaleOrderLine, self).create(values)
if line.state == 'sale':
line._action_procurement_create()
line._action_launch_procurement_rule()
return line
@api.multi
@@ -101,7 +101,7 @@ class SaleOrderLine(models.Model):
lambda r: r.state == 'sale' and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1)
res = super(SaleOrderLine, self).write(values)
if lines:
lines._action_procurement_create()
lines._action_launch_procurement_rule()
return res
@@ -186,31 +186,32 @@ class SaleOrderLine(models.Model):
return {}
@api.multi
def _prepare_order_line_procurement(self, group_id=False):
def _prepare_procurement_values(self, group_id=False):
""" Prepare specific key for moves or other components that will be created from a procurement rule
comming from a sale order line. This method could be override in order to add other custom key that could
be used in move/po creation.
"""
values = super(SaleOrderLine, self)._prepare_procurement_values(group_id)
self.ensure_one()
date_planned = datetime.strptime(self.order_id.confirmation_date, DEFAULT_SERVER_DATETIME_FORMAT)\
+ timedelta(days=self.customer_lead or 0.0) - timedelta(days=self.order_id.company_id.security_lead)
return {
'name': self.name,
'origin': self.order_id.name,
'product_id': self.product_id,
'product_qty': self.product_uom_qty,
'product_uom': self.product_uom,
values.update({
'company_id': self.order_id.company_id,
'group_id': group_id,
'sale_line_id': self.id,
'date_planned': date_planned.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'location_id': self.order_id.partner_shipping_id.property_stock_customer,
'route_ids': self.route_id,
'warehouse_id': self.order_id.warehouse_id or False,
'partner_dest_id': self.order_id.partner_shipping_id
}
})
return values
@api.multi
def _action_procurement_create(self):
def _action_launch_procurement_rule(self):
"""
Create procurements based on quantity ordered. If the quantity is increased, new
procurements are created. If the quantity is decreased, no automated action is taken.
Launch procurement group run method with required/custom fields genrated by a
sale order line. procurement group will launch '_run_move', '_run_buy' or '_run_manufacture'
depending on the sale order line product rule.
"""
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
for line in self:
@@ -223,17 +224,16 @@ class SaleOrderLine(models.Model):
continue
if not line.order_id.procurement_group_id:
line.order_id.procurement_group_id = self.env["procurement.group"].create({
line.order_id.procurement_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
})
vals = line._prepare_order_line_procurement(group_id=line.order_id.procurement_group_id)
vals['product_qty'] = line.product_uom_qty - qty
values = line._prepare_procurement_values(group_id=line.order_id.procurement_group_id)
product_qty = line.product_uom_qty - qty
new_proc = self.env["procurement.group"].run(vals)
self.env['procurement.group'].run(line.product_id, product_qty, line.product_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, values)
return True
@api.multi
def _get_delivered_qty(self):
self.ensure_one()
+17 -13
View File
@@ -20,17 +20,6 @@ class StockMove(models.Model):
line.qty_delivered = line._get_delivered_qty()
return result
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
sale_id = fields.Many2one('sale.order', 'Sale Order')
def _get_stock_move_values(self, values, rule, group_id):
result = super(ProcurementGroup, self)._get_stock_move_values(values, rule, group_id)
if values.get('sale_line_id', False):
result['sale_line_id'] = values['sale_line_id']
return result
@api.multi
def write(self, vals):
res = super(StockMove, self).write(vals)
@@ -43,10 +32,25 @@ class ProcurementGroup(models.Model):
return res
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
sale_id = fields.Many2one('sale.order', 'Sale Order')
class ProcurementRule(models.Model):
_inherit = 'procurement.rule'
def _get_stock_move_values(self, product_id, product_qty, product_uom, location_id, name, origin, values, group_id):
result = super(ProcurementRule, self)._get_stock_move_values(product_id, product_qty, product_uom, location_id, name, origin, values, group_id)
if values.get('sale_line_id', False):
result['sale_line_id'] = values['sale_line_id']
return result
class StockPicking(models.Model):
_inherit = 'stock.picking'
sale_id = fields.Many2one(related="group_id.sale_id", string="Sales Order", store=True)
@api.multi
@@ -59,4 +63,4 @@ class StockPicking(models.Model):
'mail.message_origin_link',
values={'self': backorder, 'origin': backorder.sale_id},
subtype_id=self.env.ref('mail.mt_note').id)
return res
return res
+103 -100
View File
@@ -62,6 +62,82 @@ class ProcurementRule(models.Model):
'stock.warehouse', 'Warehouse to Propagate',
help="The warehouse to propagate on the created move/procurement, which can be different of the warehouse this rule is for (e.g for resupplying rules from another warehouse)")
@api.multi
def _run_move(self, product_id, product_qty, product_uom, location_id, name, origin, values):
if not self.location_src_id:
msg = _('No source location defined on procurement 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().create(data)
move.assign_picking()
move.picking_id.action_confirm()
return True
def _get_stock_move_values(self, product_id, product_qty, product_uom, location_id, name, origin, values, group_id):
''' Returns a dictionary of values that will be used to create a stock move from a procurement.
This function assumes that the given procurement has a rule (action == 'move') set on it.
:param procurement: browse record
:rtype: dictionary
'''
date_expected = (datetime.strptime(values['date_planned'], DEFAULT_SERVER_DATETIME_FORMAT) - relativedelta(days=self.delay or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
# it is possible that we've already got some move done, so check for the done qty and create
# a new move with the correct qty
qty_left = product_qty
return {
'name': name[:2000],
'company_id': self.company_id.id or self.location_src_id.company_id.id or self.location_id.company_id.id or values['company_id'].id,
'product_id': product_id.id,
'product_uom': product_uom.id,
'product_uom_qty': qty_left,
'partner_id': self.partner_address_id.id or (values.get('group_id', False) and values['group_id'].partner_id.id) or False,
'location_id': self.location_src_id.id,
'location_dest_id': location_id.id,
'move_dest_ids': values.get('move_dest_ids', False) and [(4, x.id) for x in values['move_dest_ids']] or [],
'rule_id': self.id,
'procure_method': self.procure_method,
'origin': origin,
'picking_type_id': self.picking_type_id.id,
'group_id': group_id,
'route_ids': [(4, route.id) for route in values.get('route_ids', [])],
'warehouse_id': self.propagate_warehouse_id.id or self.warehouse_id.id,
'date': date_expected,
'date_expected': date_expected,
'propagate': self.propagate,
'priority': values.get('priority', "1"),
}
def _log_next_activity(self, product_id, note):
existing_activity = self.env['mail.activity'].search([('res_id', '=', product_id.product_tmpl_id.id), ('res_model_id', '=', self.env.ref('product.model_product_template').id),
('note', '=', note)])
if not existing_activity:
# If the user deleted todo activity type.
try:
activity_type_id = self.env.ref('mail.mail_activity_data_todo').id
except:
activity_type_id = False
self.env['mail.activity'].create({
'activity_type_id': activity_type_id,
'note': note,
'user_id': product_id.responsible_id.id,
'res_id': product_id.product_tmpl_id.id,
'res_model_id': self.env.ref('product.model_product_template').id,
})
def _make_po_get_domain(self, values, partner):
return ()
class ProcurementGroup(models.Model):
"""
The procurement group class is used to group products together
@@ -99,44 +175,21 @@ class ProcurementGroup(models.Model):
('one', 'All at once')], string='Delivery Type', default='direct',
required=True)
def _make_po_get_domain(self, values, partner):
return ()
def _log_next_activity(self, product_id, note):
existing_activity = self.env['mail.activity'].search([('res_id', '=', product_id.product_tmpl_id.id), ('res_model_id', '=', self.env.ref('product.model_product_template').id),
('note', '=', note)])
if not existing_activity:
self.env['mail.activity'].create({
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
'note': note,
'user_id': product_id.responsible_id.id,
'res_id': product_id.product_tmpl_id.id,
'res_model_id': self.env.ref('product.model_product_template').id,
})
@api.model
def run(self, values, doraise=True):
for key in ('product_id', 'product_uom', 'product_qty', 'location_id', 'name', 'origin'):
assert key in values
def run(self, product_id, product_qty, product_uom, location_id, name, origin, values):
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(values)
rule = self._get_rule(product_id, location_id, values)
if not rule:
if doraise:
raise UserError(_('No procurement rule found.'))
else:
msg = _('No procurement rule found. Update the inventory tab of the product form.')
if values.get('orderpoint_id', False):
msg = _('No procurement rule found for orderpoint %s. Update the inventory tab of the product form.') % (values['orderpoint_id'].name,)
self._log_next_activity(values['product_id'], msg)
return False
self._run(values, rule, doraise)
raise UserError(_('No procurement rule found.'))
getattr(rule, '_run_%s' % rule.action)(product_id, product_qty, product_uom, location_id, name, origin, values)
return True
@api.model
def _search_rule(self, values, domain):
def _search_rule(self, product_id, values, domain):
""" First find a rule among the ones defined on the procurement
group; then try on the routes defined for the product; finally fallback
on the default behavior """
@@ -147,7 +200,7 @@ class ProcurementGroup(models.Model):
if values.get('route_ids', False):
res = Pull.search(expression.AND([[('route_id', 'in', values['route_ids'].ids)], domain]), order='route_sequence, sequence', limit=1)
if not res:
product_routes = values['product_id'].route_ids | values['product_id'].categ_id.total_route_ids
product_routes = product_id.route_ids | product_id.categ_id.total_route_ids
if product_routes:
res = Pull.search(expression.AND([[('route_id', 'in', product_routes.ids)], domain]), order='route_sequence, sequence', limit=1)
if not res:
@@ -157,51 +210,17 @@ class ProcurementGroup(models.Model):
return res
@api.model
def _get_rule(self, values):
def _get_rule(self, product_id, location_id, values):
result = False
location = values['location_id']
location = location_id
while (not result) and location:
result = self._search_rule(values, [('location_id', '=', location.id)])
result = self._search_rule(product_id, values, [('location_id', '=', location.id)])
location = location.location_id
return result
def _get_stock_move_values(self, values, rule, group_id):
''' Returns a dictionary of values that will be used to create a stock move from a procurement.
This function assumes that the given procurement has a rule (action == 'move') set on it.
:param procurement: browse record
:rtype: dictionary
'''
date_expected = (datetime.strptime(values['date_planned'], DEFAULT_SERVER_DATETIME_FORMAT) - relativedelta(days=rule.delay or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
# it is possible that we've already got some move done, so check for the done qty and create
# a new move with the correct qty
qty_left = values['product_qty']
return {
'name': values['name'][:2000],
'company_id': rule.company_id.id or rule.location_src_id.company_id.id or rule.location_id.company_id.id or values['company_id'].id,
'product_id': values['product_id'].id,
'product_uom': values['product_uom'].id,
'product_uom_qty': qty_left,
'partner_id': rule.partner_address_id.id or (values['group_id'] and values['group_id'].partner_id.id) or False,
'location_id': rule.location_src_id.id,
'location_dest_id': values['location_id'].id,
'move_dest_ids': values.get('move_dest_ids', False) and [(4, x.id) for x in values['move_dest_ids']] or [],
'rule_id': rule.id,
'procure_method': rule.procure_method,
'origin': values['origin'],
'picking_type_id': rule.picking_type_id.id,
'group_id': group_id,
'route_ids': [(4, route.id) for route in values.get('route_ids', [])],
'warehouse_id': rule.propagate_warehouse_id.id or rule.warehouse_id.id,
'date': date_expected,
'date_expected': date_expected,
'propagate': rule.propagate,
'priority': values.get('priority', "1"),
}
def _merge_domain(self, values, rule, group_id):
return [
('group_id', '=', group_id), #extra logic?
('group_id', '=', group_id), # extra logic?
('location_id', '=', rule.location_src_id.id),
('location_dest_id', '=', values['location_id'].id),
('picking_type_id', '=', rule.picking_type_id.id),
@@ -210,33 +229,6 @@ class ProcurementGroup(models.Model):
('picking_id.backorder_id', '=', False),
('product_id', '=', values['product_id'].id)]
@api.multi
def _run(self, values, rule, doraise=True):
if rule.action == 'move':
if not rule.location_src_id:
msg = _('No source location defined on procurement rule: %s!') % (rule.name, )
if doraise:
raise UserError(msg)
else:
self._log_next_activity(values['product_id'], msg)
return False
# 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 rule.group_propagation_option == 'propagate':
group_id = values.get('group_id', False) and values['group_id'].id
elif rule.group_propagation_option == 'fixed':
group_id = rule.group_id.id
data = self._get_stock_move_values(values, rule, group_id)
# Since action_confirm launch following procurement_group we should activate it.
move = self.env['stock.move'].sudo().create(data)
move.assign_picking()
move.picking_id.action_confirm()
return True
return False
@api.model
def _get_exceptions_domain(self):
return [('procure_method', '=', 'make_to_order'), ('move_orig_ids', '=', False)]
@@ -260,10 +252,17 @@ class ProcurementGroup(models.Model):
self.env['stock.move'].browse(moves_chunk).action_assign()
if use_new_cursor:
self._cr.commit()
exception_moves = self.env['stock.move'].search(self._get_exceptions_domain())
for move in exception_moves:
self.env['procurement.group'].run(move._prepare_procurement_from_move(), doraise=False)
values = move._prepare_procurement_values()
try:
with self._cr.savepoint():
origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or move.origin or move.picking_id.name or "/")
self.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)
except UserError as error:
self.env['procurement.rule']._log_next_activity(move.product_id, error.name)
if use_new_cursor:
self._cr.commit()
finally:
@@ -355,8 +354,13 @@ class ProcurementGroup(models.Model):
qty -= substract_quantity[orderpoint.id]
qty_rounded = float_round(qty, precision_rounding=orderpoint.product_uom.rounding)
if qty_rounded > 0:
self.env['procurement.group'].run(
orderpoint._prepare_procurement_values(qty_rounded, **group['procurement_values']), doraise=False)
values = orderpoint._prepare_procurement_values(qty_rounded, **group['procurement_values'])
try:
with self._cr.savepoint():
self.env['procurement.group'].run(orderpoint.product_id, qty_rounded, orderpoint.product_uom, orderpoint.location_id,
orderpoint.name, orderpoint.name, values)
except UserError as error:
self.env['procurement.rule']._log_next_activity(orderpoint.product_id, error.name)
self._procurement_from_orderpoint_post_process([orderpoint.id])
if use_new_cursor:
cr.commit()
@@ -384,4 +388,3 @@ class ProcurementGroup(models.Model):
cr.close()
return {}
+9 -9
View File
@@ -589,7 +589,10 @@ class StockMove(models.Model):
# create procurements for make to order moves
for move in move_create_proc:
self.env['procurement.group'].run(move._prepare_procurement_from_move())
values = move._prepare_procurement_values()
origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_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'})
@@ -600,9 +603,12 @@ class StockMove(models.Model):
self._push_apply()
return self
def _prepare_procurement_from_move(self):
def _prepare_procurement_values(self):
""" Prepare specific key for moves or other componenets that will be created from a procurement rule
comming from a stock move. This method could be override in order to add other custom key that could
be used in move/po creation.
"""
self.ensure_one()
origin = (self.group_id and (self.group_id.name + ":") or "") + (self.rule_id and self.rule_id.name or self.origin or self.picking_id.name or "/")
group_id = self.group_id or False
if self.rule_id:
if self.rule_id.group_propagation_option == 'fixed' and self.rule_id.group_id:
@@ -610,14 +616,8 @@ class StockMove(models.Model):
elif self.rule_id.group_propagation_option == 'none':
group_id = False
return {
'name': self.rule_id and self.rule_id.name or "/",
'origin': origin,
'company_id': self.company_id,
'date_planned': self.date,
'product_id': self.product_id,
'product_qty': self.product_uom_qty,
'product_uom': self.product_uom,
'location_id': self.location_id,
'move_dest_ids': self,
'group_id': group_id,
'route_ids': self.route_ids,
+4 -7
View File
@@ -824,15 +824,12 @@ class Orderpoint(models.Model):
@api.multi
def _prepare_procurement_values(self, product_qty, date=False, group=False):
""" Prepare specific key for moves or other components that will be created from a procurement rule
comming from an orderpoint. This method could be override in order to add other custom key that could
be used in move/po creation.
"""
return {
'name': self.name,
'date_planned': date or self._get_date_planned(product_qty, datetime.today()),
'product_id': self.product_id,
'product_qty': product_qty,
'company_id': self.company_id,
'product_uom': self.product_uom,
'location_id': self.location_id,
'origin': self.name,
'warehouse_id': self.warehouse_id,
'orderpoint_id': self,
'group_id': group or self.group_id,
+4 -4
View File
@@ -17,12 +17,12 @@ class PurchaseOrderLine(models.Model):
return res
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
class ProcurementRule(models.Model):
_inherit = 'procurement.rule'
@api.model
def _prepare_purchase_order_line(self, values, rule, po, supplier):
res = super(ProcurementGroup, self)._prepare_purchase_order_line(values, rule, po, supplier)
def _prepare_purchase_order_line(self, product_id, product_qty, product_uom, values, po, supplier):
res = super(ProcurementRule, self)._prepare_purchase_order_line(product_id, product_qty, product_uom, values, po, supplier)
res['sale_line_id'] = values.get('sale_line_id', False)
return res