[IMP] Yet another 1000 lines removed

bzr revid: fp@tinyerp.com-20130630222646-wrpyd1o89md8gknx
This commit is contained in:
Fabien Pinckaers 2013-07-01 00:26:46 +02:00
parent e61887ffae
commit 8f6c3e14b9
37 changed files with 577 additions and 1508 deletions

View File

@ -29,6 +29,34 @@ from openerp.tools import float_compare
from openerp.tools.translate import _ from openerp.tools.translate import _
from openerp import tools from openerp import tools
class mrp_property_group(osv.osv):
"""
Group of mrp properties.
"""
_name = 'mrp.property.group'
_description = 'Property Group'
_columns = {
'name': fields.char('Property Group', size=64, required=True),
'description': fields.text('Description'),
}
class mrp_property(osv.osv):
"""
Properties of mrp.
"""
_name = 'mrp.property'
_description = 'Property'
_columns = {
'name': fields.char('Name', size=64, required=True),
'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
'description': fields.text('Description'),
}
_defaults = {
'composition': lambda *a: 'min',
}
#---------------------------------------------------------- #----------------------------------------------------------
# Work Centers # Work Centers
#---------------------------------------------------------- #----------------------------------------------------------

View File

@ -78,3 +78,7 @@ access_resource_calendar_manufacturinguser,resource.calendar manufacturing.user,
access_account_journal_mrp_manager,account.journal mrp manager,account.model_account_journal,mrp.group_mrp_manager,1,0,0,0 access_account_journal_mrp_manager,account.journal mrp manager,account.model_account_journal,mrp.group_mrp_manager,1,0,0,0
access_purchase_order_stock_user,purchase.order stock user,purchase.model_purchase_order,stock.group_stock_user,1,1,1,0 access_purchase_order_stock_user,purchase.order stock user,purchase.model_purchase_order,stock.group_stock_user,1,1,1,0
access_mrp_bom_purchase_manager,mrp.bom,model_mrp_bom,purchase.group_purchase_manager,1,0,0,0 access_mrp_bom_purchase_manager,mrp.bom,model_mrp_bom,purchase.group_purchase_manager,1,0,0,0
access_mrp_property_group,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
access_mrp_property,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
access_mrp_property_group,mrp.property.group,model_mrp_property_group,base.group_user,1,0,0,0
access_mrp_property,mrp.property,model_mrp_property,base.group_user,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
78 access_account_journal_mrp_manager account.journal mrp manager account.model_account_journal mrp.group_mrp_manager 1 0 0 0
79 access_purchase_order_stock_user purchase.order stock user purchase.model_purchase_order stock.group_stock_user 1 1 1 0
80 access_mrp_bom_purchase_manager mrp.bom model_mrp_bom purchase.group_purchase_manager 1 0 0 0
81 access_mrp_property_group mrp.property.group model_mrp_property_group stock.group_stock_manager 1 1 1 1
82 access_mrp_property mrp.property model_mrp_property stock.group_stock_manager 1 1 1 1
83 access_mrp_property_group mrp.property.group model_mrp_property_group base.group_user 1 0 0 0
84 access_mrp_property mrp.property model_mrp_property base.group_user 1 0 0 0

View File

@ -21,7 +21,5 @@
import procurement import procurement
import wizard import wizard
import schedulers
import company import company
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -26,7 +26,7 @@
'author' : 'OpenERP SA', 'author' : 'OpenERP SA',
'website' : 'http://www.openerp.com', 'website' : 'http://www.openerp.com',
'category' : 'Hidden/Dependency', 'category' : 'Hidden/Dependency',
'depends' : ['base','process', 'product', 'stock'], 'depends' : ['base','process', 'product'],
'description': """ 'description': """
This is the module for computing Procurements. This is the module for computing Procurements.
============================================== ==============================================
@ -47,20 +47,13 @@ depending on the product's configuration.
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'security/procurement_security.xml', 'security/procurement_security.xml',
'procurement_data.xml', 'procurement_data.xml',
'wizard/make_procurement_view.xml',
'wizard/mrp_procurement_view.xml',
'wizard/orderpoint_procurement_view.xml',
'wizard/schedulers_all_view.xml', 'wizard/schedulers_all_view.xml',
'procurement_view.xml', 'procurement_view.xml',
'procurement_workflow.xml',
'process/procurement_process.xml',
'company_view.xml', 'company_view.xml',
'board_mrp_procurement_view.xml',
], ],
'demo': ['stock_orderpoint.xml','procurement_demo.xml'], 'demo': [],
'test': ['test/procurement.yml'], 'test': ['test/procurement.yml'],
'installable': True, 'installable': True,
'auto_install': True, 'auto_install': True,
'images': ['images/compute_schedulers.jpeg','images/config_companies_sched.jpeg', 'images/minimum_stock_rules.jpeg'], 'images': ['images/compute_schedulers.jpeg','images/config_companies_sched.jpeg', 'images/minimum_stock_rules.jpeg'],
} }
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="procurement_action_board" model="ir.actions.act_window">
<field name="name">Procurement Exceptions</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">procurement.order</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('state','=','exception')]</field>
<field name="view_id" ref="procurement.procurement_tree_view_board"/>
</record>
<record id="board_mrp_procurement_form" model="ir.ui.view">
<field name="name">board.mrp.procurement.form</field>
<field name="model">board.board</field>
<field name="inherit_id" ref="stock.board_warehouse_form"/>
<field name="arch" type="xml">
<xpath expr="//column" position="inside">
<action name="%(procurement_action_board)d" string="Procurements in Exception"/>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -1,42 +0,0 @@
<?xml version="1.0" ?>
<openerp>
<data>
<!--
Process Node from where the Procurement flow starts
-->
<record id="process_process_procurementprocess0" model="process.process">
<field eval="&quot;&quot;&quot;Procurement&quot;&quot;&quot;" name="name"/>
<field name="model_id" ref="procurement.model_procurement_order"/>
<field eval="1" name="active"/>
</record>
<record id="process_node_procureproducts0" model="process.node">
<field name="menu_id" ref="menu_stock_procurement_action"/>
<field name="model_id" ref="procurement.model_procurement_order"/>
<field eval="&quot;&quot;&quot;state&quot;&quot;&quot;" name="kind"/>
<field eval="&quot;&quot;&quot;The way to procurement depends on the product type.&quot;&quot;&quot;" name="note"/>
<field eval="&quot;&quot;&quot;Procure Products&quot;&quot;&quot;" name="name"/>
<field name="process_id" ref="procurement.process_process_procurementprocess0"/>
<field eval="&quot;&quot;&quot;object.state in ('draft', 'confirmed', 'cancel', 'exception', 'running', 'done', 'waiting')&quot;&quot;&quot;" name="model_states"/>
<field eval="0" name="flow_start"/>
</record>
<record id="process_process_serviceproductprocess0" model="process.process">
<field eval="&quot;&quot;&quot;Service&quot;&quot;&quot;" name="name"/>
<field name="model_id" ref="procurement.model_procurement_order"/>
<field eval="1" name="active"/>
</record>
<record id="process_node_serviceonorder0" model="process.node">
<field name="menu_id" ref="menu_stock_procurement_action"/>
<field name="model_id" ref="procurement.model_procurement_order"/>
<field eval="&quot;&quot;&quot;state&quot;&quot;&quot;" name="kind"/>
<field eval="&quot;&quot;&quot;Assignment from Production or Purchase Order.&quot;&quot;&quot;" name="note"/>
<field eval="&quot;&quot;&quot;Make to Order&quot;&quot;&quot;" name="name"/>
<field name="process_id" ref="procurement.process_process_serviceproductprocess0"/>
<field eval="&quot;&quot;&quot;object.state in ('draft', 'confirmed', 'cancel', 'exception', 'running', 'done', 'waiting')&quot;&quot;&quot;" name="model_states"/>
<field eval="0" name="flow_start"/>
</record>
</data>
</openerp>

View File

