openerp-expert-production team mailing list archive
-
openerp-expert-production team
-
Mailing list archive
-
Message #00316
Re: Refactoring procurements and stock booking at once
Hi,
I attach an initial patch. You can consider it a proof of concept. It doesn't
take into account several important things such as:
- Cancel procurement when purchase order line is removed or cancelled.
- Alter procurement (stock.move) quantity when the user changes the quantity
of purchase order line.
- Automatically split stock move lines when a stock move is marked as done and
other stock move lines use it *partially* as booking. It always considers the
booking is for the total amount.
- Schedule order point or schedule procurements. Currently only the button in
stock.move is working, but it's enough to get an idea.
- The system would need several checks/constraints which have not been created
yet.
Note also that the patch does not remove a lot of code. It basically adds
code. So old procurement.order may still be created because we wanted to
analyze if it was possible to create a new module (instead of a patch) for
changing the way procurements are used.
Please, try it and let me know if the initial design fits your needs. If so, I
think we should talk to OpenERP sa and try to push a fully working version
into v6.1.
A Dimarts, 22 de febrer de 2011 09:55:27, Dukai Gábor va escriure:
> Hello Albert,
>
> I very much agree with you. The current procurement object and it's
> relations are a huge source of errors. We regularly run some cleanup code
> to deal with them. In addition, they don't provide information that's not
> already available in the system.
>
> I would be glad to test your developments with our use cases.
>
>
> Best regards,
> Gábor Dukai
>
> _______________________________________________
> Mailing list: https://launchpad.net/~openerp-expert-production
> Post to : openerp-expert-production@xxxxxxxxxxxxxxxxxxx
> Unsubscribe : https://launchpad.net/~openerp-expert-production
> More help : https://help.launchpad.net/ListHelp
--
Albert Cervera i Areny
http://www.NaN-tic.com
OpenERP Partners
Tel: +34 93 553 18 03
skype: nan-oficina
http://twitter.com/albertnan
http://www.nan-tic.com/blog
=== modified file 'mrp/mrp.py'
--- mrp/mrp.py 2011-02-04 09:03:09 +0000
+++ mrp/mrp.py 2011-02-20 15:43:33 +0000
@@ -810,7 +810,7 @@
def _get_auto_picking(self, cr, uid, production):
return True
- def action_confirm(self, cr, uid, ids):
+ def action_confirm(self, cr, uid, ids, context=None):
""" Confirms production order.
@return: Newly generated picking Id.
"""
@@ -899,6 +899,10 @@
'state': 'waiting',
'company_id': production.company_id.id,
})
+
+ if line.product_id.procure_method == 'make_to_order':
+ self.pool.get('stock.move').create_procurements(cr, uid, [move_id], context)
+
proc_id = proc_obj.create(cr, uid, {
'name': (production.origin or '').split(':')[0] + ':' + production.name,
'origin': (production.origin or '').split(':')[0] + ':' + production.name,
=== modified file 'mrp/procurement.py'
--- mrp/procurement.py 2011-01-14 00:11:01 +0000
+++ mrp/procurement.py 2011-02-20 15:14:48 +0000
@@ -28,6 +28,103 @@
import netsvc
import time
+class stock_move(osv.osv):
+ _inherit = 'stock.move'
+ _columns = {
+ 'bom_id': fields.many2one('mrp.bom', 'BoM'),
+ 'property_ids': fields.many2many('mrp.property', 'stock_move_property_rel', 'move_id','property_id', 'Properties'),
+ }
+
+ def check_produce_product(self, cr, uid, procurement, context=[]):
+ """ Finds the bill of material for the product from procurement order.
+ @return: True or False
+ """
+ properties = [x.id for x in procurement.property_ids]
+ bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
+ if not bom_id:
+ cr.execute('update procurement_order set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
+ for (id, name) in self.name_get(cr, uid, procurement.id):
+ message = _("Procurement '%s' has an exception: 'No BoM defined for this product !'") % name
+ self.log(cr, uid, id, message)
+ return False
+ return True
+
+ def get_phantom_bom_id(self, cr, uid, ids, context=None):
+ for procurement in self.browse(cr, uid, ids, context=context):
+ if procurement.move_id and procurement.move_id.product_id.supply_method=='produce' \
+ and procurement.move_id.product_id.procure_method=='make_to_order':
+ phantom_bom_id = self.pool.get('mrp.bom').search(cr, uid, [
+ ('product_id', '=', procurement.move_id.product_id.id),
+ ('bom_id', '=', False),
+ ('type', '=', 'phantom')])
+ return phantom_bom_id
+ return False
+
+ 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
+ """
+ procurement_obj = self.pool.get('procurement.order')
+ res = procurement_obj.make_mo(cr, uid, ids, context=context)
+ res = res.values()
+ return len(res) and res[0] or 0
+
+ def create_production_orders(self, cr, uid, ids, context=None):
+ """ Make Manufacturing(production) order from procurement
+ @return: New created Production Orders procurement wise
+ """
+ res = {}
+ company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
+ production_obj = self.pool.get('mrp.production')
+ wf_service = netsvc.LocalService("workflow")
+ procurement_obj = self.pool.get('procurement.order')
+ for procurement in self.browse(cr, uid, ids, context=context):
+ res_id = procurement.id
+
+ if procurement.bom_id:
+ bom_id = procurement.bom_id.id
+ else:
+ bom_ids = self.pool.get('mrp.bom').search(cr, uid, [('product_id','=',procurement.product_id.id)], context=context)
+ if not bom_ids:
+ self.write(cr, uid, [procurement.id], {
+ 'procurement_error': _('No BoM defined for this product.'),
+ })
+ continue
+ bom_id = bom_ids[0]
+
+ newdate = datetime.strptime(procurement.date_expected, '%Y-%m-%d %H:%M:%S') - relativedelta(days=procurement.product_id.product_tmpl_id.produce_delay or 0.0)
+ newdate = newdate - relativedelta(days=company.manufacturing_lead)
+ produce_id = production_obj.create(cr, uid, {
+ 'origin': procurement.origin,
+ 'product_id': procurement.product_id.id,
+ 'product_qty': procurement.product_qty,
+ 'product_uom': procurement.product_uom.id,
+ 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
+ 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
+ 'location_src_id': procurement.location_dest_id.id,
+ 'location_dest_id': procurement.location_dest_id.id,
+ 'bom_id': bom_id,
+ 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
+ 'move_prod_id': res_id,
+ 'company_id': procurement.company_id.id,
+ })
+ res[procurement.id] = produce_id
+ self.write(cr, uid, [procurement.id], {
+ 'procurement_state': 'done',
+ 'procurement_error': False,
+ 'bom_id': bom_id,
+ }, context)
+ bom_result = production_obj.action_compute(cr, uid,
+ [produce_id], properties=[x.id for x in procurement.property_ids])
+ wf_service.trg_validate(uid, 'mrp.production', produce_id, 'button_confirm', cr)
+ #self.write(cr, uid, [res_id], {
+ #'location_id': procurement.location_id.id
+ #}, context)
+ self.action_cancel(cr, uid, [res_id], context)
+ return res
+
+stock_move()
+
class procurement_order(osv.osv):
_inherit = 'procurement.order'
_columns = {
=== modified file 'procurement/schedulers.py'
--- procurement/schedulers.py 2011-01-14 00:11:01 +0000
+++ procurement/schedulers.py 2011-02-20 01:21:40 +0000
@@ -56,6 +56,8 @@
ids = procurement_obj.search(cr, uid, [], order="date_planned")
for id in ids:
wf_service.trg_validate(uid, 'procurement.order', id, 'button_restart', cr)
+
+ ids = self.pool.get('stock.move').search(cr, uid, [('procurement','=',True),('procurement_state','=','pending')], context=context)
if use_new_cursor:
cr.commit()
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
@@ -67,15 +69,18 @@
report_except = 0
report_later = 0
while True:
- cr.execute("select id from procurement_order where state='confirmed' and procure_method='make_to_order' order by priority,date_planned limit 500 offset %s", (offset,))
+ #cr.execute("select id from procurement_order where state='confirmed' and procure_method='make_to_order' order by priority,date_planned limit 500 offset %s", (offset,))
+ cr.execute("select id from stock_move where procurement and procurement_state='pending' order by priority,date_expected limit 500 offset %s", (offset,))
ids = map(lambda x: x[0], cr.fetchall())
- for proc in procurement_obj.browse(cr, uid, ids, context=context):
- if maxdate >= proc.date_planned:
- wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_check', cr)
+ for proc in self.pool.get('stock.move').browse(cr, uid, ids, context=context):
+ if maxdate >= proc.date_expected:
+ # TODO: Analyze what to do with procurements in stock
+ #wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_check', cr)
+ self.pool.get('stock.move').process_procurements(cr, uid, [proc.id], context)
else:
offset += 1
report_later += 1
- for proc in procurement_obj.browse(cr, uid, ids, context=context):
+ for proc in self.pool.get('stock.move').browse(cr, uid, ids, context=context):
if proc.state == 'exception':
report.append('PROC %d: on order - %3.2f %-5s - %s' % \
(proc.id, proc.product_qty, proc.product_uom.name,
@@ -92,7 +97,7 @@
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:
+ if maxdate >= proc.date_expected:
wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_check', cr)
report_ids.append(proc.id)
else:
=== modified file 'purchase/purchase.py'
--- purchase/purchase.py 2011-02-10 18:48:06 +0000
+++ purchase/purchase.py 2011-02-20 02:30:39 +0000
@@ -425,7 +425,7 @@
self.log(cr, uid, id, message)
return True
- def action_picking_create(self,cr, uid, ids, *args):
+ def action_picking_create(self,cr, uid, ids, context=None):
picking_id = False
for order in self.browse(cr, uid, ids):
loc_id = order.partner_id.property_stock_supplier.id
@@ -447,6 +447,19 @@
for order_line in order.order_line:
if not order_line.product_id:
continue
+
+ move_ids = []
+ source_booking_ids = []
+ target_booking_ids = []
+ for move in order_line.move_ids:
+ source_booking_ids += [x.id for x in move.source_booking_ids]
+ target_booking_ids += [x.id for x in move.target_booking_ids]
+ move_ids.append( move.id )
+
+ self.pool.get('stock.move').write(cr, uid, move_ids, {
+ 'state': 'cancel',
+ }, context)
+
if order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
dest = order.location_id.id
move = self.pool.get('stock.move').create(cr, uid, {
@@ -465,7 +478,10 @@
'state': 'draft',
'purchase_line_id': order_line.id,
'company_id': order.company_id.id,
- 'price_unit': order_line.price_unit
+ 'price_unit': order_line.price_unit,
+ 'source_booking_ids': [(6,0,source_booking_ids)],
+ 'target_booking_ids': [(6,0,target_booking_ids)],
+
})
if order_line.move_dest_id:
self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
@@ -744,6 +760,88 @@
purchase_order_line()
+class stock_move(osv.osv):
+ _inherit = 'stock.move'
+
+ def create_purchase_order(self, cr, uid, ids, context=None):
+ """ Make purchase order from procurement
+ @return: New created Purchase Orders procurement wise
+ """
+ res = {}
+ if context is None:
+ context = {}
+ company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
+ partner_obj = self.pool.get('res.partner')
+ uom_obj = self.pool.get('product.uom')
+ pricelist_obj = self.pool.get('product.pricelist')
+ prod_obj = self.pool.get('product.product')
+ acc_pos_obj = self.pool.get('account.fiscal.position')
+ po_obj = self.pool.get('purchase.order')
+ print "HERE!"
+ for procurement in self.browse(cr, uid, ids, context=context):
+ #res_id = procurement.move_id.id
+ partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
+ seller_qty = procurement.product_id.seller_qty
+ seller_delay = int(procurement.product_id.seller_delay)
+ partner_id = partner.id
+ address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
+ pricelist_id = partner.property_product_pricelist_purchase.id
+
+ uom_id = procurement.product_id.uom_po_id.id
+
+ qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
+ if seller_qty:
+ qty = max(qty,seller_qty)
+
+ price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
+
+ newdate = datetime.strptime(procurement.date_expected, '%Y-%m-%d %H:%M:%S')
+ newdate = (newdate - relativedelta(days=company.po_lead)) - relativedelta(days=seller_delay)
+
+ #Passing partner_id to context for purchase order line integrity of Line name
+ context.update({'lang': partner.lang, 'partner_id': partner_id})
+
+ product = prod_obj.browse(cr, uid, procurement.product_id.id, context=context)
+
+ line = {
+ 'name': product.partner_ref,
+ 'product_qty': qty,
+ 'product_id': procurement.product_id.id,
+ 'product_uom': uom_id,
+ 'price_unit': price,
+ 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
+ # TODO: Remove ???
+ 'move_dest_id': procurement.id,
+ 'notes': product.description_purchase,
+ }
+
+ taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
+ taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
+ line.update({
+ 'taxes_id': [(6,0,taxes)]
+ })
+ purchase_id = po_obj.create(cr, uid, {
+ 'origin': procurement.origin,
+ 'partner_id': partner_id,
+ 'partner_address_id': address_id,
+ # TODO: What should be set in 'location_id'
+ 'location_id': procurement.location_dest_id.id,
+ 'pricelist_id': pricelist_id,
+ #'order_line': [(0,0,line)],
+ 'company_id': procurement.company_id.id,
+ 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
+ })
+ line['order_id'] = purchase_id
+ purchase_line_id = self.pool.get('purchase.order.line').create(cr, uid, line, context)
+
+ res[procurement.id] = purchase_id
+ self.write(cr, uid, [procurement.id], {
+ 'procurement_state': 'done',
+ 'purchase_line_id': purchase_line_id,
+ }, context)
+ return res
+stock_move()
+
class procurement_order(osv.osv):
_inherit = 'procurement.order'
_columns = {
=== modified file 'sale/sale.py'
--- sale/sale.py 2011-01-19 08:38:17 +0000
+++ sale/sale.py 2011-02-20 15:16:09 +0000
@@ -650,7 +650,7 @@
return False
return canceled
- def action_ship_create(self, cr, uid, ids, *args):
+ def action_ship_create(self, cr, uid, ids, context=None):
wf_service = netsvc.LocalService("workflow")
picking_id = False
move_obj = self.pool.get('stock.move')
@@ -706,6 +706,9 @@
'note': line.notes,
'company_id': order.company_id.id,
})
+ # TODO: Consider services
+ if line.type == 'make_to_order':
+ self.pool.get('stock.move').create_procurements(cr, uid, [move_id], context)
if line.product_id:
proc_id = self.pool.get('procurement.order').create(cr, uid, {
=== modified file 'stock/stock.py'
--- stock/stock.py 2011-01-20 14:02:14 +0000
+++ stock/stock.py 2011-02-20 18:52:27 +0000
@@ -1554,6 +1554,17 @@
# used for colors in tree views:
'scrapped': fields.related('location_dest_id','scrap_location',type='boolean',relation='stock.location',string='Scrapped', readonly=True),
+
+ # Procurement-related fields
+
+ # TODO: Add procurement.order's property_ids for production
+ 'procurement': fields.boolean('Procurement?', readonly=True, help='Check this field if the procurement must be processed.'),
+ 'procurement_state': fields.selection([('pending','Pending'),('done','Done')], 'Procurement State', required=True, readonly=True, help=''),
+ 'procurement_error': fields.char('Procurement Error', size=500, readonly=True, help='Reason why the procurement could not be processed.'),
+
+ 'source_booking_ids': fields.one2many('stock.booking', 'target_move_id', 'Source Bookings', help='If stock for this move was reserved before execution, here there are the references to source stock moves.'),
+ 'target_booking_ids': fields.one2many('stock.booking', 'source_move_id', 'Target Bookings', help='Stock moves for which current move will provide the products.'),
+
}
_constraints = [
(_check_tracking,
@@ -1608,6 +1619,9 @@
'date': time.strftime('%Y-%m-%d %H:%M:%S'),
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.move', context=c),
'date_expected': time.strftime('%Y-%m-%d %H:%M:%S'),
+
+ # Procurement-related fields
+ 'procurement_state': lambda *a: 'done',
}
def write(self, cr, uid, ids, vals, context=None):
@@ -2138,6 +2152,9 @@
if move.state not in ('confirmed','done'):
self.action_confirm(cr, uid, move_ids, context=context)
+
+ self.process_target_bookings(cr, uid, move_ids, context)
+
self.write(cr, uid, move_ids, {'state': 'done', 'date_planned': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
for id in move_ids:
wf_service.trg_trigger(uid, 'stock.move', id, cr)
@@ -2147,6 +2164,95 @@
return True
+ def process_target_bookings(self, cr, uid, ids, context):
+ for move in self.browse(cr, uid, ids, context):
+ for booking in move.target_booking_ids:
+ # TODO: Ensure it's executed if user forces (presses 'Ejecutar Ahora' in stock.move.form).
+ # TODO: Take into account booking's quantity and uom.
+ # TODO: Ensure there are no infinite loops due to booking cycles.
+ # TODO: Move production lots automatically.
+ self.check_assign(cr, uid, [booking.target_move_id.id], context)
+
+ def create_purchase_orders(self, cr, uid, ids, context):
+ self.write(cr, uid, [ids], {
+ 'procurement_error': _('Can not process products of type purchase: missing module.'),
+ }, context)
+ return False
+
+ def create_production_orders(self, cr, uid, ids, context):
+ self.write(cr, uid, [ids], {
+ 'procurement_error': _('Can not process products of type production: missing module.'),
+ }, context)
+ return False
+
+ def process_procurements(self, cr, uid, ids, context):
+ user = self.pool.get('res.users').browse(cr, uid, uid, context)
+ for move in self.browse(cr, uid, ids, context):
+ if not move.procurement:
+ continue
+ if not move.product_id.type in ('product','consu'):
+ continue
+ if move.procurement_state == 'done':
+ continue
+ if move.product_id.supply_method == 'buy':
+ # TODO: These checks should be moved to purchase module
+ if not move.product_id.seller_ids:
+ self.write(cr, uid, [move.id], {
+ 'procurement_error': _('No supplier defined for this product !'),
+ }, context)
+ continue
+ partner = move.product_id.seller_id #Taken Main Supplier of Product of Procurement.
+ if user.company_id and user.company_id.partner_id:
+ if partner.id == user.company_id.partner_id.id:
+ continue
+ address_id = self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['delivery'])['delivery']
+ if not address_id:
+ self.write(cr, uid, [move.id], {
+ 'procurement_error': _("No address defined for product's supplier."),
+ }, context)
+ continue
+ self.create_purchase_order(cr, uid, [move.id], context)
+ self.write(cr, uid, [move.id], {
+ 'procurement_state': 'done',
+ 'procurement_error': False,
+ }, context)
+ else:
+ self.create_production_orders(cr, uid, [move.id], context)
+ return True
+
+ def create_procurements(self, cr, uid, ids, context):
+ new_move_ids = []
+ for move in self.browse(cr, uid, ids, context):
+ new_move = self.create(cr, uid, {
+ 'product_id': move.product_id.id,
+ 'name': move.name,
+ 'location_id': move.product_id.property_stock_procurement.id,
+ 'location_dest_id': move.location_id.id,
+ 'date': move.date_expected,
+ 'date_expected': move.date_expected,
+ 'product_qty': move.product_qty,
+ 'product_uom': move.product_uom.id,
+ 'product_uos_qty': move.product_uos_qty,
+ 'product_uos': (move.product_uos and move.product_uos.id) or move.product_uom.id,
+ 'company_id': move.company_id.id,
+ 'address_id': move.address_id.id,
+ 'state': 'confirmed',
+
+ # Procurement-related fields
+ 'procurement': True,
+ 'procurement_state': 'pending',
+ }, context)
+
+ self.pool.get('stock.booking').create(cr, uid, {
+ 'source_move_id': new_move,
+ 'target_move_id': move.id,
+ 'quantity': move.product_qty,
+ 'uom_id': move.product_uom.id,
+ }, context)
+ new_move_ids.append( new_move )
+
+ return new_move_ids
+
def _create_account_move_line(self, cr, uid, move, src_account_id, dest_account_id, reference_amount, reference_currency_id, context=None):
"""
Generate the account.move.line values to post to track the stock valuation difference due to the
@@ -2502,6 +2608,37 @@
stock_move()
+class stock_booking(osv.osv):
+ _name = 'stock.booking'
+ _description = 'Stock Booking'
+
+ _columns = {
+ 'source_move_id': fields.many2one('stock.move', 'Source Stock Move', required=True, ondelete='cascade', help='', domain="[('state','in',['draft','confirmed','assigned','waiting'])]"),
+ 'target_move_id': fields.many2one('stock.move', 'Target Stock Move', required=True, ondelete='cascade', help='', domain="[('state','in',['draft','confirmed','assigned','waiting'])]"),
+ 'quantity': fields.float('Quantity', required=True, help=''),
+ 'uom_id': fields.many2one('product.uom', 'UoM', required=True, help=''),
+ 'product_id': fields.related('source_move_id','product_id', type='many2one', relation='product.product', string='Product', readonly=True),
+ }
+
+ def _check_quantity(self, cr, uid, ids):
+ # TODO: Ensure source_move_id and target_move_id refer to the same product.
+ # TODO: Ensure source_move_id does not have more reserved stock than it's quantity (UoM!).
+ # TODO: Ensure target_move_id does not have more reserved stock than it's quantity.
+ # TODO: Ensure booking's quantity is not greater than source_move_id's quantity.
+ # TODO: Ensure booking's quantity is not greater than target_move_id's quantity.
+ # TODO: Ensure source_move_id.location_dest_id == target_move_id.location_id
+ # TODO: Ensure both moves have the same company_id (this is not checked anywhere else in OpenERP, do we want it here?)
+ return True
+
+ _constraints = [
+ (_check_quantity, 'Incorrect Quantity.', [])
+ ]
+
+ # TODO: Take into account what should be done if source_move_id is duplicated. (copy)
+ # TODO: Take into account what should be done if target_move_id is duplicated. (copy)
+
+stock_booking()
+
class stock_inventory(osv.osv):
_name = "stock.inventory"
_description = "Inventory"
=== modified file 'stock/stock_view.xml'
--- stock/stock_view.xml 2011-01-18 17:14:00 +0000
+++ stock/stock_view.xml 2011-02-20 17:04:19 +0000
@@ -1470,6 +1470,48 @@
string="Split" type="action" icon="terp-stock_effects-object-colorize" colspan="1"/>
</group>
+ <group colspan="2" col="1" groups="base.group_extended">
+ <separator string="Back Bookings"/>
+ <field name="source_booking_ids" nolabel="1">
+ <tree string="Stock Booking">
+ <field name="source_move_id"/>
+ <field name="quantity"/>
+ <field name="uom_id"/>
+ </tree>
+ <form string="Stock Booking">
+ <field name="source_move_id"/>
+ <field name="quantity"/>
+ <field name="uom_id"/>
+ </form>
+ </field>
+ </group>
+
+ <group colspan="2" col="1" groups="base.group_extended">
+ <separator string="Forward Bookings"/>
+ <field name="target_booking_ids" nolabel="1">
+ <tree string="Stock Booking">
+ <field name="target_move_id"/>
+ <field name="quantity"/>
+ <field name="uom_id"/>
+ </tree>
+ <form string="Stock Booking">
+ <field name="target_move_id"/>
+ <field name="quantity"/>
+ <field name="uom_id"/>
+ </form>
+ </field>
+ </group>
+
+ <group colspan="4" groups="base.group_extended">
+ <separator string="Procurement Information" colspan="4"/>
+ <group colspan="4" col="5">
+ <field name="procurement"/>
+ <field name="procurement_state"/>
+ <button type="object" name="process_procurements" string="Process Procurement" icon="gtk-convert"/>
+ </group>
+ <field name="procurement_error" colspan="4"/>
+ </group>
+
<separator colspan="4"/>
<field name="state"/>
<group col="4" colspan="2">
@@ -1607,6 +1649,42 @@
</field>
</record>
+ <record id="view_move_tree_booking" model="ir.ui.view">
+ <field name="name">stock.booking.tree</field>
+ <field name="model">stock.booking</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Stock Booking">
+ <field name="product_id"/>
+ <field name="source_move_id"/>
+ <field name="target_move_id"/>
+ <field name="quantity"/>
+ <field name="uom_id"/>
+ </tree>
+ </field>
+ </record>
+ <record id="view_move_form_booking" model="ir.ui.view">
+ <field name="name">stock.booking.form</field>
+ <field name="model">stock.booking</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Stock Booking">
+ <field name="product_id" colspan="4"/>
+ <field name="source_move_id"/>
+ <field name="target_move_id"/>
+ <field name="quantity"/>
+ <field name="uom_id"/>
+ </form>
+ </field>
+ </record>
+ <record id="action_booking_form" model="ir.actions.act_window">
+ <field name="name">Stock Booking</field>
+ <field name="res_model">stock.booking</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem action="action_booking_form" id="menu_action_booking_form" parent="menu_traceability" sequence="4"/>
<record id="view_move_form_reception_picking" model="ir.ui.view">
<field name="name">stock.move.form2</field>
References