Skip to content

Commit

Permalink
[IMP] stock: remove procurement orders to fufill immediately
Browse files Browse the repository at this point in the history
This removes the procurement.order model. To fufill their needs SO, PO, MO and
stock moves now call the _run method of the relevant procurement.group.

This mecanism is now only used for stockable product, tasks now uses their own
independent mecanism.

The _run method will check all the applicable rules and create directly the
needed model to fufill the need.

The modules stock, purchase, mrp, extends the _run method to implement their
specific strategy relevant for the rule type they define.

If an exception happens the message will be logged as a mail messsage on the
source model, for example, if a sales order cannot be fufilled the salesperson
will now see directly the reason.

OLD commit messages:
[WIP] procurement: removing procurement.order in stock, sale, purchase, sale_stock. WIP
fixup! [WIP] procurement: removing procurement.order in stock, sale, purchase, sale_stock. WIP
[IMP] Basic tests
[FIX] test not necessary anymore
[FIX] remove unnecessary print statement
[FIX] unnecessary test + why passing warehouse worked before?
[IMP] purchase: one move by purchase order line
[FIX] purchase: correct inventory tests and pass move_dest_ids among procurements
[FIX] because of bad cherry-pick merge
[IMP] make mrp pass by adding move_dest_ids there too
[IMP] tests of sale_mrp, no need for cancelpropagation then
[IMP] better to consistently use recordsets also for one2many
[FIX] purchase_requisition
[FIX] Exceptions should trigger errors, which should be caught in the tests
[FIX] sale_mrp: remove usage of procurement.order and use sale order name instead of sol
[FIX] stock_dropshipping: add sale_line_id on purchase_line_id
[FIX] Remove pdb
[IMP] add stock_dropshipping files
[IMP] stock: search carrier through sale line instead of procurement group
[IMP] add procrule test and preision needed when updating sol
[FIX] sale_order_dates + [IMP] procurement exceptions by scheduler
[FIX] No need to return task
[IMP] move file as name changes and add corrections
[FIX] Continue Run Schedulers wizard fix
[FIX] name issues of takss
[FIX] updating sale order line, but there is still a problem with the recompute
  • Loading branch information
fpodoo authored and amoyaux committed Sep 8, 2017
1 parent ad2dc1e commit 0e4f3bb
Show file tree
Hide file tree
Showing 101 changed files with 1,078 additions and 1,697 deletions.
2 changes: 1 addition & 1 deletion addons/delivery/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ def _cal_move_weight(self):

def _get_new_picking_values(self):
vals = super(StockMove, self)._get_new_picking_values()
vals['carrier_id'] = self.group_id.sale_order_id.carrier_id.id
vals['carrier_id'] = self.sale_line_id.order_id.carrier_id.id
return vals
2 changes: 1 addition & 1 deletion addons/mrp/data/mrp_demo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@
<function model="stock.inventory" name="action_done" eval="[[ref('stock_inventory_product_laptop_charger'), ref('stock_inventory_product_laptop_keypad')]]"/>

<!-- Run Scheduler -->
<function model="procurement.order" name="run_scheduler"/>
<function model="procurement.group" name="run_scheduler"/>


<!-- OEE -->
Expand Down
15 changes: 2 additions & 13 deletions addons/mrp/models/mrp_production.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from odoo.exceptions import UserError
from odoo.tools import float_compare


class MrpProduction(models.Model):
""" Manufacturing Orders """
_name = 'mrp.production'
Expand Down Expand Up @@ -112,6 +111,7 @@ def _get_default_location_dest_id(self):
copy=False, oldname='workcenter_lines', readonly=True)
workorder_count = fields.Integer('# Work Orders', compute='_compute_workorder_count')
workorder_done_count = fields.Integer('# Done Work Orders', compute='_compute_workorder_done_count')
move_dest_ids = fields.One2many('stock.move', 'created_production_id')