@ -19,62 +19,14 @@
# #
############################################################################## ##############################################################################
from operator import attrgetter
import time import time
from datetime import datetime
from dateutil.relativedelta import relativedelta
from openerp.osv import fields, osv from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp import netsvc
import openerp.addons.decimal_precision as dp import openerp.addons.decimal_precision as dp
# Procurement
# ------------------------------------------------------------------
#
# Produce, Buy or Find products and place a move
# then wizard for picking lists & move
#
class mrp_property_group(osv.osv):
"""
Group of mrp properties.
"""
_name = 'mrp.property.group'
_description = 'Property Group'
_columns = {
'name': fields.char('Property Group', size=64, required=True),
'description': fields.text('Description'),
}
class mrp_property(osv.osv):
"""
Properties of mrp.
"""
_name = 'mrp.property'
_description = 'Property'
_columns = {
'name': fields.char('Name', size=64, required=True),
'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
'description': fields.text('Description'),
}
_defaults = {
'composition': lambda *a: 'min',
}
class StockMove(osv.osv):
_inherit = 'stock.move'
_columns= {
'procurements': fields.one2many('procurement.order', 'move_id', 'Procurements'),
'group_id':fields.many2one('procurement.group', 'Move Group'),
}
def copy_data(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
default['procurements'] = []
return super(StockMove, self).copy_data(cr, uid, id, default, context=context)
class procurement_group(osv.osv): class procurement_group(osv.osv):
''' '''
The procurement requirement class is used to group products together The procurement requirement class is used to group products together
@ -108,6 +60,21 @@ class procurement_group(osv.osv):
'name': lambda self,cr,uid,c: self.pool.get('ir.sequence').get(cr,uid,'procurement.group') or '' 'name': lambda self,cr,uid,c: self.pool.get('ir.sequence').get(cr,uid,'procurement.group') or ''
} }
class procurement_rule(osv.osv):
'''
A rule describe what a procurement should do; produce, buy, move, ...
'''
_name = 'procurement.rule'
_description = "Procurement Rule"
_columns = {
'name': fields.char('Name', required=True,
help="This field will fill the packing origin and the name of its moves"),
'group_id': fields.many2one('procurement.group', 'Procurement Group'),
'action': fields.selection(selection=lambda s, c, u, ctx: s._get_action(c, u, context=ctx),
string='Action', required=True)
}
def _get_action(self, cr, uid, context=None):
return []
class procurement_order(osv.osv): class procurement_order(osv.osv):
@ -121,61 +88,47 @@ class procurement_order(osv.osv):
_log_create = False _log_create = False
_columns = { _columns = {
'name': fields.text('Description', required=True), 'name': fields.text('Description', required=True),
'origin': fields.char('Source Document', size=64, 'origin': fields.char('Source Document', size=64,
help="Reference of the document that created this Procurement.\n" help="Reference of the document that created this Procurement.\n"
"This is automatically completed by OpenERP."), "This is automatically completed by OpenERP."),
'company_id': fields.many2one('res.company','Company',required=True),
# These two fields are used for shceduling
'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True, select=True), 'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True, select=True),
'date_planned': fields.datetime('Scheduled date', required=True, select=True), 'date_planned': fields.datetime('Scheduled date', required=True, select=True),
'date_close': fields.datetime('Date Closed'),
'group_id':fields.many2one('procurement.group', 'Procurement Requisition'),
'rule_id': fields.many2one('procurement.rule', 'Rule'),
'product_id': fields.many2one('product.product', 'Product', required=True, states={'draft':[('readonly',False)]}, readonly=True), 'product_id': fields.many2one('product.product', 'Product', required=True, states={'draft':[('readonly',False)]}, readonly=True),
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, states={'draft':[('readonly',False)]}, readonly=True), 'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, states={'draft':[('readonly',False)]}, readonly=True),
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, states={'draft':[('readonly',False)]}, readonly=True), 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, states={'draft':[('readonly',False)]}, readonly=True),
'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True), 'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True), 'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
'close_move': fields.boolean('Close Move at end'),
'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'), 'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')],
'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True), 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \ readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
" a make to order method."), " a make to order method."),
'note': fields.text('Note'),
'message': fields.text('Latest error', help="Exception occurred while computing procurement orders."),
'state': fields.selection([ 'state': fields.selection([
('draft','Draft'),
('cancel','Cancelled'), ('cancel','Cancelled'),
('confirmed','Confirmed'), ('confirmed','Confirmed'),
('exception','Exception'), ('exception','Exception'),
('running','Running'), ('running','Running'),
('ready','Ready'), ('done','Done')
('done','Done'), ], 'Status', required=True, track_visibility='onchange'),
('waiting','Waiting')], 'Status', required=True, track_visibility='onchange',
help='When a procurement is created the status is set to \'Draft\'.\n If the procurement is confirmed, the status is set to \'Confirmed\'.\
\nAfter confirming the status is set to \'Running\'.\n If any exception arises in the order then the status is set to \'Exception\'.\n Once the exception is removed the status becomes \'Ready\'.\n It is in \'Waiting\'. status when the procurement is waiting for another one to finish.'),
'note': fields.text('Note'),
'company_id': fields.many2one('res.company','Company',required=True),
'group_id':fields.many2one('procurement.group', 'Procurement Requisition'),
} }
_defaults = { _defaults = {
'state': 'draft', 'state': 'confirmed',
'priority': '1', 'priority': '1',
'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'close_move': 0,
'procure_method': 'make_to_order', 'procure_method': 'make_to_order',
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'procurement.order', context=c) 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'procurement.order', context=c)
} }
def unlink(self, cr, uid, ids, context=None):
procurements = self.read(cr, uid, ids, ['state'], context=context)
unlink_ids = []
for s in procurements:
if s['state'] in ['draft','cancel']:
unlink_ids.append(s['id'])
else:
raise osv.except_osv(_('Invalid Action!'),
_('Cannot delete Procurement Order(s) which are in %s state.') % \
s['state'])
return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
def onchange_product_id(self, cr, uid, ids, product_id, context=None): def onchange_product_id(self, cr, uid, ids, product_id, context=None):
""" Finds UoM and UoS of changed product. """ Finds UoM and UoS of changed product.
@param product_id: Changed id of product. @param product_id: Changed id of product.
@ -190,455 +143,83 @@ class procurement_order(osv.osv):
return {'value': v} return {'value': v}
return {} return {}
def is_product(self, cr, uid, ids, context=None): def run(self, cr, uid, ids, context=None):
""" Checks product type to decide which transition of the workflow to follow. for procurement in self.browse(cr, uid, ids, context=context or {}):
@return: True if all product ids received in argument are of type 'product' or 'consummable'. False if any is of type 'service' if procurement.procure_method=='make_to_order':
""" result = self._run(cr, uid, procurement.id, context=context or {})
return all(proc.product_id.type in ('product', 'consu') for proc in self.browse(cr, uid, ids, context=context))
def check_move_cancel(self, cr, uid, ids, context=None):
""" Checks if move is cancelled or not.
@return: True or False.
"""
return all(procurement.move_id.state == 'cancel' for procurement in self.browse(cr, uid, ids, context=context))
#This Function is create to avoid a server side Error Like 'ERROR:tests.mrp:name 'check_move' is not defined'
def check_move(self, cr, uid, ids, context=None):
pass
def check_move_done(self, cr, uid, ids, context=None):
""" Checks if move is done or not.
@return: True or False.
"""
return all(proc.product_id.type == 'service' or (proc.move_id and proc.move_id.state == 'done') \
for proc in self.browse(cr, uid, ids, context=context))
#
# This method may be overrided by objects that override procurement.order
# for computing their own purpose
#
def _quantity_compute_get(self, cr, uid, proc, context=None):
""" Finds sold quantity of product.
@param proc: Current procurement.
@return: Quantity or False.
"""
if proc.product_id.type == 'product' and proc.move_id:
if proc.move_id.product_uos:
return proc.move_id.product_uos_qty
return False
def _uom_compute_get(self, cr, uid, proc, context=None):
""" Finds UoS if product is Stockable Product.
@param proc: Current procurement.
@return: UoS or False.
"""
if proc.product_id.type == 'product' and proc.move_id:
if proc.move_id.product_uos:
return proc.move_id.product_uos.id
return False
#
# Return the quantity of product shipped/produced/served, which may be
# different from the planned quantity
#
def quantity_get(self, cr, uid, id, context=None):
""" Finds quantity of product used in procurement.
@return: Quantity of product.
"""
proc = self.browse(cr, uid, id, context=context)
result = self._quantity_compute_get(cr, uid, proc, context=context)
if not result:
result = proc.product_qty
return result
def uom_get(self, cr, uid, id, context=None):
""" Finds UoM of product used in procurement.
@return: UoM of product.
"""
proc = self.browse(cr, uid, id, context=context)
result = self._uom_compute_get(cr, uid, proc, context=context)
if not result:
result = proc.product_uom.id
return result
def check_waiting(self, cr, uid, ids, context=None):
""" Checks state of move.
@return: True or False
"""
for procurement in self.browse(cr, uid, ids, context=context):
if procurement.move_id and procurement.move_id.state == 'auto':
return True
return False
def check_produce_service(self, cr, uid, procurement, context=None):
""" Depicts the capacity of the procurement workflow to deal with production of services.
By default, it's False. Overwritten by project_mrp module.
"""
return False
def check_produce_product(self, cr, uid, procurement, context=None):
""" Depicts the capacity of the procurement workflow to deal with production of products.
By default, it's False. Overwritten by mrp module.
"""
return False
def check_make_to_stock(self, cr, uid, ids, context=None):
""" Checks product type.
@return: True or False
"""
ok = True
for procurement in self.browse(cr, uid, ids, context=context):
if procurement.product_id.type == 'service':
ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
else: else:
ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context) result = True
return ok if result:
self.write(cr, uid, [procurement.id], {'state': 'running'}, context=context)
def check_produce(self, cr, uid, ids, context=None):
""" Checks product type.
@return: True or False
"""
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
for procurement in self.browse(cr, uid, ids, context=context):
product = procurement.product_id
#TOFIX: if product type is 'service' but supply_method is 'buy'.
if product.supply_method <> 'produce':
return False
if product.type=='service':
res = self.check_produce_service(cr, uid, procurement, context)
else: else:
res = self.check_produce_product(cr, uid, procurement, context) self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context)
if not res:
return False
return True return True
def check_buy(self, cr, uid, ids): def check(self, cr, uid, ids, context=None):
""" Depicts the capacity of the procurement workflow to manage the supply_method == 'buy'. done = []
By default, it's False. Overwritten by purchase module. for procurement in self.browse(cr, uid, ids, context=context or {}):
""" result = self._check(cr, uid, procurement.id, context=context or {})
return False if result:
self.write(cr, uid, [procurement.id], {'state': 'done'}, context=context)
done.append(procurement.id)
return done
def check_conditions_confirm2wait(self, cr, uid, ids): #
""" condition on the transition to go from 'confirm' activity to 'confirm_wait' activity """ # Method to overwrite in different procurement modules
return not self.test_cancel(cr, uid, ids) #
def _run(self, cr, uid, procurement, context=None):
def test_cancel(self, cr, uid, ids):
""" Tests whether state of move is cancelled or not.
@return: True or False
"""
for record in self.browse(cr, uid, ids):
if record.move_id and record.move_id.state == 'cancel':
return True
return False
#Initialize get_phantom_bom_id method as it is raising an error from yml of mrp_jit
#when one install first mrp and after that, mrp_jit. get_phantom_bom_id defined in mrp module
#which is not dependent for mrp_jit.
def get_phantom_bom_id(self, cr, uid, ids, context=None):
return False
def action_confirm(self, cr, uid, ids, context=None):
""" Confirms procurement and writes exception message if any.
@return: True
"""
move_obj = self.pool.get('stock.move')
for procurement in self.browse(cr, uid, ids, context=context):
if procurement.product_qty <= 0.00:
raise osv.except_osv(_('Data Insufficient!'),
_('Please check the quantity in procurement order(s) for the product "%s", it should not be 0 or less!' % procurement.product_id.name))
if procurement.product_id.type in ('product', 'consu'):
if not procurement.move_id:
source = procurement.location_id.id
if procurement.procure_method == 'make_to_order':
source = procurement.product_id.property_stock_procurement.id
id = move_obj.create(cr, uid, {
'name': procurement.name,
'location_id': source,
'location_dest_id': procurement.location_id.id,
'product_id': procurement.product_id.id,
'product_qty': procurement.product_qty,
'product_uom': procurement.product_uom.id,
'date_expected': procurement.date_planned,
'state': 'draft',
'company_id': procurement.company_id.id,
'auto_validate': True,
})
move_obj.action_confirm(cr, uid, [id], context=context)
self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move': 1})
self.write(cr, uid, ids, {'state': 'confirmed', 'message': ''})
return True return True
def action_move_assigned(self, cr, uid, ids, context=None): def _check(self, cr, uid, procurement, context=None):
""" Changes procurement state to Running and writes message.
@return: True
"""
message = _('Products reserved from stock.')
self.write(cr, uid, ids, {'state': 'running',
'message': message}, context=context)
self.message_post(cr, uid, ids, body=message, context=context)
return True return True
def _check_make_to_stock_service(self, cr, uid, procurement, context=None): #
""" # Scheduler
This method may be overrided by objects that override procurement.order #
for computing their own purpose def run_scheduler(self, cr, uid, use_new_cursor=False, context=None):
@return: True""" '''
return True Call the scheduler to check the procurement order
def _check_make_to_stock_product(self, cr, uid, procurement, context=None): @param self: The object pointer
""" Checks procurement move state. @param cr: The current row, from the database cursor,
@param procurement: Current procurement. @param uid: The current user ID for security checks
@return: True or move id. @param ids: List of selected IDs
""" @param use_new_cursor: False or the dbname
ok = True @param context: A standard dictionary for contextual values
if procurement.move_id: @return: Dictionary of values
message = False '''
id = procurement.move_id.id
if not (procurement.move_id.state in ('done','assigned','cancel')):
ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
order_point_id = self.pool.get('stock.warehouse.orderpoint').search(cr, uid, [('product_id', '=', procurement.product_id.id)], context=context)
if not order_point_id and not ok:
message = _("Not enough stock and no minimum orderpoint rule defined.")
elif not ok:
message = _("Not enough stock.")
if message:
message = _("Procurement '%s' is in exception: ") % (procurement.name) + message
#temporary context passed in write to prevent an infinite loop
ctx_wkf = dict(context or {})
ctx_wkf['workflow.trg_write.%s' % self._name] = False
self.write(cr, uid, [procurement.id], {'message': message},context=ctx_wkf)
self.message_post(cr, uid, [procurement.id], body=message, context=context)
return ok
def step_workflow(self, cr, uid, ids, context=None):
""" Don't trigger workflow for the element specified in trigger """
wkf_op_key = 'workflow.trg_write.%s' % self._name
if context and not context.get(wkf_op_key, True):
# make sure we don't have a trigger loop while processing triggers
return
return super(procurement_order, self).step_workflow(cr, uid, ids, context=context)
def action_produce_assign_service(self, cr, uid, ids, context=None):
""" Changes procurement state to Running.
@return: True
"""
for procurement in self.browse(cr, uid, ids, context=context):
self.write(cr, uid, [procurement.id], {'state': 'running'})
return True
def action_produce_assign_product(self, cr, uid, ids, context=None):
""" This is action which call from workflow to assign production order to procurements
@return: True
"""
return 0
def action_po_assign(self, cr, uid, ids, context=None):
""" This is action which call from workflow to assign purchase order to procurements
@return: True
"""
return 0
# XXX action_cancel() should accept a context argument
def action_cancel(self, cr, uid, ids):
"""Cancel Procurements and either cancel or assign the related Stock Moves, depending on the procurement configuration.
@return: True
"""
to_assign = []
to_cancel = []
move_obj = self.pool.get('stock.move')
for proc in self.browse(cr, uid, ids):
if proc.close_move and proc.move_id:
if proc.move_id.state not in ('done', 'cancel'):
to_cancel.append(proc.move_id.id)
else:
if proc.move_id and proc.move_id.state == 'waiting':
to_assign.append(proc.move_id.id)
if len(to_cancel):
move_obj.action_cancel(cr, uid, to_cancel)
if len(to_assign):
move_obj.write(cr, uid, to_assign, {'state': 'assigned'})
self.write(cr, uid, ids, {'state': 'cancel'})
wf_service = netsvc.LocalService("workflow")
for id in ids:
wf_service.trg_trigger(uid, 'procurement.order', id, cr)
return True
def action_check_finished(self, cr, uid, ids):
return self.check_move_done(cr, uid, ids)
def action_check(self, cr, uid, ids):
""" Checks procurement move state whether assigned or done.
@return: True
"""
ok = False
for procurement in self.browse(cr, uid, ids):
if procurement.move_id and procurement.move_id.state == 'assigned' or procurement.move_id.state == 'done':
self.action_done(cr, uid, [procurement.id])
ok = True
return ok
def action_ready(self, cr, uid, ids):
""" Changes procurement state to Ready.
@return: True
"""
res = self.write(cr, uid, ids, {'state': 'ready'})
return res
def action_done(self, cr, uid, ids):
""" Changes procurement state to Done and writes Closed date.
@return: True
"""
move_obj = self.pool.get('stock.move')
for procurement in self.browse(cr, uid, ids):
if procurement.move_id:
if procurement.close_move and (procurement.move_id.state <> 'done'):
move_obj.action_done(cr, uid, [procurement.move_id.id])
res = self.write(cr, uid, ids, {'state': 'done', 'date_close': time.strftime('%Y-%m-%d')})
wf_service = netsvc.LocalService("workflow")
for id in ids:
wf_service.trg_trigger(uid, 'procurement.order', id, cr)
return res
class StockPicking(osv.osv):
_inherit = 'stock.picking'
def test_finished(self, cr, uid, ids):
res = super(StockPicking, self).test_finished(cr, uid, ids)
for picking in self.browse(cr, uid, ids):
for move in picking.move_lines:
if move.state == 'done' and move.procurements:
self.pool.get('procurement.order').signal_button_check(cr, uid, map(attrgetter('id'), move.procurements))
return res
class stock_warehouse_orderpoint(osv.osv):
"""
Defines Minimum stock rules.
"""
_name = "stock.warehouse.orderpoint"
_description = "Minimum Inventory Rule"
def _get_draft_procurements(self, cr, uid, ids, field_name, arg, context=None):
if context is None: if context is None:
context = {} context = {}
result = {} try:
procurement_obj = self.pool.get('procurement.order') if use_new_cursor:
for orderpoint in self.browse(cr, uid, ids, context=context): cr = openerp.registry(use_new_cursor).db.cursor()
procurement_ids = procurement_obj.search(cr, uid , [('state', '=', 'draft'), ('product_id', '=', orderpoint.product_id.id), ('location_id', '=', orderpoint.location_id.id)])
result[orderpoint.id] = procurement_ids
return result
def _check_product_uom(self, cr, uid, ids, context=None): company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
''' maxdate = (datetime.today() + relativedelta(days=company.schedule_range)).strftime('%Y-%m-%d %H:%M:%S')
Check if the UoM has the same category as the product standard UoM
'''
if not context:
context = {}
for rule in self.browse(cr, uid, ids, context=context):
if rule.product_id.uom_id.category_id.id != rule.product_uom.category_id.id:
return False
return True
_columns = { # Run confirmed procurements
'name': fields.char('Name', size=32, required=True), while True:
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the orderpoint without removing it."), ids = self.search(cr, uid, [('state', '=', 'confirmed'),('date_planned','<=', maxdate)], context=context)
'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True), if not ids: break
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, ondelete="cascade"), self.run(cr, uid, ids, context=context)
'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade"), if use_new_cursor:
'product_id': fields.many2one('product.product', 'Product', required=True, ondelete='cascade', domain=[('type','!=','service')]), cr.commit()
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
'product_min_qty': fields.float('Minimum Quantity', required=True,
help="When the virtual stock goes below the Min Quantity specified for this field, OpenERP generates "\
"a procurement to bring the forecasted quantity to the Max Quantity."),
'product_max_qty': fields.float('Maximum Quantity', required=True,
help="When the virtual stock goes below the Min Quantity, OpenERP generates "\
"a procurement to bring the forecasted quantity to the Quantity specified as Max Quantity."),
'qty_multiple': fields.integer('Qty Multiple', required=True,
help="The procurement quantity will be rounded up to this multiple."),
'procurement_id': fields.many2one('procurement.order', 'Latest procurement', ondelete="set null"),
'company_id': fields.many2one('res.company','Company',required=True),
'procurement_draft_ids': fields.function(_get_draft_procurements, type='many2many', relation="procurement.order", \
string="Related Procurement Orders",help="Draft procurement of the product and location of that orderpoint"),
}
_defaults = {
'active': lambda *a: 1,
'logic': lambda *a: 'max',
'qty_multiple': lambda *a: 1,
'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'stock.orderpoint') or '',
'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', context=c)
}
_sql_constraints = [
('qty_multiple_check', 'CHECK( qty_multiple > 0 )', 'Qty Multiple must be greater than zero.'),
]
_constraints = [
(_check_product_uom, 'You have to select a product unit of measure in the same category than the default unit of measure of the product', ['product_id', 'product_uom']),
]
def default_get(self, cr, uid, fields, context=None): # Check if running procurements are done
res = super(stock_warehouse_orderpoint, self).default_get(cr, uid, fields, context) offset = 0
# default 'warehouse_id' and 'location_id' while True:
if 'warehouse_id' not in res: ids = self.search(cr, uid, [('state', '=', 'running'),('date_planned','<=', maxdate)], offset=offset, context=context)
warehouse = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'warehouse0', context) if not ids: break
res['warehouse_id'] = warehouse.id done = self.check(cr, uid, ids, context=context)
if 'location_id' not in res: offset += len(ids) - len(done)
warehouse = self.pool.get('stock.warehouse').browse(cr, uid, res['warehouse_id'], context) if use_new_cursor and len(done):
res['location_id'] = warehouse.lot_stock_id.id cr.commit()
return res
def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None): finally:
""" Finds location id for changed warehouse. if use_new_cursor:
@param warehouse_id: Changed id of warehouse. try:
@return: Dictionary of values. cr.close()
""" except Exception:
if warehouse_id: pass
w = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
v = {'location_id': w.lot_stock_id.id}
return {'value': v}
return {} return {}
def onchange_product_id(self, cr, uid, ids, product_id, context=None):
""" Finds UoM for changed product.
@param product_id: Changed id of product.
@return: Dictionary of values.
"""
if product_id:
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
d = {'product_uom': [('category_id', '=', prod.uom_id.category_id.id)]}
v = {'product_uom': prod.uom_id.id}
return {'value': v, 'domain': d}
return {'domain': {'product_uom': []}}
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
default.update({
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.orderpoint') or '',
})
return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context=context)
class product_template(osv.osv):
_inherit="product.template"
_columns = {
'type': fields.selection([('product','Stockable Product'),('consu', 'Consumable'),('service','Service')], 'Product Type', required=True, help="Consumable: Will not imply stock management for this product. \nStockable product: Will imply stock management for this product."),
'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement Method', required=True, help="Make to Stock: When needed, the product is taken from the stock or we wait for replenishment. \nMake to Order: When needed, the product is purchased or produced."),
'supply_method': fields.selection([('produce','Manufacture'),('buy','Buy')], 'Supply Method', required=True, help="Manufacture: When procuring the product, a manufacturing order or a task will be generated, depending on the product type. \nBuy: When procuring the product, a purchase order will be generated."),
}
_defaults = {
'procure_method': 'make_to_stock',
'supply_method': 'buy',
}
class product_product(osv.osv):
_inherit="product.product"
_columns = {
'orderpoint_ids': fields.one2many('stock.warehouse.orderpoint', 'product_id', 'Minimum Stock Rules'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -29,18 +29,5 @@
<field name="number_increment">1</field> <field name="number_increment">1</field>
</record> </record>
<record id="sequence_mrp_op_type" model="ir.sequence.type">
<field name="name">Stock orderpoint</field>
<field name="code">stock.orderpoint</field>
</record>
<record id="sequence_mrp_op" model="ir.sequence">
<field name="name">Stock orderpoint</field>
<field name="code">stock.orderpoint</field>
<field name="prefix">OP/</field>
<field name="padding">5</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -18,37 +18,19 @@
<field name="product_uom" string="Unit of Measure"/> <field name="product_uom" string="Unit of Measure"/>
<field name="procure_method"/> <field name="procure_method"/>
<field name="state"/> <field name="state"/>
<field name="name" invisible="1"/>
<field name="message"/>
</tree>
</field>
</record>
<record id="procurement_tree_view_board" model="ir.ui.view">
<field name="name">procurement.order.tree.board</field>
<field name="model">procurement.order</field>
<field eval="20" name="priority"/>
<field name="arch" type="xml">
<tree string="Procurement Lines" colors="red:date_planned&lt;current_date and state == 'exception';black:state=='running';darkgreen:state=='confirmed';gray:state in ['done','cancel'];blue:state == 'ready'">
<field name="date_planned" widget="date"/>
<field name="origin"/>
<field name="product_id"/>
<field name="product_qty"/>
<field name="product_uom" string="Unit of Measure"/>
<field name="state" invisible = "1"/>
<field name="message"/>
</tree> </tree>
</field> </field>
</record> </record>
<record id="procurement_form_view" model="ir.ui.view"> <record id="procurement_form_view" model="ir.ui.view">
<field name="name">procurement.order.form</field> <field name="name">procurement.order.form</field>
<field name="model">procurement.order</field> <field name="model">procurement.order</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Procurement" version="7.0"> <form string="Procurement" version="7.0">
<header> <header>
<button name="button_confirm" states="draft" string="Confirm" class="oe_highlight"/> <button name="run" states="confirmed,exception" string="Run Procurement" class="oe_highlight"/>
<button name="button_check" states="confirmed" string="Run Procurement" class="oe_highlight"/> <button name="check" states="running" string="Check Procurement" class="oe_highlight"/>
<button name="button_restart" states="exception" string="Retry" class="oe_highlight"/> <button name="cancel" states="exception,confirmed,running" string="Cancel Procurement"/>
<button name="button_cancel" states="draft,exception,waiting" string="Cancel Procurement"/>
<field name="state" readonly="1" widget="statusbar" statusbar_visible="draft,confirmed" /> <field name="state" readonly="1" widget="statusbar" statusbar_visible="draft,confirmed" />
</header> </header>
<sheet> <sheet>
@ -73,7 +55,6 @@
<group> <group>
<field name="company_id" groups="base.group_multi_company" widget="selection"/> <field name="company_id" groups="base.group_multi_company" widget="selection"/>
<field name="origin" class="oe_inline" placeholder="e.g. SO005"/> <field name="origin" class="oe_inline" placeholder="e.g. SO005"/>
<field name="message"/>
</group> </group>
</group> </group>
<notebook> <notebook>
@ -84,14 +65,7 @@
<field name="product_uos_qty" class="oe_inline"/> <field name="product_uos_qty" class="oe_inline"/>
<field name="product_uos" class="oe_inline"/> <field name="product_uos" class="oe_inline"/>
</div> </div>
<field name="location_id" domain="[('usage','=','internal')]"/>
</group> </group>
<group>
<field name="move_id"/>
<field name="date_close"/>
<field name="close_move"/>
</group>
<field name="note" placeholder="Internal note..."/>
</page> </page>
</notebook> </notebook>
</sheet> </sheet>
@ -111,15 +85,11 @@
<field name="date_planned"/> <field name="date_planned"/>
<filter icon="terp-emblem-important" string="Exceptions" name="exceptions" domain="[('state','=','exception')]" help="Procurement Exceptions"/> <filter icon="terp-emblem-important" string="Exceptions" name="exceptions" domain="[('state','=','exception')]" help="Procurement Exceptions"/>
<separator/> <separator/>
<filter icon="terp-emblem-important" string="To Fix" name="perm_exceptions" domain="[('state','=','exception'),('message', '!=', '')]" help="Permanent Procurement Exceptions"/>
<filter icon="terp-emblem-important" string="Temporary" name="temp_exceptions" domain="[('state','=','exception'),('message', '=', '')]" help="Temporary Procurement Exceptions"/>
<separator/>
<filter icon="terp-gnome-cpu-frequency-applet+" string="Late" domain="['&amp;', ('date_planned','&lt;', current_date), ('state', 'in', ('draft', 'confirmed'))]" help="Procurement started late" /> <filter icon="terp-gnome-cpu-frequency-applet+" string="Late" domain="['&amp;', ('date_planned','&lt;', current_date), ('state', 'in', ('draft', 'confirmed'))]" help="Procurement started late" />
<field name="product_id" /> <field name="product_id" />
<field name="state" /> <field name="state" />
<group expand="0" string="Group By"> <group expand="0" string="Group By">
<filter string="Product" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'product_id'}"/> <filter string="Product" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'product_id'}"/>
<filter string="Reason" icon="terp-gtk-jump-to-rtl" domain="[]" context="{'group_by':'name'}"/>
<filter string="Scheduled Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_planned'}"/> <filter string="Scheduled Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_planned'}"/>
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/> <filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
</group> </group>
@ -174,186 +144,5 @@
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="domain">[('state','=','exception')]</field> <field name="domain">[('state','=','exception')]</field>
</record> </record>
<!-- Order Point -->
<record id="view_warehouse_orderpoint_tree" model="ir.ui.view">
<field name="name">stock.warehouse.orderpoint.tree</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="arch" type="xml">
<tree string="Reordering Rules">
<field name="name"/>
<field name="warehouse_id" groups="stock.group_locations"/>
<field name="location_id" groups="stock.group_locations"/>
<field name="product_id"/>
<field name="product_uom" groups="product.group_uom"/>
<field name="product_min_qty"/>
<field name="product_max_qty"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="warehouse_orderpoint_search">
<field name="name">stock.warehouse.orderpoint.search</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="arch" type="xml">
<search string="Reordering Rules Search">
<field name="name" string="Reordering Rules"/>
<field name="warehouse_id"/>
<field name="location_id" groups="stock.group_locations"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="product_id"/>
<group expand="0" string="Group By...">
<filter string="Warehouse" icon="terp-go-home" domain="[]" context="{'group_by':'warehouse_id'}"/>
<filter string="Location" icon="terp-go-home" domain="[]" context="{'group_by':'location_id'}"/>
</group>
</search>
</field>
</record>
<record id="view_warehouse_orderpoint_form" model="ir.ui.view">
<field name="name">stock.warehouse.orderpoint.form</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="arch" type="xml">
<form string="Reordering Rules" version="7.0">
<sheet>
<group>
<group>
<field name="name" />
<field name="product_id" on_change="onchange_product_id(product_id)"/>
</group>
<group>
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
<field name="product_uom" groups="product.group_uom"/>
<field name="location_id" groups="stock.group_locations"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
</group>
<group>
<group string="Rules">
<field name="product_min_qty" />
<field name="product_max_qty" />
<field name="qty_multiple" string="Quantity Multiple"/>
</group>
<group string="Misc">
<field name="procurement_id" readonly="1"/>
<field name="active" />
</group>
</group>
<group string="Procurement Orders to Process">
<field name="procurement_draft_ids" nolabel="1"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_orderpoint_form" model="ir.actions.act_window">
<field name="name">Reordering Rules</field>
<field name="res_model">stock.warehouse.orderpoint</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_warehouse_orderpoint_tree"/>
<field name="search_view_id" ref="warehouse_orderpoint_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a reordering rule.
</p><p>You can define your minimum stock rules, so that OpenERP will automatically create draft manufacturing orders or request for quotations according to the stock level. Once the virtual stock of a product (= stock on hand minus all confirmed orders and reservations) is below the minimum quantity, OpenERP will generate a procurement request to increase the stock up to the maximum quantity.</p>
</field>
</record>
<act_window
context="{'search_default_warehouse_id': active_id, 'default_warehouse_id': active_id}"
id="act_stock_warehouse_2_stock_warehouse_orderpoint"
name="Reordering Rules"
res_model="stock.warehouse.orderpoint"
src_model="stock.warehouse"
groups="stock.group_stock_user"/>
<act_window
context="{'product_uom': locals().has_key('uom_id') and uom_id, 'default_procurement_id': active_id}"
id="act_procurement_2_stock_warehouse_orderpoint"
name="Reordering Rules"
res_model="stock.warehouse.orderpoint"
src_model="procurement.order"
groups="stock.group_stock_user"/>
<!-- Procurements are located in Warehouse menu hierarchy, MRP users should come to Stock application to use it. -->
<menuitem id="menu_stock_sched" name="Schedulers" parent="stock.menu_stock_root" sequence="4" groups="stock.group_stock_manager"/>
<menuitem action="action_compute_schedulers" id="menu_stock_proc_schedulers" parent="menu_stock_sched" sequence="20" groups="stock.group_stock_manager"/>
<menuitem action="procurement_exceptions" id="menu_stock_procurement_action" parent="menu_stock_sched" sequence="50" groups="stock.group_stock_manager"/>
<menuitem id="menu_stock_procurement" name="Automatic Procurements" parent="stock.menu_stock_configuration" sequence="5"/>
<menuitem action="action_orderpoint_form" id="menu_stock_order_points" parent="stock.menu_stock_configuration" sequence="10"/>
<record model="ir.actions.act_window" id="product_open_orderpoint">
<field name="context">{'default_product_id': active_id, 'search_default_product_id': active_id}</field>
<field name="name">Orderpoints</field>
<field name="res_model">stock.warehouse.orderpoint</field>
</record>
<record model="ir.ui.view" id="product_template_form_view_procurement">
<field name="name">product.template.procurement</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='type']" position="after">
<field name="procure_method"/>
<field name="supply_method"/>
</xpath>
</field>
</record>
<record id="product_search_form_view_procurment" model="ir.ui.view">
<field name="name">product.search.procurment.form</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_search_form_view"/>
<field name="arch" type="xml">
<filter name="consumable" position="before">
<filter string="Products" icon="terp-accessories-archiver" domain="[('type','=','product')]" help="Stockable products"/>
</filter>
</field>
</record>
<record model="ir.ui.view" id="product_form_view_procurement_button">
<field name="name">product.product.procurement</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="stock.view_normal_procurement_locations_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='incoming_qty']" position="after">
<button string="⇒ Request Procurement" name="%(act_make_procurement)d" type="action" class="oe_link"/>
</xpath>
<xpath expr="//div[@name='buttons']" position="inside">
<button string="Orderpoints" name="%(product_open_orderpoint)d" type="action" attrs="{'invisible':[('type', '=', 'service')]}"/>
</xpath>
<xpath expr="//field[@name='cost_method']" position="before">
<field name="procure_method" groups="base.group_user"/>
<field name="supply_method" groups="base.group_user"/>
</xpath>
<xpath expr="//group[@name='general']" position="after" >
<group name="procurement_help" class="oe_grey" col="1" groups="base.group_user">
<p attrs="{'invisible': ['|','|',('type','&lt;&gt;','service'),('procure_method','&lt;&gt;','make_to_stock')]}">
When you sell this service, nothing special will be triggered
to deliver the customer, as you set the procurement method as
'Make to Stock'.
</p>
<p attrs="{'invisible': ['|','|',('type','&lt;&gt;','product'),('procure_method','&lt;&gt;','make_to_stock')]}">
When you sell this product, OpenERP will <b>use the available
inventory</b> for the delivery order.
<br/><br/>
If there are not enough quantities available, the delivery order
will wait for new products. To fulfill the inventory, you should
create others rules like orderpoints.
</p>
<p attrs="{'invisible': ['|','|',('type','&lt;&gt;','consu'),('procure_method','&lt;&gt;','make_to_stock')]}">
When you sell this product, a delivery order will be created.
OpenERP will consider that the <b>required quantities are always
available</b> as it's a consumable (as a result of this, the quantity
on hand may become negative).
</p>
</group>
</xpath>
</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -1,181 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="wkf_procurement" model="workflow">
<field name="name">procurement.order.basic</field>
<field name="osv">procurement.order</field>
<field name="on_create">True</field>
</record>
<record id="act_draft" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="flow_start">True</field>
<field name="name">draft</field>
</record>
<record id="act_cancel" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="name">cancel</field>
<field name="kind">function</field>
<field name="action">action_cancel()</field>
<field name="flow_stop">True</field>
</record>
<record id="act_confirm" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="name">confirm</field>
<field name="kind">function</field>
<field name="action">action_confirm()</field>
</record>
<record id="act_confirm_wait" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="name">confirm_wait</field>
<field name="kind">function</field>
<field name="action">write({'state':'exception'})</field>
</record>
<record id="act_confirm_mts" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="name">confirm_mts</field>
</record>
<record id="act_confirm_mto" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="name">confirm_mto</field>
</record>
<record id="act_make_to_stock" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="name">make_to_stock</field>
<field name="kind">function</field>
<field name="action">action_move_assigned()</field>
</record>
<record id="act_make_done" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="name">ready</field>
<field name="kind">function</field>
<field name="action">action_ready()</field>
</record>
<record id="act_wait_done" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="name">wait_done</field>
<field name="kind">function</field>
<field name="action">write({'state':'waiting'})</field>
</record>
<record id="act_done" model="workflow.activity">
<field name="wkf_id" ref="wkf_procurement"/>
<field name="flow_stop">True</field>
<field name="name">done</field>
<field name="kind">function</field>
<field name="action">action_done()</field>
</record>
<record id="trans_draft_confirm" model="workflow.transition">
<field name="act_from" ref="act_draft"/>
<field name="act_to" ref="act_confirm"/>
<field name="signal">button_confirm</field>
</record>
<record id="trans_confirm_cancel2" model="workflow.transition">
<field name="act_from" ref="act_confirm"/>
<field name="act_to" ref="act_wait_done"/>
<field name="signal">button_wait_done</field>
<field name="condition">True</field>
</record>
<record id="trans_confirm_wait_done" model="workflow.transition">
<field name="act_from" ref="act_wait_done"/>
<field name="act_to" ref="act_done"/>
<field name="condition">check_move_done()</field>
<field name="trigger_model">stock.move</field>
<field name="trigger_expr_id">[move_id.id]</field>
</record>
<record id="trans_confirm_cancel" model="workflow.transition">
<field name="act_from" ref="act_confirm"/>
<field name="act_to" ref="act_cancel"/>
<field name="signal">button_check</field>
<field name="condition">test_cancel()</field>
</record>
<record id="trans_confirm_confirm_wait" model="workflow.transition">
<field name="act_from" ref="act_confirm"/>
<field name="act_to" ref="act_confirm_wait"/>
<field name="signal">button_check</field>
<field name="condition">check_conditions_confirm2wait()</field>
</record>
<record id="trans_confirm_wait_confirm_mto" model="workflow.transition">
<field name="act_from" ref="act_confirm_wait"/>
<field name="act_to" ref="act_confirm_mto"/>
<field name="condition">procure_method=='make_to_order'</field>
</record>
<record id="trans_confirm_wait_confirm_mts" model="workflow.transition">
<field name="act_from" ref="act_confirm_wait"/>
<field name="act_to" ref="act_confirm_mts"/>
<field name="condition">procure_method=='make_to_stock'</field>
</record>
<record id="trans_confirm_mts_cancel" model="workflow.transition">
<field name="act_from" ref="act_confirm_mts"/>
<field name="act_to" ref="act_cancel"/>
<field name="signal">button_cancel</field>
</record>
<record id="trans_confirm_waiting_cancel" model="workflow.transition">
<field name="act_from" ref="act_wait_done"/>
<field name="act_to" ref="act_cancel"/>
<field name="signal">button_cancel</field>
</record>
<record id="trans_confirm_mts_confirm" model="workflow.transition">
<field name="act_from" ref="act_confirm_mts"/>
<field name="act_to" ref="act_confirm"/>
<field name="signal">button_restart</field>
</record>
<record id="trans_confirm_mto_cancel" model="workflow.transition">
<field name="act_from" ref="act_confirm_mto"/>
<field name="act_to" ref="act_cancel"/>
<field name="signal">button_cancel</field>
</record>
<record id="trans_confirm_mto_confirm" model="workflow.transition">
<field name="act_from" ref="act_confirm_mto"/>
<field name="act_to" ref="act_confirm"/>
<field name="signal">button_restart</field>
</record>
<record id="trans_draft_cancel" model="workflow.transition">
<field name="act_from" ref="act_draft"/>
<field name="act_to" ref="act_cancel"/>
<field name="signal">button_cancel</field>
</record>
<record id="trans_confirm_mts_make_to_stock" model="workflow.transition">
<field name="act_from" ref="act_confirm_mts"/>
<field name="act_to" ref="act_make_to_stock"/>
<field name="condition">check_make_to_stock()</field>
</record>
<record id="trans_confirm_mto_make_done" model="workflow.transition">
<!-- This transition is there to unblock products that would be in MTO with a supply method that would be
produce or buy without MRP or purchase module installed. These modules overwrite the check_produce()
and check_buy() methods -so that it invalidates their part of this 'bypass transition'-, and define
their own workflow paths.
-->
<field name="act_from" ref="act_confirm_mto"/>
<field name="act_to" ref="act_make_done"/>
<field name="condition">not check_produce() and not check_buy()</field>
</record>
<record id="trans_make_to_stock_make_done" model="workflow.transition">
<field name="act_from" ref="act_make_to_stock"/> <!-- TOFIX: If product is service product and procure method is 'make_to_stock', procurement is closed without generated service -->
<field name="act_to" ref="act_make_done"/>
<field name="condition">True</field>
<field name="trigger_model" eval="False"/>
<field name="trigger_expr_id" eval="False"/>
</record>
<record id="trans_make_done_done" model="workflow.transition">
<field name="act_from" ref="act_make_done"/>
<field name="act_to" ref="act_done"/>
<field name="condition">action_check_finished()</field>
<field name="trigger_model">stock.move</field>
<field name="trigger_expr_id">move_id and [move_id.id] or []</field>
</record>
<record id="trans_make_done_confirm" model="workflow.transition">
<field name="act_from" ref="act_make_done"/>
<field name="act_to" ref="act_cancel"/>
<field name="condition">check_move_cancel()</field>
</record>
</data>
</openerp>