state = fields.Selection([
('confirmed', 'Confirmed'),
Expand Down Expand Up @@ -149,7 +149,6 @@ def _get_default_location_dest_id(self):
procurement_group_id = fields.Many2one(
'procurement.group', 'Procurement Group',
copy=False)
procurement_ids = fields.One2many('procurement.order', 'production_id', 'Related Procurements')
propagate = fields.Boolean(
'Propagate cancel and split',
help='If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too')
Expand Down Expand Up @@ -316,13 +315,12 @@ def _generate_finished_moves(self):
'product_uom_qty': self.product_qty,
'location_id': self.product_id.property_stock_production.id,
'location_dest_id': self.location_dest_id.id,
'move_dest_ids': self.procurement_ids and [(4, p) for p in self.procurement_ids.mapped('move_dest_id').ids] or False,
'procurement_id': self.procurement_ids and self.procurement_ids[0].id or False,
'company_id': self.company_id.id,
'production_id': self.id,
'origin': self.name,
'group_id': self.procurement_group_id.id,
'propagate': self.propagate,
'move_dest_ids': [(4, x.id) for x in self.move_dest_ids],
})
move.action_confirm()
return move
Expand Down Expand Up @@ -493,21 +491,13 @@ def action_cancel(self):
orders in exception """
if any(workorder.state == 'progress' for workorder in self.mapped('workorder_ids')):
raise UserError(_('You can not cancel production order, a work order is still in progress.'))
ProcurementOrder = self.env['procurement.order']
for production in self:
production.workorder_ids.filtered(lambda x: x.state != 'cancel').action_cancel()

finish_moves = production.move_finished_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
raw_moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
(finish_moves | raw_moves).action_cancel()

procurements = ProcurementOrder.search([('move_dest_id', 'in', (finish_moves | raw_moves).ids)])
if procurements:
procurements.cancel()

# Put relatfinish_to_canceled procurements in exception -> I agree
ProcurementOrder.search([('production_id', 'in', self.ids)]).write({'state': 'exception'})

self.write({'state': 'cancel'})
return True

Expand Down Expand Up @@ -547,7 +537,6 @@ def button_mark_done(self):
moves_to_cancel = (self.move_raw_ids | self.move_finished_ids).filtered(lambda x: x.state not in ('done', 'cancel'))
moves_to_cancel.action_cancel()
self.write({'state': 'done', 'date_finished': fields.Datetime.now()})
self.env["procurement.order"].search([('production_id', 'in', self.ids)]).check()
return self.write({'state': 'done'})

@api.multi
Expand Down
132 changes: 54 additions & 78 deletions addons/mrp/models/procurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,98 +4,74 @@
from dateutil.relativedelta import relativedelta

from odoo import api, fields, models, _
from odoo.exceptions import UserError


class ProcurementRule(models.Model):
_inherit = 'procurement.rule'
action = fields.Selection(selection_add=[('manufacture', 'Manufacture')])

@api.model
def _get_action(self):
return [('manufacture', _('Manufacture'))] + super(ProcurementRule, self)._get_action()


class ProcurementOrder(models.Model):
_inherit = 'procurement.order'

bom_id = fields.Many2one('mrp.bom', 'BoM', index=True, ondelete='cascade')
production_id = fields.Many2one('mrp.production', 'Manufacturing Order')

@api.multi
def propagate_cancels(self):
cancel_man_orders = self.filtered(lambda procurement: procurement.rule_id.action == 'manufacture' and procurement.production_id).mapped('production_id')
if cancel_man_orders:
cancel_man_orders.action_cancel()
return super(ProcurementOrder, self).propagate_cancels()
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'

@api.multi
def _run(self):
self.ensure_one()
if self.rule_id.action == 'manufacture':
# make a manufacturing order for the procurement
return self.make_mo()[self.id]
return super(ProcurementOrder, self)._run()
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 _check(self):
return self.production_id.state == 'done' or super(ProcurementOrder, self)._check()

@api.multi
def _get_matching_bom(self):
""" Finds the bill of material for the product from procurement order. """
if self.bom_id:
return self.bom_id
def _get_matching_bom(self, values, rule):
if values.get('bom_id', False):
return values['bom_id']
return self.env['mrp.bom'].with_context(
company_id=self.company_id.id, force_company=self.company_id.id
)._bom_find(product=self.product_id, picking_type=self.rule_id.picking_type_id) # TDE FIXME: context bullshit
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

def _get_date_planned(self):
format_date_planned = fields.Datetime.from_string(self.date_planned)
date_planned = format_date_planned - relativedelta(days=self.product_id.produce_delay or 0.0)
date_planned = date_planned - relativedelta(days=self.company_id.manufacturing_lead)
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, bom):
def _prepare_mo_vals(self, values, rule, bom):
return {
'origin': self.origin,
'product_id': self.product_id.id,
'product_qty': self.product_qty,
'product_uom_id': self.product_uom.id,
'location_src_id': self.rule_id.location_src_id.id or self.location_id.id,
'location_dest_id': self.location_id.id,
'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,
'bom_id': bom.id,
'date_planned_start': fields.Datetime.to_string(self._get_date_planned()),
'date_planned_finished': self.date_planned,
'procurement_group_id': self.group_id.id,
'propagate': self.rule_id.propagate,
'picking_type_id': self.rule_id.picking_type_id.id or self.warehouse_id.manu_type_id.id,
'company_id': self.company_id.id,
'procurement_ids': [(6, 0, [self.id])],
'date_planned_start': fields.Datetime.to_string(self._get_date_planned(values, rule)),
'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,
'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,
}

@api.multi
def make_mo(self):
""" Create production orders from procurements """
res = {}
Production = self.env['mrp.production']
for procurement in self:
ProductionSudo = Production.sudo().with_context(force_company=procurement.company_id.id)
bom = procurement._get_matching_bom()
if bom:
# 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(procurement._prepare_mo_vals(bom))
origin_production = procurement.move_dest_id.raw_material_production_id
orderpoint = procurement.orderpoint_id
if orderpoint:
production.message_post_with_view('mail.message_origin_link',
values={'self': production, 'origin': orderpoint},
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)

res[procurement.id] = production.id
else:
res[procurement.id] = False
procurement.message_post(body=_("No BoM exists for this product!"))
return res
2 changes: 1 addition & 1 deletion addons/mrp/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def write(self, vals):
class StockMove(models.Model):
_inherit = 'stock.move'

created_production_id = fields.Many2one('mrp.production', 'Created Production Order')
production_id = fields.Many2one(
'mrp.production', 'Production Order for finished products')
raw_material_production_id = fields.Many2one(
Expand Down Expand Up @@ -178,7 +179,6 @@ def _generate_move_phantom(self, bom_line, quantity):
'product_uom_qty': quantity,
'state': 'draft', # will be confirmed below
'name': self.name,
'procurement_id': self.procurement_id.id,
})
return self.env['stock.move']

Expand Down
8 changes: 0 additions & 8 deletions addons/mrp/report/mrp_production_templates.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,6 @@
<span t-esc="time.strftime('%Y-%m-%d')"
t-options='{"widget": "date"}'/>
</div>
<div class="col-xs-3" t-if="'sale_ref' in o">
<strong>Partner Ref:</strong><br/>
<span t-esc="'sale_ref' in o and o.sale_ref"/>
</div>
<div class="col-xs-3" t-if="'sale_name' in o">
<strong>SO Number:</strong><br/>
<span t-esc="'sale_name' in o and o.sale_name"/>
</div>
</div>

<div t-if="o.workorder_ids">
Expand Down
2 changes: 0 additions & 2 deletions addons/mrp/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ access_mrp_routing_workcenter,mrp.routing.workcenter,model_mrp_routing_workcente
access_mrp_bom,mrp.bom,model_mrp_bom,group_mrp_user,1,0,0,0
access_mrp_bom_line,mrp.bom.line,model_mrp_bom_line,group_mrp_user,1,0,0,0
access_mrp_production,mrp.production user,model_mrp_production,mrp.group_mrp_user,1,1,1,1
access_procurement,procurement.order,model_procurement_order,mrp.group_mrp_user,1,1,1,1
access_mrp_workcenter_manager,mrp.workcenter.manager,model_mrp_workcenter,mrp.group_mrp_manager,1,1,1,1
access_mrp_resource_manager,access_mrp_resource manager,resource.model_resource_resource,mrp.group_mrp_manager,1,1,1,1
access_mrp_routing_manager,mrp.routing.manager,model_mrp_routing,mrp.group_mrp_manager,1,1,1,1
Expand All @@ -19,7 +18,6 @@ access_stock_location_mrp_worker,stock.location mrp_worker,stock.model_stock_loc
access_stock_move_mrp_worker,stock.move mrp_worker,stock.model_stock_move,mrp.group_mrp_user,1,1,1,0
access_stock_picking_mrp_worker,stock.picking mrp_worker,stock.model_stock_picking,mrp.group_mrp_user,1,1,1,1
access_stock_warehouse,stock.warehouse mrp_worker,stock.model_stock_warehouse,mrp.group_mrp_user,1,0,0,0
access_procurement_user,procurement.order.user,model_procurement_order,base.group_user,1,1,1,1
access_mrp_production_stock_worker,mrp.production stock_worker,model_mrp_production,stock.group_stock_user,1,0,0,0
access_product_product_user,product.product user,product.model_product_product,mrp.group_mrp_user,1,0,0,0
access_product_template_user,product.template user,product.model_product_template,mrp.group_mrp_user,1,0,0,0
Expand Down
34 changes: 14 additions & 20 deletions addons/mrp/tests/test_procurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo.addons.mrp.tests.common import TestMrpCommon

from odoo.exceptions import UserError

class TestProcurement(TestMrpCommon):

Expand Down Expand Up @@ -38,11 +38,11 @@ def test_procurement(self):
self.assertEqual(production_product_6.state, 'confirmed', 'Production order should be for Confirmed state')

# Check procurement for product 4 created or not.
procurement = self.env['procurement.order'].search([('group_id', '=', production_product_6.procurement_group_id.id), ('product_id', '=', self.product_4.id)])
self.assertTrue(procurement, 'No procurement are created !')
self.assertEqual(procurement.state, 'running', 'Procurement order should be in state running')
# Check it created a purchase order

produce_product_4 = procurement.production_id
move_raw_product4 = production_product_6.move_raw_ids.filtered(lambda x: x.product_id == self.product_4)
produce_product_4 = self.env['mrp.production'].search([('product_id', '=', self.product_4.id),
('move_dest_ids', '=', move_raw_product4[0].id)])
# produce product
self.assertEqual(produce_product_4.availability, 'waiting', "Consume material not available")

Expand Down Expand Up @@ -76,7 +76,6 @@ def test_procurement(self):
# Check procurement and Production state for product 4.
produce_product_4.button_mark_done()
self.assertEqual(produce_product_4.state, 'done', 'Production order should be in state done')
self.assertEqual(procurement.state, 'done', 'Procurement order should be in state done')

# Produce product 6
# ------------------
Expand Down Expand Up @@ -126,17 +125,12 @@ def test_procurement_2(self):
mto_route.product_categ_selectable = True
all_categ_id.write({'route_ids': [(6, 0, [mto_route.id])]})

# create MO
production_product_4 = self.env['mrp.production'].create({
'name': 'MO/Test-00002',
'product_id': self.product_4.id,
'product_qty': 1,
'bom_id': self.bom_1.id,
'product_uom_id': self.product_4.uom_id.id,
})

# check that procurement are correctly created
procurement = self.env['procurement.order'].search(
[('group_id', '=', production_product_4.procurement_group_id.id),
('product_id', 'in', self.bom_1.bom_line_ids.mapped('product_id.id'))])
self.assertEqual(len(procurement), 2)
# create MO, but check it raises error as components are in make to order and not everyone has
with self.assertRaises(UserError):
production_product_4 = self.env['mrp.production'].create({
'name': 'MO/Test-00002',
'product_id': self.product_4.id,
'product_qty': 1,
'bom_id': self.bom_1.id,
'product_uom_id': self.product_4.uom_id.id,
})
17 changes: 2 additions & 15 deletions addons/mrp/views/procurement_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,13 @@
<record id="view_procurement_rule_form_stock_inherit_mrp" model="ir.ui.view">
<field name="name">procurement.rule.form.stock.inherit.mrp</field>
<field name="model">procurement.rule</field>
<field name="inherit_id" ref="stock.view_procurement_rule_form_stock_inherit"/>
<field name="inherit_id" ref="stock.view_procurement_rule_form"/>
<field name="arch" type="xml">
<field name="location_src_id" position="attributes">
<attribute name="attrs">{'required': [('action', '=', 'move')], 'invisible': [('action', 'not in', ['move', 'manufacture'])]}</attribute>
</field>
</field>
</record>

<record id="view_procurement_form_inherit" model="ir.ui.view">
<field name="name">procurement.order.form.inherit</field>
<field name="model">procurement.order</field>
<field name="inherit_id" ref="procurement.procurement_form_view"/>
<field name="arch" type="xml">
<data>
<field name="origin" position="before">
<field name="bom_id" domain="[('product_id','=',product_id)]"/>
<field name="production_id" attrs="{'invisible': [('production_id','=',False)]}"/>
</field>
</data>
</field>
</record>
</data>
</odoo>
</odoo>
Loading

0 comments on commit 0e4f3bb

Please sign in to comment.