View File

@ -1,276 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime
from dateutil.relativedelta import relativedelta
import openerp
from openerp.osv import osv
from openerp.osv import fields
from openerp.tools.translate import _
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from openerp import tools
class procurement_order(osv.osv):
_inherit = 'procurement.order'
def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
''' Runs through scheduler.
@param use_new_cursor: False or the dbname
'''
self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
use_new_cursor=use_new_cursor, context=context)
def _procure_confirm(self, cr, uid, ids=None, use_new_cursor=False, context=None):
'''
Call the scheduler to check the procurement order
@param self: The object pointer
@param cr: The current row, from the database cursor,
@param uid: The current user ID for security checks
@param ids: List of selected IDs
@param use_new_cursor: False or the dbname
@param context: A standard dictionary for contextual values
@return: Dictionary of values
'''
if context is None:
context = {}
try:
if use_new_cursor:
cr = openerp.registry(use_new_cursor).db.cursor()
procurement_obj = self.pool.get('procurement.order')
if not ids:
ids = procurement_obj.search(cr, uid, [('state', '=', 'exception')], order="date_planned")
self.signal_button_restart(cr, uid, ids)
if use_new_cursor:
cr.commit()
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
maxdate = (datetime.today() + relativedelta(days=company.schedule_range)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
start_date = fields.datetime.now()
offset = 0
report = []
report_total = 0
report_except = 0
report_later = 0
while True:
ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_order')], offset=offset, limit=500, order='priority, date_planned', context=context)
for proc in procurement_obj.browse(cr, uid, ids, context=context):
if maxdate >= proc.date_planned:
self.signal_button_check(cr, uid, [proc.id])
else:
offset += 1
report_later += 1
if proc.state == 'exception':
report.append(_('PROC %d: on order - %3.2f %-5s - %s') % \
(proc.id, proc.product_qty, proc.product_uom.name,
proc.product_id.name))
report_except += 1
report_total += 1
if use_new_cursor:
cr.commit()
if not ids:
break
offset = 0
ids = []
while True:
report_ids = []
ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_stock')], offset=offset)
for proc in procurement_obj.browse(cr, uid, ids):
if maxdate >= proc.date_planned:
self.signal_button_check(cr, uid, [proc.id])
report_ids.append(proc.id)
else:
report_later += 1
report_total += 1
if proc.state == 'exception':
report.append(_('PROC %d: from stock - %3.2f %-5s - %s') % \
(proc.id, proc.product_qty, proc.product_uom.name,
proc.product_id.name,))
report_except += 1
if use_new_cursor:
cr.commit()
offset += len(ids)
if not ids: break
end_date = fields.datetime.now()
if use_new_cursor:
cr.commit()
finally:
if use_new_cursor:
try:
cr.close()
except Exception:
pass
return {}
def _prepare_automatic_op_procurement(self, cr, uid, product, warehouse, location_id, context=None):
return {'name': _('Automatic OP: %s') % (product.name,),
'origin': _('SCHEDULER'),
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'product_id': product.id,
'product_qty': -product.virtual_available,
'product_uom': product.uom_id.id,
'location_id': location_id,
'company_id': warehouse.company_id.id,
'procure_method': 'make_to_order',}
def create_automatic_op(self, cr, uid, context=None):
"""
Create procurement of virtual stock < 0
@param self: The object pointer
@param cr: The current row, from the database cursor,
@param uid: The current user ID for security checks
@param context: A standard dictionary for contextual values
@return: Dictionary of values
"""
if context is None:
context = {}
product_obj = self.pool.get('product.product')
proc_obj = self.pool.get('procurement.order')
warehouse_obj = self.pool.get('stock.warehouse')
warehouse_ids = warehouse_obj.search(cr, uid, [], context=context)
products_ids = product_obj.search(cr, uid, [], order='id', context=context)
for warehouse in warehouse_obj.browse(cr, uid, warehouse_ids, context=context):
context['warehouse'] = warehouse
# Here we check products availability.
# We use the method 'read' for performance reasons, because using the method 'browse' may crash the server.
for product_read in product_obj.read(cr, uid, products_ids, ['virtual_available'], context=context):
if product_read['virtual_available'] >= 0.0:
continue
product = product_obj.browse(cr, uid, [product_read['id']], context=context)[0]
if product.supply_method == 'buy':
location_id = warehouse.lot_input_id.id
elif product.supply_method == 'produce':
location_id = warehouse.lot_stock_id.id
else:
continue
proc_id = proc_obj.create(cr, uid,
self._prepare_automatic_op_procurement(cr, uid, product, warehouse, location_id, context=context),
context=context)
self.signal_button_confirm(cr, uid, [proc_id])
self.signal_button_check(cr, uid, [proc_id])
return True
def _get_orderpoint_date_planned(self, cr, uid, orderpoint, start_date, context=None):
date_planned = start_date + \
relativedelta(days=orderpoint.product_id.seller_delay or 0.0)
return date_planned.strftime(DEFAULT_SERVER_DATE_FORMAT)
def _prepare_orderpoint_procurement(self, cr, uid, orderpoint, product_qty, context=None):
return {'name': orderpoint.name,
'date_planned': self._get_orderpoint_date_planned(cr, uid, orderpoint, datetime.today(), context=context),
'product_id': orderpoint.product_id.id,
'product_qty': product_qty,
'company_id': orderpoint.company_id.id,
'product_uom': orderpoint.product_uom.id,
'location_id': orderpoint.location_id.id,
'procure_method': 'make_to_order',
'origin': orderpoint.name}
def _product_virtual_get(self, cr, uid, order_point):
location_obj = self.pool.get('stock.location')
return location_obj._product_virtual_get(cr, uid,
order_point.location_id.id, [order_point.product_id.id],
{'uom': order_point.product_uom.id})[order_point.product_id.id]
def _procure_orderpoint_confirm(self, cr, uid, automatic=False,\
use_new_cursor=False, context=None, user_id=False):
'''
Create procurement based on Orderpoint
use_new_cursor: False or the dbname
@param self: The object pointer
@param cr: The current row, from the database cursor,
@param user_id: The current user ID for security checks
@param context: A standard dictionary for contextual values
@param param: False or the dbname
@return: Dictionary of values
"""
'''
if context is None:
context = {}
if use_new_cursor:
cr = openerp.registry(use_new_cursor).db.cursor()
orderpoint_obj = self.pool.get('stock.warehouse.orderpoint')
procurement_obj = self.pool.get('procurement.order')
offset = 0
ids = [1]
if automatic:
self.create_automatic_op(cr, uid, context=context)
while ids:
ids = orderpoint_obj.search(cr, uid, [], offset=offset, limit=100)
for op in orderpoint_obj.browse(cr, uid, ids, context=context):
prods = self._product_virtual_get(cr, uid, op)
if prods is None:
continue
if prods < op.product_min_qty:
qty = max(op.product_min_qty, op.product_max_qty)-prods
reste = qty % op.qty_multiple
if reste > 0:
qty += op.qty_multiple - reste
if qty <= 0:
continue
if op.product_id.type not in ('consu'):
if op.procurement_draft_ids:
# Check draft procurement related to this order point
pro_ids = [x.id for x in op.procurement_draft_ids]
procure_datas = procurement_obj.read(
cr, uid, pro_ids, ['id', 'product_qty'], context=context)
to_generate = qty
for proc_data in procure_datas:
if to_generate >= proc_data['product_qty']:
self.signal_button_confirm(cr, uid, [proc_data['id']])
procurement_obj.write(cr, uid, [proc_data['id']], {'origin': op.name}, context=context)
to_generate -= proc_data['product_qty']
if not to_generate:
break
qty = to_generate
if qty:
proc_id = procurement_obj.create(cr, uid,
self._prepare_orderpoint_procurement(cr, uid, op, qty, context=context),
context=context)
self.signal_button_confirm(cr, uid, [proc_id])
self.signal_button_check(cr, uid, [proc_id])
orderpoint_obj.write(cr, uid, [op.id],
{'procurement_id': proc_id}, context=context)
offset += len(ids)
if use_new_cursor:
cr.commit()
if use_new_cursor:
cr.commit()
cr.close()
return {}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,9 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_procurement,procurement.order,model_procurement_order,base.group_user,1,0,0,0 access_procurement,procurement.order,model_procurement_order,base.group_user,1,1,1,0
access_procurement_stock_user,procurement.order stock.user,model_procurement_order,stock.group_stock_user,1,1,1,1 access_procurement_group,procurement.group,model_procurement_group,base.group_user,1,1,1,0
access_stock_warehouse_orderpoint,stock.warehouse.orderpoint,model_stock_warehouse_orderpoint,stock.group_stock_user,1,0,0,0 access_procurement_rule,procurement.rule,model_procurement_rule,base.group_user,1,1,1,0
access_stock_warehouse_orderpoint_system,stock.warehouse.orderpoint system,model_stock_warehouse_orderpoint,stock.group_stock_manager,1,1,1,1
access_mrp_property_group,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
access_mrp_property,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
access_mrp_property_group,mrp.property.group,model_mrp_property_group,base.group_user,1,0,0,0
access_mrp_property,mrp.property,model_mrp_property,base.group_user,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_procurement procurement.order model_procurement_order base.group_user 1 0 1 0 1 0
3 access_procurement_stock_user access_procurement_group procurement.order stock.user procurement.group model_procurement_order model_procurement_group stock.group_stock_user base.group_user 1 1 1 1 0
4 access_stock_warehouse_orderpoint access_procurement_rule stock.warehouse.orderpoint procurement.rule model_stock_warehouse_orderpoint model_procurement_rule stock.group_stock_user base.group_user 1 0 1 0 1 0
access_stock_warehouse_orderpoint_system stock.warehouse.orderpoint system model_stock_warehouse_orderpoint stock.group_stock_manager 1 1 1 1
access_mrp_property_group mrp.property.group model_mrp_property_group stock.group_stock_manager 1 1 1 1
access_mrp_property mrp.property model_mrp_property stock.group_stock_manager 1 1 1 1
access_mrp_property_group mrp.property.group model_mrp_property_group base.group_user 1 0 0 0
access_mrp_property mrp.property model_mrp_property base.group_user 1 0 0 0

View File

@ -9,12 +9,5 @@
<field name="domain_force">['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]</field> <field name="domain_force">['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]</field>
</record> </record>
<record model="ir.rule" id="stock_warehouse_orderpoint_rule">
<field name="name">stock_warehouse.orderpoint multi-company</field>
<field name="model_id" search="[('model','=','stock.warehouse.orderpoint')]" model="ir.model"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -1,89 +1,22 @@
- -
For test the procurement process, First I have to apply a minimum stock rule on product I create a procurement
- -
I create minimum stock rule for product. !record {model: procurement.order, id: procurement_order0}:
-
!record {model: stock.warehouse.orderpoint, id: stock_warehouse_orderpoint_op0}:
company_id: base.main_company company_id: base.main_company
location_id: stock.stock_location_stock name: Procurement Test
logic: max
name: OP/00008
product_id: product.product_product_32 product_id: product.product_product_32
product_max_qty: 15.0 product_qty: 15.0
product_min_qty: 5.0
product_uom: product.product_uom_unit product_uom: product.product_uom_unit
qty_multiple: 1 product_uos_qty: 15.0
warehouse_id: stock.warehouse0 product_uos: product.product_uom_unit
-
Check product quantity and update it, if needed for apply a minimum stock rule.
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product.product_product_32'))
if product.virtual_available < 5.0:
change_qty = self.pool.get('stock.change.product.qty')
id = change_qty.create(cr, uid, {'location_id' : ref('stock.stock_location_stock'), 'new_quantity': 4, 'product_id': product.id})
change_qty.change_product_qty(cr, uid, [id], {'active_model':'product.product', 'active_id': product.id, 'active_ids':[product.id]})
assert product.qty_available == 4,"Product quantity is not updated."
assert product.virtual_available < 5.0,'Virtual stock have more quantities.'
- -
I run the scheduler. I run the scheduler.
- -
!python {model: procurement.order}: | !python {model: procurement.order}: |
self.run_scheduler(cr, uid) self.run_scheduler(cr, uid)
- -
I check that procurement order is based on minimum stock rule. I check that procurement order is done
- -
!python {model: procurement.order}: | !python {model: procurement.order}: |
proc_ids = self.search(cr, uid, [('product_id','=', ref('product.product_product_32'))]) proc_order = self.browse(cr, uid, ref('procurement_order0'), context=context)
assert proc_ids, 'No Procurement created.' assert proc_order.state == 'done'
proc_order = self.browse(cr, uid, proc_ids)[0]
assert proc_order.product_qty == 11.0,"Procurement product quantity is not corresponded."
-
I check product quantity.
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product.product_product_32'))
assert product.virtual_available == 15.0,"After run the scheduler product's virtual stock is not updated."
-
For test the Procurement Request wizard, Again I have to update product quantity.
-
!python {model: product.product}: |
mk_procure = self.pool.get('make.procurement')
procur_order = self.pool.get('procurement.order')
product = self.browse(cr, uid, ref('product.product_product_32'))
context.update({'active_model': 'product.product','active_id':ref('product.product_product_32')})
values = {'warehouse_id': ref('base.main_company'), 'uom_id': ref('product.product_uom_unit'), 'qty': 5}
id = mk_procure.create(cr, uid, values, context)
procurement = mk_procure.make_procurement(cr, uid, [id], context)
assert product.virtual_available == 20.0, 'Virtual stock should be updated'
proc_id = procurement.get('res_id')
for procurement in procur_order.browse(cr, uid, [proc_id]):
if procurement.state == 'confirmed':
assert procurement.state == 'confirmed',"Procurement state should be 'Confirmed'."
assert procurement.product_id.id == ref('product.product_product_32'),"Product is not correspond."
assert procurement.product_qty == 5,"Product Quantity is not correspond."
assert procurement.product_uom.id == ref('product.product_uom_unit'),"Product's UOM is not correspond."
context.update({'proc': proc_id})
-
I run the scheduler.
-
!python {model: procurement.order}: |
self.run_scheduler(cr, uid)
-
I check the current state of procurement.
-
!python {model: procurement.order}: |
proc_id = context.get('proc')
proc = self.browse(cr, uid, [proc_id])[0]
assert proc.state == 'ready' or 'exception',"Procurement should be in Ready or Exception state"
#-
# I compute minimum stock.
# [Note. Commented out because it spawns a thread that may query the db after tests have been reverted.]
#-
# !python {model: procurement.orderpoint.compute}: |
# proc_id = context.get('proc')
# context.update({'active_model': 'procurement.order', 'active_id': proc_id})
# id = self.create(cr, uid, {'automatic': True}, context)
# self.procure_calculation(cr, uid, [id], context)

View File

@ -19,9 +19,6 @@
# #
############################################################################## ##############################################################################
import orderpoint_procurement
import mrp_procurement
import schedulers_all import schedulers_all
import make_procurement_product
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -28,11 +28,6 @@ class procurement_compute_all(osv.osv_memory):
_description = 'Compute all schedulers' _description = 'Compute all schedulers'
_columns = { _columns = {
'automatic': fields.boolean('Automatic orderpoint',help='Triggers an automatic procurement for all products that have a virtual stock under 0. You should probably not use this option, we suggest using a MTO configuration on products.'),
}
_defaults = {
'automatic': lambda *a: False,
} }
def _procure_calculation_all(self, cr, uid, ids, context=None): def _procure_calculation_all(self, cr, uid, ids, context=None):
@ -46,9 +41,7 @@ class procurement_compute_all(osv.osv_memory):
proc_obj = self.pool.get('procurement.order') proc_obj = self.pool.get('procurement.order')
#As this function is in a new thread, i need to open a new cursor, because the old one may be closed #As this function is in a new thread, i need to open a new cursor, because the old one may be closed
new_cr = self.pool.db.cursor() new_cr = self.pool.db.cursor()
for proc in self.browse(new_cr, uid, ids, context=context): proc_obj.run_scheduler(new_cr, uid, use_new_cursor=new_cr.dbname, context=context)
proc_obj.run_scheduler(new_cr, uid, automatic=proc.automatic, use_new_cursor=new_cr.dbname,\
context=context)
#close the new cursor #close the new cursor
new_cr.close() new_cr.close()
return {} return {}

View File

@ -9,9 +9,9 @@
<field name="model">procurement.order.compute.all</field> <field name="model">procurement.order.compute.all</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Scheduler Parameters" version="7.0"> <form string="Scheduler Parameters" version="7.0">
<group> <p>
<field name="automatic"/> Compute all procurements in the background.
</group> </p>
<footer> <footer>
<button name="procure_calculation" string="Run Schedulers" type="object" class="oe_highlight" /> <button name="procure_calculation" string="Run Schedulers" type="object" class="oe_highlight" />
or or

View File

@ -54,11 +54,13 @@ Dashboard / Reports for Warehouse Management will include:
""", """,
'website': 'http://www.openerp.com', 'website': 'http://www.openerp.com',
'images': ['images/stock_forecast_report.png', 'images/delivery_orders.jpeg', 'images/inventory_analysis.jpeg','images/location.jpeg','images/moves_analysis.jpeg','images/physical_inventories.jpeg','images/warehouse_dashboard.jpeg'], 'images': ['images/stock_forecast_report.png', 'images/delivery_orders.jpeg', 'images/inventory_analysis.jpeg','images/location.jpeg','images/moves_analysis.jpeg','images/physical_inventories.jpeg','images/warehouse_dashboard.jpeg'],
'depends': ['product', 'account'], 'depends': ['product', 'account','procurement'],
'category': 'Warehouse Management', 'category': 'Warehouse Management',
'sequence': 16, 'sequence': 16,
'demo': [ 'demo': [
'stock_demo.xml', 'stock_demo.xml',
'procurement_demo.xml',
'stock_orderpoint.xml',
# 'stock_demo.yml', # 'stock_demo.yml',
], ],
'data': [ 'data': [
@ -77,6 +79,9 @@ Dashboard / Reports for Warehouse Management will include:
'wizard/stock_inventory_line_split_view.xml', 'wizard/stock_inventory_line_split_view.xml',
'wizard/stock_change_standard_price_view.xml', 'wizard/stock_change_standard_price_view.xml',
'wizard/stock_return_picking_view.xml', 'wizard/stock_return_picking_view.xml',
'wizard/make_procurement_view.xml',
'wizard/mrp_procurement_view.xml',
'wizard/orderpoint_procurement_view.xml',
'stock_workflow.xml', 'stock_workflow.xml',
'stock_incoterms.xml', 'stock_incoterms.xml',
'stock_report.xml', 'stock_report.xml',

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
from datetime import datetime
from dateutil.relativedelta import relativedelta
from openerp.osv import fields, osv
import openerp.addons.decimal_precision as dp
class procurement_group(osv.osv):
_inherit = 'procurement.group'
_columns = {
'partner_id': fields.many2one('res.partner', 'Partner')
}
class procurement_rule(osv.osv):
_inherit = 'procurement.rule'
def _get_action(self, cr, uid, context=context):
result = super(procurement_rule, self)._get_action(cr, uid, context=context)
return result + [('move','Move From Another Location')]
class procurement_order(osv.osv):
_inherit = "procurement.order"
_columns = {
'location_id': fields.many2one('stock.location', 'Source Location')
'move_id': fields.many2one('stock.move', 'Move')
'move_dest_id': fields.many2one('stock.move', 'Destination Move')
'location_src_id': fields.many2one('stock.location', 'Source Location',
help="Source location is action=move")
}
def _run(self, cr, uid, procurement, context=None):
if procurement.rule_id and procurement.rule_id.action == 'move':
if not procurement.location_src_id:
self.message_post(cr, uid, [procurement.id], body=_('No source location defined!'), context=context)
return False
move_obj = self.pool.get('stock.move')
move_id = move_obj.create(cr, uid, {
'name': procurement.name,
'company_id': procurement.company_id.id,
'product_id': procurement.product_id.id,
'date': procurement.date_planned,
'product_qty': procurement.product_qty,
'product_uom': procurement.product_uom.id,
'product_uos_qty': (procurement.product_uos and procurement.product_uos_qty)\
or procurement.product_qty,
'product_uos': (procurement.product_uos and procurement.product_uos.id)\
or procurement.product_uom.id,
'partner_id': procurement.group_id and procurement.group_id.partner_id and \
procurement.group_id.partner_id.id or False,
'location_id': procurement.location_src_id.id,
'location_dest_id': procurement.location_id.id,
'move_dest_id': procurement.move_dest_id and procurement.move_dest_id.id or False,
'cancel_cascade': procurement.rule_id and procurement.rule_id.cancel_cascade or False,
'group_id': procurement.group_id and procurement.group_id.id or False,
})
move_obj.button_confirm(cr,uid, [move_id], context=context)
self.write(cr, uid, [procurement.id], {'move_id': move_id}, context=context)
return True
return super(procurement_order, self)._run(cr, uid, procurement, context)
def _check(self, cr, uid, procurement, context=None):
if procurement.rule_id and procurement.rule_id.action == 'move':
return procurement.move_id.state=='done'
return super(procurement_order, self)._check(cr, uid, procurement, context)

View File

@ -53,3 +53,5 @@ access_product_pricelist_version_stock_manager,product.pricelist.version stock_m
access_product_pricelist_item_stock_manager,product.pricelist.item stock_manager,product.model_product_pricelist_item,stock.group_stock_manager,1,1,1,1 access_product_pricelist_item_stock_manager,product.pricelist.item stock_manager,product.model_product_pricelist_item,stock.group_stock_manager,1,1,1,1
access_account_account_stock_manager,account.account stock manager,account.model_account_account,stock.group_stock_manager,1,0,0,0 access_account_account_stock_manager,account.account stock manager,account.model_account_account,stock.group_stock_manager,1,0,0,0
access_board_stock_user,board.board user,board.model_board_board,stock.group_stock_user,1,1,0,0 access_board_stock_user,board.board user,board.model_board_board,stock.group_stock_user,1,1,0,0
access_stock_warehouse_orderpoint,stock.warehouse.orderpoint,model_stock_warehouse_orderpoint,stock.group_stock_user,1,0,0,0
access_stock_warehouse_orderpoint_system,stock.warehouse.orderpoint system,model_stock_warehouse_orderpoint,stock.group_stock_manager,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
53 access_product_pricelist_item_stock_manager product.pricelist.item stock_manager product.model_product_pricelist_item stock.group_stock_manager 1 1 1 1
54 access_account_account_stock_manager account.account stock manager account.model_account_account stock.group_stock_manager 1 0 0 0
55 access_board_stock_user board.board user board.model_board_board stock.group_stock_user 1 1 0 0
56 access_stock_warehouse_orderpoint stock.warehouse.orderpoint model_stock_warehouse_orderpoint stock.group_stock_user 1 0 0 0
57 access_stock_warehouse_orderpoint_system stock.warehouse.orderpoint system model_stock_warehouse_orderpoint stock.group_stock_manager 1 1 1 1

View File

@ -80,5 +80,13 @@
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field> <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record> </record>
<record model="ir.rule" id="stock_warehouse_orderpoint_rule">
<field name="name">stock_warehouse.orderpoint multi-company</field>
<field name="model_id" search="[('model','=','stock.warehouse.orderpoint')]" model="ir.model"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -2840,4 +2840,130 @@ class stock_pack_operation(osv.osv):
pass pass
return todo_on_moves, todo_on_operations return todo_on_moves, todo_on_operations
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
class stock_warehouse_orderpoint(osv.osv):
"""
Defines Minimum stock rules.
"""
_name = "stock.warehouse.orderpoint"
_description = "Minimum Inventory Rule"
def _get_draft_procurements(self, cr, uid, ids, field_name, arg, context=None):
if context is None:
context = {}
result = {}
procurement_obj = self.pool.get('procurement.order')
for orderpoint in self.browse(cr, uid, ids, context=context):
procurement_ids = procurement_obj.search(cr, uid , [('state', '=', 'draft'), ('product_id', '=', orderpoint.product_id.id), ('location_id', '=', orderpoint.location_id.id)])
result[orderpoint.id] = procurement_ids
return result
def _check_product_uom(self, cr, uid, ids, context=None):
'''
Check if the UoM has the same category as the product standard UoM
'''
if not context:
context = {}
for rule in self.browse(cr, uid, ids, context=context):
if rule.product_id.uom_id.category_id.id != rule.product_uom.category_id.id:
return False
return True
_columns = {
'name': fields.char('Name', size=32, required=True),
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the orderpoint without removing it."),
'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, ondelete="cascade"),
'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade"),
'product_id': fields.many2one('product.product', 'Product', required=True, ondelete='cascade', domain=[('type','!=','service')]),
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
'product_min_qty': fields.float('Minimum Quantity', required=True,
help="When the virtual stock goes below the Min Quantity specified for this field, OpenERP generates "\
"a procurement to bring the forecasted quantity to the Max Quantity."),
'product_max_qty': fields.float('Maximum Quantity', required=True,
help="When the virtual stock goes below the Min Quantity, OpenERP generates "\
"a procurement to bring the forecasted quantity to the Quantity specified as Max Quantity."),
'qty_multiple': fields.integer('Qty Multiple', required=True,
help="The procurement quantity will be rounded up to this multiple."),
'procurement_id': fields.many2one('procurement.order', 'Latest procurement', ondelete="set null"),
'company_id': fields.many2one('res.company','Company',required=True),
'procurement_draft_ids': fields.function(_get_draft_procurements, type='many2many', relation="procurement.order", \
string="Related Procurement Orders",help="Draft procurement of the product and location of that orderpoint"),
}
_defaults = {
'active': lambda *a: 1,
'logic': lambda *a: 'max',
'qty_multiple': lambda *a: 1,
'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'stock.orderpoint') or '',
'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', context=c)
}
_sql_constraints = [
('qty_multiple_check', 'CHECK( qty_multiple > 0 )', 'Qty Multiple must be greater than zero.'),
]
_constraints = [
(_check_product_uom, 'You have to select a product unit of measure in the same category than the default unit of measure of the product', ['product_id', 'product_uom']),
]
def default_get(self, cr, uid, fields, context=None):
res = super(stock_warehouse_orderpoint, self).default_get(cr, uid, fields, context)
# default 'warehouse_id' and 'location_id'
if 'warehouse_id' not in res:
warehouse = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'warehouse0', context)
res['warehouse_id'] = warehouse.id
if 'location_id' not in res:
warehouse = self.pool.get('stock.warehouse').browse(cr, uid, res['warehouse_id'], context)
res['location_id'] = warehouse.lot_stock_id.id
return res
def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):
""" Finds location id for changed warehouse.
@param warehouse_id: Changed id of warehouse.
@return: Dictionary of values.
"""
if warehouse_id:
w = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
v = {'location_id': w.lot_stock_id.id}
return {'value': v}
return {}
def onchange_product_id(self, cr, uid, ids, product_id, context=None):
""" Finds UoM for changed product.
@param product_id: Changed id of product.
@return: Dictionary of values.
"""
if product_id:
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
d = {'product_uom': [('category_id', '=', prod.uom_id.category_id.id)]}
v = {'product_uom': prod.uom_id.id}
return {'value': v, 'domain': d}
return {'domain': {'product_uom': []}}
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
default.update({
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.orderpoint') or '',
})
return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context=context)
class product_template(osv.osv):
_inherit="product.template"
_columns = {
'type': fields.selection([('product','Stockable Product'),('consu', 'Consumable'),('service','Service')], 'Product Type', required=True, help="Consumable: Will not imply stock management for this product. \nStockable product: Will imply stock management for this product."),
'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement Method', required=True, help="Make to Stock: When needed, the product is taken from the stock or we wait for replenishment. \nMake to Order: When needed, the product is purchased or produced."),
'supply_method': fields.selection([('produce','Manufacture'),('buy','Buy')], 'Supply Method', required=True, help="Manufacture: When procuring the product, a manufacturing order or a task will be generated, depending on the product type. \nBuy: When procuring the product, a purchase order will be generated."),
}
_defaults = {
'procure_method': 'make_to_stock',
'supply_method': 'buy',
}
class product_product(osv.osv):
_inherit="product.product"
_columns = {
'orderpoint_ids': fields.one2many('stock.warehouse.orderpoint', 'product_id', 'Minimum Stock Rules'),
}

View File

@ -159,5 +159,19 @@ watch your stock valuation, and track production lots upstream and downstream (b
<field name="lot_stock_id" ref="stock_location_stock"/> <field name="lot_stock_id" ref="stock_location_stock"/>
<field name="lot_output_id" ref="stock_location_output"/> <field name="lot_output_id" ref="stock_location_output"/>
</record> </record>
<record id="sequence_mrp_op_type" model="ir.sequence.type">
<field name="name">Stock orderpoint</field>
<field name="code">stock.orderpoint</field>
</record>
<record id="sequence_mrp_op" model="ir.sequence">
<field name="name">Stock orderpoint</field>
<field name="code">stock.orderpoint</field>
<field name="prefix">OP/</field>
<field name="padding">5</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -1446,5 +1446,186 @@
id="menu_action_stock_journal_form" id="menu_action_stock_journal_form"
parent="menu_warehouse_config" sequence="1"/> parent="menu_warehouse_config" sequence="1"/>
<!-- Order Point -->
<record id="view_warehouse_orderpoint_tree" model="ir.ui.view">
<field name="name">stock.warehouse.orderpoint.tree</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="arch" type="xml">
<tree string="Reordering Rules">
<field name="name"/>
<field name="warehouse_id" groups="stock.group_locations"/>
<field name="location_id" groups="stock.group_locations"/>
<field name="product_id"/>
<field name="product_uom" groups="product.group_uom"/>
<field name="product_min_qty"/>
<field name="product_max_qty"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="warehouse_orderpoint_search">
<field name="name">stock.warehouse.orderpoint.search</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="arch" type="xml">
<search string="Reordering Rules Search">
<field name="name" string="Reordering Rules"/>
<field name="warehouse_id"/>
<field name="location_id" groups="stock.group_locations"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="product_id"/>
<group expand="0" string="Group By...">
<filter string="Warehouse" icon="terp-go-home" domain="[]" context="{'group_by':'warehouse_id'}"/>
<filter string="Location" icon="terp-go-home" domain="[]" context="{'group_by':'location_id'}"/>
</group>
</search>
</field>
</record>
<record id="view_warehouse_orderpoint_form" model="ir.ui.view">
<field name="name">stock.warehouse.orderpoint.form</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="arch" type="xml">
<form string="Reordering Rules" version="7.0">
<sheet>
<group>
<group>
<field name="name" />
<field name="product_id" on_change="onchange_product_id(product_id)"/>
</group>
<group>
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
<field name="product_uom" groups="product.group_uom"/>
<field name="location_id" groups="stock.group_locations"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
</group>
<group>
<group string="Rules">
<field name="product_min_qty" />
<field name="product_max_qty" />
<field name="qty_multiple" string="Quantity Multiple"/>
</group>
<group string="Misc">
<field name="procurement_id" readonly="1"/>
<field name="active" />
</group>
</group>
<group string="Procurement Orders to Process">
<field name="procurement_draft_ids" nolabel="1"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_orderpoint_form" model="ir.actions.act_window">
<field name="name">Reordering Rules</field>
<field name="res_model">stock.warehouse.orderpoint</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_warehouse_orderpoint_tree"/>
<field name="search_view_id" ref="warehouse_orderpoint_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a reordering rule.
</p><p>You can define your minimum stock rules, so that OpenERP will automatically create draft manufacturing orders or request for quotations according to the stock level. Once the virtual stock of a product (= stock on hand minus all confirmed orders and reservations) is below the minimum quantity, OpenERP will generate a procurement request to increase the stock up to the maximum quantity.</p>
</field>
</record>
<act_window
context="{'search_default_warehouse_id': active_id, 'default_warehouse_id': active_id}"
id="act_stock_warehouse_2_stock_warehouse_orderpoint"
name="Reordering Rules"
res_model="stock.warehouse.orderpoint"
src_model="stock.warehouse"
groups="stock.group_stock_user"/>
<act_window
context="{'product_uom': locals().has_key('uom_id') and uom_id, 'default_procurement_id': active_id}"
id="act_procurement_2_stock_warehouse_orderpoint"
name="Reordering Rules"
res_model="stock.warehouse.orderpoint"
src_model="procurement.order"
groups="stock.group_stock_user"/>
<!-- Procurements are located in Warehouse menu hierarchy, MRP users should come to Stock application to use it. -->
<menuitem id="menu_stock_sched" name="Schedulers" parent="stock.menu_stock_root" sequence="4" groups="stock.group_stock_manager"/>
<menuitem action="action_compute_schedulers" id="menu_stock_proc_schedulers" parent="menu_stock_sched" sequence="20" groups="stock.group_stock_manager"/>
<menuitem action="procurement_exceptions" id="menu_stock_procurement_action" parent="menu_stock_sched" sequence="50" groups="stock.group_stock_manager"/>
<menuitem id="menu_stock_procurement" name="Automatic Procurements" parent="stock.menu_stock_configuration" sequence="5"/>
<menuitem action="action_orderpoint_form" id="menu_stock_order_points" parent="stock.menu_stock_configuration" sequence="10"/>
<record model="ir.actions.act_window" id="product_open_orderpoint">
<field name="context">{'default_product_id': active_id, 'search_default_product_id': active_id}</field>
<field name="name">Orderpoints</field>
<field name="res_model">stock.warehouse.orderpoint</field>
</record>
<record model="ir.ui.view" id="product_template_form_view_procurement">
<field name="name">product.template.procurement</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='type']" position="after">
<field name="procure_method"/>
<field name="supply_method"/>
</xpath>
</field>
</record>
<record id="product_search_form_view_procurment" model="ir.ui.view">
<field name="name">product.search.procurment.form</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_search_form_view"/>
<field name="arch" type="xml">
<filter name="consumable" position="before">
<filter string="Products" icon="terp-accessories-archiver" domain="[('type','=','product')]" help="Stockable products"/>
</filter>
</field>
</record>
<record model="ir.ui.view" id="product_form_view_procurement_button">
<field name="name">product.product.procurement</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="stock.view_normal_procurement_locations_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='incoming_qty']" position="after">
<button string="⇒ Request Procurement" name="%(act_make_procurement)d" type="action" class="oe_link"/>
</xpath>
<xpath expr="//div[@name='buttons']" position="inside">
<button string="Orderpoints" name="%(product_open_orderpoint)d" type="action" attrs="{'invisible':[('type', '=', 'service')]}"/>
</xpath>
<xpath expr="//field[@name='cost_method']" position="before">
<field name="procure_method" groups="base.group_user"/>
<field name="supply_method" groups="base.group_user"/>
</xpath>
<xpath expr="//group[@name='general']" position="after" >
<group name="procurement_help" class="oe_grey" col="1" groups="base.group_user">
<p attrs="{'invisible': ['|','|',('type','&lt;&gt;','service'),('procure_method','&lt;&gt;','make_to_stock')]}">
When you sell this service, nothing special will be triggered
to deliver the customer, as you set the procurement method as
'Make to Stock'.
</p>
<p attrs="{'invisible': ['|','|',('type','&lt;&gt;','product'),('procure_method','&lt;&gt;','make_to_stock')]}">
When you sell this product, OpenERP will <b>use the available
inventory</b> for the delivery order.
<br/><br/>
If there are not enough quantities available, the delivery order
will wait for new products. To fulfill the inventory, you should
create others rules like orderpoints.
</p>
<p attrs="{'invisible': ['|','|',('type','&lt;&gt;','consu'),('procure_method','&lt;&gt;','make_to_stock')]}">
When you sell this product, a delivery order will be created.
OpenERP will consider that the <b>required quantities are always
available</b> as it's a consumable (as a result of this, the quantity
on hand may become negative).
</p>
</group>
</xpath>
</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -20,6 +20,3 @@
############################################################################## ##############################################################################
import stock_location import stock_location
import procurement_pull
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -96,7 +96,7 @@ You can use the demo data as follow:
'author': 'OpenERP SA', 'author': 'OpenERP SA',
'images': ['images/pulled_flow.jpeg','images/pushed_flow.jpeg'], 'images': ['images/pulled_flow.jpeg','images/pushed_flow.jpeg'],
'depends': ['procurement','stock','sale'], 'depends': ['procurement','stock','sale'],
'data': ['stock_location_view.xml', 'security/stock_location_security.xml', 'security/ir.model.access.csv', 'procurement_pull_workflow.xml'], 'data': ['stock_location_view.xml', 'security/stock_location_security.xml', 'security/ir.model.access.csv'],
'demo': [ 'demo': [
'stock_location_demo_cpu1.xml', 'stock_location_demo_cpu1.xml',
'stock_location_demo_cpu3.yml', 'stock_location_demo_cpu3.yml',

View File

@ -1,79 +0,0 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import osv, fields
from openerp.tools.translate import _
class procurement_order(osv.osv):
_inherit = 'procurement.order'
def check_buy(self, cr, uid, ids, context=None):
for procurement in self.browse(cr, uid, ids, context=context):
for line in procurement.product_id.flow_pull_ids:
if line.location_id==procurement.location_id:
return line.type_proc=='buy'
return super(procurement_order, self).check_buy(cr, uid, ids)
def check_produce(self, cr, uid, ids, context=None):
for procurement in self.browse(cr, uid, ids, context=context):
for line in procurement.product_id.flow_pull_ids:
if line.location_id==procurement.location_id:
return line.type_proc=='produce'
return super(procurement_order, self).check_produce(cr, uid, ids)
def check_move(self, cr, uid, ids, context=None):
for procurement in self.browse(cr, uid, ids, context=context):
for line in procurement.product_id.flow_pull_ids:
if line.location_id==procurement.location_id:
return (line.type_proc=='move') and (line.location_src_id)
return False
def action_move_create(self, cr, uid, ids, context=None):
move_obj = self.pool.get('stock.move')
for proc in proc_obj.browse(cr, uid, ids, context=context):
line = None
for line in proc.product_id.flow_pull_ids:
if line.location_id == proc.location_id:
break
assert line, 'Line cannot be False if we are on this state of the workflow'
origin = (proc.origin or proc.name or '').split(':')[0] +':'+line.name
move_id = move_obj.create(cr, uid, {
'name': line.name,
'company_id': line.company_id and line.company_id.id or False,
'product_id': proc.product_id.id,
'date': proc.date_planned,
'product_qty': proc.product_qty,
'product_uom': proc.product_uom.id,
'product_uos_qty': (proc.product_uos and proc.product_uos_qty)\
or proc.product_qty,
'product_uos': (proc.product_uos and proc.product_uos.id)\
or proc.product_uom.id,
'partner_id': line.partner_address_id.id,
'location_id': line.location_src_id.id,
'location_dest_id': line.location_id.id,
'move_dest_id': proc.move_id and proc.move_id.id or False, # to verif, about history ?
'tracking_id': False,
'cancel_cascade': line.cancel_cascade,
'group_id': proc.group_id.id,
'state': 'confirmed',
'note': _('Move for pulled procurement coming from original location %s, pull rule %s, via original Procurement %s (#%d)') % (proc.location_id.name, line.name, proc.name, proc.id),
})
move_obj.button_confirm(cr,uid, [move_id], context=context)
return False

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Production -->
<record id="act_move" model="workflow.activity">
<field name="wkf_id" ref="procurement.wkf_procurement"/>
<field name="name">move</field>
<field name="kind">function</field>
<field name="action">action_move_create()</field>
</record>
<record id="trans_confirm_mto_buy01" model="workflow.transition">
<field name="act_from" ref="procurement.act_confirm_mto"/>
<field name="act_to" ref="act_move"/>
<field name="condition">check_move()</field>
</record>
<record id="trans_confirm_mto_buy02" model="workflow.transition">
<field name="act_from" ref="act_move"/>
<field name="act_to" ref="procurement.act_make_done"/>
</record>
<!-- Overwrite the default stop condition of the procurement in order to take into account its new activity
and not skipping the chained stock/move creation -->
<record id="procurement.trans_confirm_mto_make_done" model="workflow.transition">
<field name="condition">not check_produce() and not check_buy() and not check_move()</field>
</record>
</data>
</openerp>

View File

@ -103,20 +103,14 @@ class stock_location_path(osv.osv):
class product_pulled_flow(osv.osv): class product_pulled_flow(osv.osv):
_name = 'product.pulled.flow' _inherit = 'product.pulled.flow'
_description = "Pulled Flows"
_columns = { _columns = {
'name': fields.char('Name', size=64, required=True, help="This field will fill the packing Origin and the name of its moves"),
'cancel_cascade': fields.boolean('Cancel Cascade', help="Allow you to cancel moves related to the product pull flow"),
'route_id': fields.many2one('stock.location.route', 'Route'), 'route_id': fields.many2one('stock.location.route', 'Route'),
'delay': fields.integer('Number of Hours'), 'delay': fields.integer('Number of Hours'),
'location_id': fields.many2one('stock.location','Destination Location', required=True, help="Is the destination location that needs supplying"), 'location_id': fields.many2one('stock.location','Destination Location', required=True, help="Is the destination location that needs supplying"),
'location_src_id': fields.many2one('stock.location','Source Location', help="Location used by Destination Location to supply"),
'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procure Method', required=True, help="'Make to Stock': When needed, take from the stock or wait until re-supplying. 'Make to Order': When needed, purchase or produce for the procurement request."), 'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procure Method', required=True, help="'Make to Stock': When needed, take from the stock or wait until re-supplying. 'Make to Order': When needed, purchase or produce for the procurement request."),
'type_proc': fields.selection([('produce','Produce'),('buy','Buy'),('move','Move')], 'Type of Procurement', required=True), 'type_proc': fields.selection([('produce','Produce'),('buy','Buy'),('move','Move')], 'Type of Procurement', required=True),
'company_id': fields.many2one('res.company', 'Company', help="Is used to know to which company the pickings and moves belong."),
'partner_address_id': fields.many2one('res.partner', 'Partner Address'), 'partner_address_id': fields.many2one('res.partner', 'Partner Address'),
'picking_type': fields.selection([('out','Sending Goods'),('in','Getting Goods'),('internal','Internal')], 'Shipping Type', required=True, select=True, help="Depending on the company, choose whatever you want to receive or send products"),
'invoice_state': fields.selection([ 'invoice_state': fields.selection([
("invoiced", "Invoiced"), ("invoiced", "Invoiced"),
("2binvoiced", "To Be Invoiced"), ("2binvoiced", "To Be Invoiced"),
@ -124,12 +118,9 @@ class product_pulled_flow(osv.osv):
required=True,), required=True,),
} }
_defaults = { _defaults = {
'cancel_cascade': False,
'procure_method': 'make_to_stock', 'procure_method': 'make_to_stock',
'type_proc': 'move', 'type_proc': 'move',
'picking_type': 'out',
'invoice_state': 'none', 'invoice_state': 'none',
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'product.pulled.flow', context=c),
} }
# FP Note: implement this # FP Note: implement this
def _apply(self, cr, uid, rule, move, context=None): def _apply(self, cr, uid, rule, move, context=None):