← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~camptocamp/openerp-humanitarian-ngo/purchase-wkfl-fix-js into lp:openerp-humanitarian-ngo

 

Nicolas Bessi - Camptocamp has proposed merging lp:~camptocamp/openerp-humanitarian-ngo/purchase-wkfl-fix-js into lp:openerp-humanitarian-ngo.

Requested reviews:
  OpenERP for Humanitarian Core Editors (humanitarian-core-editors)

For more details, see:
https://code.launchpad.net/~camptocamp/openerp-humanitarian-ngo/purchase-wkfl-fix-js/+merge/246901

Fix javascript to be resilient to return values
-- 
Your team OpenERP for Humanitarian Core Editors is requested to review the proposed merge of lp:~camptocamp/openerp-humanitarian-ngo/purchase-wkfl-fix-js into lp:openerp-humanitarian-ngo.
=== added directory 'purchase_extended'
=== added file 'purchase_extended/__init__.py'
--- purchase_extended/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_extended/__init__.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+from . import model
+from . import wizard

=== added file 'purchase_extended/__openerp__.py'
--- purchase_extended/__openerp__.py	1970-01-01 00:00:00 +0000
+++ purchase_extended/__openerp__.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Copyright 2013 Camptocamp SA
+#
+#    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/>.
+#
+##############################################################################
+
+{"name": "Purchase Extended",
+ "version": "0.1",
+ "author": "Camptocamp",
+ "category": "Purchase Management",
+ "license": "AGPL-3",
+ 'complexity': "normal",
+ "images": [],
+ "description": """
+This module improves the standard Purchase module.
+==================================================
+In standard, RFQs, Bids and PO are all the same object.  The purchase workflow
+has been improved with a new 'Draft PO' state to clearly differentiate the
+RFQ->Bid workflow and the PO workflow. A type field has also been added to
+identify if a document is of type 'rfq' or 'purchase'. This is particularly
+usefull for canceled state and for datawarehouse.
+
+The 'Requests for Quotation' menu entry shows only documents of type 'rfq' and
+the new documents are created in state 'Draft RFQ'. Those documents have lines
+with a price, by default, set to 0; it will have to be encoded when the bid is
+received. The state 'Bid Received' has been renamed 'Bid Encoded'. This clearly
+indicates that the price has been filled in. The bid received date will be
+requested when moving to that state.
+
+The 'Purchase Orders' menu entry shows only documents of type 'purchase' and
+the new documents are created in state 'Draft PO'.
+
+The logged messages have been improved to notify users at the state changes and
+with the right naming.
+
+
+In the scope of internation transactions, some fields have been added:
+ - Consignee: the person to whom the shipment is to be delivered
+ - Incoterms Place: the standard incoterms field specifies the incoterms rule
+   that applies. This field allows to name the place where the goods will be
+   available
+
+TODO: describe onchange warehouse
+""",
+ "depends": ["purchase",
+             ],
+    "demo": [],
+    "data": ["view/purchase_order.xml",
+             "view/purchase_cancel.xml",
+             "data/purchase_order.xml",
+             "data/purchase.cancelreason.yml",
+             "workflow/purchase_order.xml",
+             "wizard/modal.xml",
+             "wizard/action_cancel_reason.xml",
+             "security/ir.model.access.csv",
+             ],
+    "auto_install": False,
+    "test": ["test/process/rfq2order.yml",
+             "test/process/bid2order.yml",
+             "test/process/po2order.yml",
+             "test/process/rfq2cancel.yml",
+             ],
+    "installable": True,
+    "certificate": "",
+ }

=== added directory 'purchase_extended/data'
=== added file 'purchase_extended/data/purchase.cancelreason.yml'
--- purchase_extended/data/purchase.cancelreason.yml	1970-01-01 00:00:00 +0000
+++ purchase_extended/data/purchase.cancelreason.yml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,30 @@
+-
+  !context {noupdate: True}
+-
+  Creating the cancel reasons
+-
+  !record {model: purchase.cancelreason, id: purchase_cancelreason_rfq_canceled}:
+    name: RFQ canceled
+    type: rfq
+    nounlink: 1
+-
+  !record {model: purchase.cancelreason, id: purchase_cancelreason_bid_regretfromsupplier}:
+    name: Regret from supplier
+    type: rfq
+-
+  !record {model: purchase.cancelreason, id: purchase_cancelreason_bid_noreply}:
+    name: No reply
+    type: rfq
+-
+  !record {model: purchase.cancelreason, id: purchase_cancelreason_bid_latereply}:
+    name: Late reply
+    type: rfq
+-
+  !record {model: purchase.cancelreason, id: purchase_cancelreason_bid_bidnotselected}:
+    name: Bid not selected
+    type: rfq
+-
+  !record {model: purchase.cancelreason, id: purchase_cancelreason_purchase_canceled}:
+    name: PO canceled
+    type: purchase
+    nounlink: 1

=== added file 'purchase_extended/data/purchase_order.xml'
--- purchase_extended/data/purchase_order.xml	1970-01-01 00:00:00 +0000
+++ purchase_extended/data/purchase_order.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+<data>
+    <record id="purchase.mt_rfq_confirmed" model="mail.message.subtype">
+        <field name="name">Purchase Order confirmed</field>
+        <field name="default" eval="False"/>
+        <field name="res_model">purchase.order</field>
+    </record>
+</data>
+</openerp>

=== added directory 'purchase_extended/model'
=== added file 'purchase_extended/model/__init__.py'
--- purchase_extended/model/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_extended/model/__init__.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+import purchase_order
+import purchase_cancel

=== added file 'purchase_extended/model/purchase_cancel.py'
--- purchase_extended/model/purchase_cancel.py	1970-01-01 00:00:00 +0000
+++ purchase_extended/model/purchase_cancel.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from openerp.osv import fields, orm
+from openerp.tools.translate import _
+
+
+class purchase_cancel(orm.Model):
+    _name = "purchase.cancelreason"
+    _columns = {
+        'name': fields.char('Reason', size=64, required=True, translate=True),
+        'type': fields.selection([('rfq', 'RFQ/Bid'), ('purchase', 'Purchase Order')], 'Type', required=True),
+        'nounlink': fields.boolean('No unlink'),
+    }
+
+    def unlink(self, cr, uid, ids, context=None):
+        """ Prevent to unlink records that are used in the code
+        """
+        unlink_ids = []
+        for value in self.read(cr, uid, ids, ['nounlink'], context=context):
+            if not value['nounlink']:
+                unlink_ids.append(value['id'])
+        if unlink_ids:
+            return super(purchase_cancel, self).unlink(cr, uid, unlink_ids, context=context)
+        return True

=== added file 'purchase_extended/model/purchase_order.py'
--- purchase_extended/model/purchase_order.py	1970-01-01 00:00:00 +0000
+++ purchase_extended/model/purchase_order.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,264 @@
+# -*- coding: utf-8 -*-
+
+from openerp.osv import fields, orm
+from openerp import netsvc
+from openerp.tools.translate import _
+from openerp import SUPERUSER_ID
+
+
+class PurchaseOrder(orm.Model):
+    _inherit = "purchase.order"
+
+    STATE_SELECTION = [
+        ('draft', 'Draft RFQ'),
+        ('sent', 'RFQ Sent'),
+        ('draftbid', 'Draft Bid'),  # added
+        ('bid', 'Bid Encoded'),  # Bid Received renamed into Bid Encoded
+        ('bid_selected', 'Bid selected'), # added
+        ('draftpo', 'Draft PO'),  # added
+        ('confirmed', 'Waiting Approval'),
+        ('approved', 'Purchase Confirmed'),
+        ('except_picking', 'Shipping Exception'),
+        ('except_invoice', 'Invoice Exception'),
+        ('done', 'Done'),
+        ('cancel', 'Canceled')
+    ]
+    TYPE_SELECTION = [
+        ('rfq', 'Request for Quotation'),
+        ('bid', 'Bid'),
+        ('purchase', 'Purchase Order')
+    ]
+
+    _columns = {
+        'state': fields.selection(STATE_SELECTION, 'Status', readonly=True, select=True,
+            help="The status of the purchase order or the quotation request. A "
+                 "quotation is a purchase order in a 'Draft' status. Then the order "
+                 "has to be confirmed by the user, the status switch to 'Confirmed'. "
+                 "Then the supplier must confirm the order to change the status to "
+                 "'Approved'. When the purchase order is paid and received, the "
+                 "status becomes 'Done'. If a cancel action occurs in the invoice or "
+                 "in the reception of goods, the status becomes in exception."),
+        'type': fields.selection(TYPE_SELECTION, 'Type', required=True, readonly=True),
+        'consignee_id': fields.many2one('res.partner', 'Consignee', help="the person to whom the shipment is to be delivered"),
+        'incoterm_address': fields.char(
+            'Incoterms Place',
+            help="Incoterms Place of Delivery. "
+                 "International Commercial Terms are a series of "
+                 "predefined commercial terms used in "
+                 "international transactions."),
+        'cancel_reason_id': fields.many2one('purchase.cancelreason', 'Reason for Cancellation', readonly=True),
+    }
+    _defaults = {
+        'state': lambda self, cr, uid, context: 'draftpo' if context.get('draft_po')
+                                          else 'draftbid' if context.get('draft_bid')
+                                          else 'draft',
+        'type': lambda self, cr, uid, context: 'purchase' if context.get('draft_po')
+                                          else 'bid' if context.get('draft_bid')
+                                          else 'rfq',
+    }
+
+    def create(self, cr, uid, vals, context=None):
+        # Document can be created as Draft RFQ or Draft PO. We need to log the right message.
+        if context is None:
+            context = {}
+        description = self._description
+        if context.get('draft_bid'):
+            self._description = 'Draft Bid'
+        elif not context.get('draft_po'):
+            self._description = 'Request for Quotation'
+        id = super(PurchaseOrder, self).create(cr, uid, vals, context=context)
+        self._description = description
+        if context.get('draft_bid'):
+            wf_service = netsvc.LocalService("workflow")
+            wf_service.trg_validate(uid, 'purchase.order', id, 'draft_bid', cr)
+        if context.get('draft_po'):
+            wf_service = netsvc.LocalService("workflow")
+            wf_service.trg_validate(uid, 'purchase.order', id, 'draft_po', cr)
+        return id
+
+    def copy(self, cr, uid, id, default=None, context=None):
+        newid = super(PurchaseOrder, self).copy(cr, uid, id, default=default, context=context)
+        po = self.read(cr, SUPERUSER_ID, newid, ['type', 'order_line'], context=context, load='_classic_write')
+        if po['type'] == 'rfq' and po['order_line']:
+            self.pool.get('purchase.order.line').write(cr, SUPERUSER_ID, po['order_line'], {'price_unit': 0}, context=context)
+        return newid
+
+    def wkf_draft_po(self, cr, uid, ids, context=None):
+        self.message_post(cr, uid, ids, body=_("Converted to draft Purchase Order"), subtype="mail.mt_comment", context=context)
+        return self.write(cr, uid, ids, {'state': 'draftpo', 'type': 'purchase'}, context=context)
+
+    def action_cancel(self, cr, uid, ids, context=None):
+        """ Ask a cancel reason
+        """
+        if context is None:
+            context = {}
+        context['action'] = 'action_cancel_ok'
+        view_id = self.pool.get('ir.model.data').get_object_reference(cr, SUPERUSER_ID, 'purchase_extended', 'action_modal_cancel_reason')[1]
+        #TODO: filter based on po type
+        return {
+            'type': 'ir.actions.act_window',
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'purchase.action_modal_cancelreason',
+            'view_id': view_id,
+            'views': [(view_id, 'form')],
+            'target': 'new',
+            'context': context,
+        }
+
+    def action_cancel_no_reason(self, cr, uid, ids, context=None):
+        return super(PurchaseOrder, self).action_cancel(cr, uid, ids,
+                                                        context=context)
+
+    def action_cancel_ok(self, cr, uid, ids, context=None):
+        reason_id = self.pool.get('purchase.action_modal_cancelreason').read(cr, uid,
+                                context['active_id'], ['reason_id'], context=context,
+                                load='_classic_write')['reason_id']
+        self.write(cr, uid, ids, {'cancel_reason_id': reason_id}, context=context)
+        return super(PurchaseOrder, self).action_cancel(cr, uid, ids, context=context)
+
+    def purchase_cancel(self, cr, uid, ids, context=None):
+        """ Ask a cancel reason
+        """
+        if context is None:
+            context = {}
+        ctx = context.copy()
+        ctx['action'] = 'purchase_cancel_ok'
+        for e in ('active_model', 'active_ids', 'active_id'):  # those will be set by the web layer unless they are already defined
+            if e in ctx:
+                del ctx[e]
+        view_id = self.pool.get('ir.model.data').get_object_reference(cr, SUPERUSER_ID, 'purchase_extended', 'action_modal_cancel_reason')[1]
+        #TODO: filter based on po type
+        return {
+            'type': 'ir.actions.act_window',
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'purchase.action_modal_cancelreason',
+            'view_id': view_id,
+            'views': [(view_id, 'form')],
+            'target': 'new',
+            'context': ctx,
+        }
+
+    def purchase_cancel_ok(self, cr, uid, ids, context=None):
+        reason_id = self.pool.get('purchase.action_modal_cancelreason').read(cr, uid,
+                                context['active_id'], ['reason_id'], context=context,
+                                load='_classic_write')['reason_id']
+        self.write(cr, uid, ids, {'cancel_reason_id': reason_id}, context=context)
+        for id in ids:
+            wf_service = netsvc.LocalService("workflow")
+            wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
+        return {}
+
+    def wkf_action_cancel(self, cr, uid, ids, context=None):
+        for element in self.browse(cr, uid, ids, context=context):
+            if element.state in ('draft', 'sent'):
+                message = _("Request for Quotation")
+            elif element.state == 'bid':
+                message = _("Bid")
+            else:
+                message = self._description
+            message += " " + _("canceled")
+            self.message_post(cr, uid, [element.id], body=message, subtype="mail.mt_comment", context=context)
+        return super(PurchaseOrder, self).wkf_action_cancel(cr, uid, ids, context=context)
+
+    def bid_received(self, cr, uid, ids, context=None):
+        assert len(ids) == 1, 'This action should only be used for a single id at a time'
+        if context is None:
+            context = {}
+        order = self.read(cr, uid, ids[0], ['bid_date'], context=context)
+        ctx = context.copy()
+        ctx.update({
+            'action': 'bid_received_ok',
+            'default_datetime': order['bid_date'] or fields.date.context_today(self, cr, uid, context=context),
+        })
+        for e in ('active_model', 'active_ids', 'active_id'):  # those will be set by the web layer unless they are already defined
+            if e in ctx:
+                del ctx[e]
+        view_id = self.pool.get('ir.model.data').get_object_reference(cr, SUPERUSER_ID, 'purchase_extended', 'action_modal_bid_date')[1]
+        return {
+            'type': 'ir.actions.act_window',
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'purchase.action_modal_datetime',
+            'view_id': view_id,
+            'views': [(view_id, 'form')],
+            'target': 'new',
+            'context': ctx,
+        }
+
+    def bid_received_ok(self, cr, uid, ids, context=None):
+        # TODO: send warning if not all lines have a price
+        value = self.pool.get('purchase.action_modal_datetime').read(cr, uid, context['active_id'], ['datetime'], context=context)['datetime']
+        self.write(cr, uid, ids, {'bid_date': value}, context=context)
+        self.message_post(cr, uid, ids, body=_("Bid received and encoded"), subtype="mail.mt_comment", context=context)
+        for id in ids:
+            wf_service = netsvc.LocalService("workflow")
+            wf_service.trg_validate(uid, 'purchase.order', id, 'bid_received', cr)
+        return {}
+
+    def wkf_bid_received(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'state': 'bid', 'type': 'bid'}, context=context)
+
+    def _has_lines(self, cr, uid, ids, context=None):
+        for rfq in self.browse(cr, uid, ids, context=context):
+            if not rfq.order_line:
+                return False
+        return True
+
+    def wkf_send_rfq(self, cr, uid, ids, context=None):
+        if not self._has_lines(cr, uid, ids, context=context):
+            raise orm.except_orm(_('Error!'), _('You cannot send a Request for Quotation without any product line.'))
+        return super(PurchaseOrder, self).wkf_send_rfq(cr, uid, ids, context=context)
+
+    def print_quotation(self, cr, uid, ids, context=None):
+        if not self._has_lines(cr, uid, ids, context=context):
+            raise orm.except_orm(_('Error!'), _('You cannot print a Request for Quotation without any product line.'))
+        self.message_post(cr, uid, ids, body=_("Request for Quotation printed"), subtype="mail.mt_comment", context=context)
+        return super(PurchaseOrder, self).print_quotation(cr, uid, ids, context=context)
+
+    def onchange_dest_address_id_mod(self, cr, uid, ids, dest_address_id,
+                                     warehouse_id, context=None):
+        value = self.onchange_dest_address_id(cr, uid, ids, dest_address_id)
+        warehouse_obj = self.pool.get('stock.warehouse')
+        dest_ids = warehouse_obj.search(cr, uid,
+                                        [('partner_id', '=', dest_address_id)],
+                                        context=context)
+        if dest_ids:
+            if warehouse_id not in dest_ids:
+                warehouse_id = dest_ids[0]
+        else:
+            warehouse_id = False
+        value['value']['warehouse_id'] = warehouse_id
+        return value
+
+    def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):
+        value = super(PurchaseOrder, self).onchange_warehouse_id(cr, uid, ids, warehouse_id)
+        if not warehouse_id:
+            return {}
+        warehouse_obj = self.pool.get('stock.warehouse')
+        dest_id = warehouse_obj.browse(cr, uid, warehouse_id, context=context).partner_id.id
+        value['value']['dest_address_id'] = dest_id
+        return value
+
+    def po_tender_requisition_selected(self, cr, uid, ids, context=None):
+        """Workflow function that write state 'bid selected'"""
+        return self.write(cr, uid, ids, {'state': 'bid_selected'},
+                          context=context)
+
+
+class purchase_order_line(orm.Model):
+    _inherit = 'purchase.order.line'
+
+    def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
+            partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
+            name=False, price_unit=False, context=None, state='draftpo', type='purchase', **kwargs):
+        res = super(purchase_order_line, self).onchange_product_id(cr, uid, ids,
+                pricelist_id, product_id, qty, uom_id, partner_id, date_order,
+                fiscal_position_id, date_planned, name, price_unit, context)
+        if state == 'draft' and type == 'rfq':
+            res['value'].update({'price_unit': 0})
+        elif state in ('sent', 'draftbid', 'bid'):
+            if 'price_unit' in res['value']:
+                del res['value']['price_unit']
+        return res

=== added directory 'purchase_extended/security'
=== added file 'purchase_extended/security/ir.model.access.csv'
--- purchase_extended/security/ir.model.access.csv	1970-01-01 00:00:00 +0000
+++ purchase_extended/security/ir.model.access.csv	2015-01-19 14:42:10 +0000
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_purchase_cancelreason_user,access_purchase_cancelreason,model_purchase_cancelreason,purchase.group_purchase_user,1,0,0,0
+access_purchase_cancelreason_manager,access_purchase_cancelreason,model_purchase_cancelreason,purchase.group_purchase_manager,1,1,1,1

=== added directory 'purchase_extended/test'
=== added directory 'purchase_extended/test/process'
=== added file 'purchase_extended/test/process/bid2cancel.yml'
--- purchase_extended/test/process/bid2cancel.yml	1970-01-01 00:00:00 +0000
+++ purchase_extended/test/process/bid2cancel.yml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,79 @@
+-
+  Cancel a new RFQ.
+-
+  Create RFQ
+-
+  !record {model: purchase.order, id: purchase_order_ext_bid2cancel1}:
+    partner_id: base.res_partner_1
+    invoice_method: order
+    date_order: '2013-08-02'
+    bid_validity: '2013-08-15'
+    order_line:
+      - product_id: product.product_product_15
+        product_qty: 15.0
+        date_planned: '2013-08-30'
+      - product_id: product.product_product_25
+        product_qty: 5.0
+      - product_id: product.product_product_27
+        product_qty: 4.0
+-
+  I print the RFQ.
+-
+  !python {model: purchase.order}: |
+    self.print_quotation(cr, uid, [ref("purchase_order_ext_bid2cancel1")])
+-
+  I run the 'Cancel' wizard. I fill the reason.
+-
+  !record {model: purchase.action_modal_cancelreason, id: purchase_order_ext_bid2cancel1_cancelreason}:
+    reason_id: purchase_cancelreason_rfq_canceled
+-
+  I run the 'Cancel' wizard. I confirm the wizard.
+-
+  !python {model: purchase.order}: |
+    self.purchase_cancel_ok(cr, uid, [ref("purchase_order_ext_bid2cancel1")],{'active_id':ref("purchase_order_ext_bid2cancel1_cancelreason")})
+-
+  I check the "Canceled" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_bid2cancel1}:
+    - type == 'rfq'
+    - state == 'cancel'
+    - cancel_reason_id.id == ref("purchase_cancelreason_rfq_canceled")
+-
+-
+  Cancel a new Bid.
+-
+  Create a Bid
+-
+  !record {model: purchase.order, id: purchase_order_ext_bid2cancel2, context: '{"draft_bid": 1}'}:
+    partner_id: base.res_partner_1
+    invoice_method: order
+    date_order: '2013-08-02'
+    bid_validity: '2013-08-15'
+    order_line:
+      - product_id: product.product_product_15
+        product_qty: 15.0
+        date_planned: '2013-08-30'
+        price_unit: 43.35
+      - product_id: product.product_product_25
+        product_qty: 5.0
+        price_unit: 43.35
+      - product_id: product.product_product_27
+        product_qty: 4.0
+        price_unit: 43.35
+-
+  I run the 'Cancel' wizard. I fill the reason.
+-
+  !record {model: purchase.action_modal_cancelreason, id: purchase_order_ext_bid2cancel1_cancelreason}:
+    reason_id: purchase_cancelreason_rfq_canceled
+-
+  I run the 'Cancel' wizard. I confirm the wizard.
+-
+  !python {model: purchase.order}: |
+    self.purchase_cancel_ok(cr, uid, [ref("purchase_order_ext_bid2cancel2")],{'active_id':ref("purchase_order_ext_bid2cancel1_cancelreason")})
+-
+  I check the "Canceled" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_bid2cancel2}:
+    - type == 'rfq'
+    - state == 'cancel'
+    - cancel_reason_id.id == ref("purchase_cancelreason_rfq_canceled")

=== added file 'purchase_extended/test/process/bid2order.yml'
--- purchase_extended/test/process/bid2order.yml	1970-01-01 00:00:00 +0000
+++ purchase_extended/test/process/bid2order.yml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,79 @@
+-
+  Standard flow from a new Bid to a PO.
+-
+  Create Bid
+-
+  !record {model: purchase.order, id: purchase_order_ext_bid2order1}:
+    partner_id: base.res_partner_1
+    invoice_method: order
+    date_order: '2013-08-02'
+    bid_validity: '2013-08-15'
+    order_line:
+      - product_id: product.product_product_15
+        product_qty: 15.0
+        date_planned: '2013-08-30'
+        price_unit: 43.35
+      - product_id: product.product_product_25
+        product_qty: 5.0
+        date_planned: '2013-08-30'
+        price_unit: 63.12
+      - product_id: product.product_product_27
+        product_qty: 4.0
+        date_planned: '2013-08-30'
+        price_unit: 52.53
+-
+  I print the RFQ.
+-
+  !python {model: purchase.order}: |
+    self.print_quotation(cr, uid, [ref("purchase_order_ext_bid2order1")])
+-
+  Type must be 'rfq' and the total untaxed amount of the RFQ must be computed.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_bid2order1, string: The amount of RFQ is not correctly computed}:
+    - type == 'rfq'
+    - state == 'sent'
+    - round(sum([l.price_subtotal for l in order_line]), 2) == round(amount_untaxed, 2)
+-
+  I run the 'Bid encoded' wizard. I fill the date.
+-
+  !record {model: purchase.action_modal_datetime, id: purchase_order_ext_bid2order1_bidencoded}:
+    datetime: '2013-08-10 00:00:00'
+-
+  I run the 'Bid encoded' wizard. I confirm the wizard.
+-
+  !python {model: purchase.order}: |
+    self.bid_received_ok(cr, uid, [ref("purchase_order_ext_bid2order1")],{'active_id':ref("purchase_order_ext_bid2order1_bidencoded")})
+-
+  I check the "Bid Encoded" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_bid2order1}:
+    - type == 'bid'
+    - state == 'bid'
+-
+  I convert to PO
+-
+  !python {model: purchase.order}: |
+    import netsvc
+    purchase_order = self.browse(cr, uid, ref("purchase_order_ext_bid2order1"))
+    wf_service = netsvc.LocalService("workflow")
+    wf_service.trg_validate(uid, 'purchase.order', purchase_order.id, 'draft_po', cr)
+-
+  I check the "Draft PO" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_bid2order1}:
+    - type == 'purchase'
+    - state == 'draftpo'
+-
+  I confirm the draft PO.
+-
+  !python {model: purchase.order}: |
+    import netsvc
+    purchase_order = self.browse(cr, uid, ref("purchase_order_ext_bid2order1"))
+    wf_service = netsvc.LocalService("workflow")
+    wf_service.trg_validate(uid, 'purchase.order', purchase_order.id, 'purchase_confirm', cr)
+-
+  I check the "Approved" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_bid2order1}:
+    - type == 'purchase'
+    - state == 'approved'

=== added file 'purchase_extended/test/process/po2order.yml'
--- purchase_extended/test/process/po2order.yml	1970-01-01 00:00:00 +0000
+++ purchase_extended/test/process/po2order.yml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,43 @@
+-
+  Standard flow from a new draft PO to a PO.
+-
+  Create draft PO
+-
+  !record {model: purchase.order, id: purchase_order_ext_po2order1, context: {"draft_po": 1}}:
+    partner_id: base.res_partner_1
+    invoice_method: order
+    date_order: '2013-08-02'
+    order_line:
+      - product_id: product.product_product_15
+        product_qty: 15.0
+        date_planned: '2013-08-30'
+        price_unit: 43.35
+      - product_id: product.product_product_25
+        product_qty: 5.0
+        date_planned: '2013-08-30'
+        price_unit: 63.12
+      - product_id: product.product_product_27
+        product_qty: 4.0
+        date_planned: '2013-08-30'
+        price_unit: 52.53
+-
+  Type must be 'purchase' and the total untaxed amount of the PO must be computed.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_po2order1, string: The amount of RFQ is not correctly computed}:
+    - type == 'purchase'
+    - state == 'draftpo'
+    - round(sum([l.price_subtotal for l in order_line]), 2) == round(amount_untaxed, 2)
+-
+  I confirm the draft PO.
+-
+  !python {model: purchase.order}: |
+    import netsvc
+    purchase_order = self.browse(cr, uid, ref("purchase_order_ext_po2order1"))
+    wf_service = netsvc.LocalService("workflow")
+    wf_service.trg_validate(uid, 'purchase.order', purchase_order.id, 'purchase_confirm', cr)
+-
+  I check the "Approved" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_po2order1}:
+    - type == 'purchase'
+    - state == 'approved'

=== added file 'purchase_extended/test/process/rfq2cancel.yml'
--- purchase_extended/test/process/rfq2cancel.yml	1970-01-01 00:00:00 +0000
+++ purchase_extended/test/process/rfq2cancel.yml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,35 @@
+-
+  Cancel a new RFQ.
+-
+  Create RFQ
+-
+  !record {model: purchase.order, id: purchase_order_ext_rfq2cancel1}:
+    partner_id: base.res_partner_1
+    invoice_method: order
+    date_order: '2013-08-02'
+    bid_validity: '2013-08-15'
+    order_line:
+      - product_id: product.product_product_15
+        product_qty: 15.0
+        date_planned: '2013-08-30'
+      - product_id: product.product_product_25
+        product_qty: 5.0
+      - product_id: product.product_product_27
+        product_qty: 4.0
+-
+  I run the 'Cancel' wizard. I fill the reason.
+-
+  !record {model: purchase.action_modal_cancelreason, id: purchase_order_ext_rfq2cancel1_cancelreason}:
+    reason_id: purchase_cancelreason_rfq_canceled
+-
+  I run the 'Cancel' wizard. I confirm the wizard.
+-
+  !python {model: purchase.order}: |
+    self.purchase_cancel_ok(cr, uid, [ref("purchase_order_ext_rfq2cancel1")],{'active_id':ref("purchase_order_ext_rfq2cancel1_cancelreason")})
+-
+  I check the "Canceled" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_rfq2cancel1}:
+    - type == 'rfq'
+    - state == 'cancel'
+    - cancel_reason_id.id == ref("purchase_cancelreason_rfq_canceled")

=== added file 'purchase_extended/test/process/rfq2order.yml'
--- purchase_extended/test/process/rfq2order.yml	1970-01-01 00:00:00 +0000
+++ purchase_extended/test/process/rfq2order.yml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,93 @@
+-
+  Standard flow from a new RFQ to a PO.
+-
+  Create RFQ
+-
+  !record {model: purchase.order, id: purchase_order_ext_rfq2order1}:
+    partner_id: base.res_partner_1
+    invoice_method: order
+    date_order: '2013-08-02'
+    bid_validity: '2013-08-15'
+    order_line:
+      - product_id: product.product_product_15
+        product_qty: 15.0
+        date_planned: '2013-08-30'
+      - product_id: product.product_product_25
+        product_qty: 5.0
+      - product_id: product.product_product_27
+        product_qty: 4.0
+-
+  All prices must be 0.
+-
+  !python {model: purchase.order}: |
+    purchase_order = self.browse(cr, uid, ref("purchase_order_ext_rfq2order1"))
+    for line in purchase_order.order_line:
+      assert line.price_subtotal == 0, "The price must be 0 in the RFQ"
+-
+  Type must be 'rfq' and the total untaxed amount of the RFQ is 0.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_rfq2order1, string: The amount of RFQ is not correctly computed}:
+    - type == 'rfq'
+    - state == 'draft'
+    - amount_untaxed == 0
+-
+  I print the RFQ.
+-
+  !python {model: purchase.order}: |
+    self.print_quotation(cr, uid, [ref("purchase_order_ext_rfq2order1")])
+-
+  I check the "RFQ sent" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_rfq2order1}:
+    - type == 'rfq'
+    - state == 'sent'
+-
+  I encode the Bid. I set a price on the lines.
+-
+  !python {model: purchase.order}: |
+    line_ids = self.read(cr, uid, ref("purchase_order_ext_rfq2order1"), ['order_line'], load='_classic_write')['order_line']
+    self.pool.get('purchase.order.line').write(cr, uid, line_ids, {'price_unit': 79.80})
+-
+  I run the 'Bid encoded' wizard. I fill the date.
+-
+  !record {model: purchase.action_modal_datetime, id: purchase_order_ext_rfq2order1_bidencoded}:
+    datetime: '2013-08-10 00:00:00'
+-
+  I run the 'Bid encoded' wizard. I confirm the wizard.
+-
+  !python {model: purchase.order}: |
+    self.bid_received_ok(cr, uid, [ref("purchase_order_ext_rfq2order1")],{'active_id':ref("purchase_order_ext_rfq2order1_bidencoded")})
+-
+  I check the "Bid Encoded" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_rfq2order1}:
+    - type == 'bid'
+    - state == 'bid'
+-
+  I convert to PO
+-
+  !python {model: purchase.order}: |
+    import netsvc
+    purchase_order = self.browse(cr, uid, ref("purchase_order_ext_rfq2order1"))
+    wf_service = netsvc.LocalService("workflow")
+    wf_service.trg_validate(uid, 'purchase.order', purchase_order.id, 'draft_po', cr)
+-
+  I check the "Draft PO" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_rfq2order1}:
+    - type == 'purchase'
+    - state == 'draftpo'
+-
+  I confirm the draft PO.
+-
+  !python {model: purchase.order}: |
+    import netsvc
+    purchase_order = self.browse(cr, uid, ref("purchase_order_ext_rfq2order1"))
+    wf_service = netsvc.LocalService("workflow")
+    wf_service.trg_validate(uid, 'purchase.order', purchase_order.id, 'purchase_confirm', cr)
+-
+  I check the "Approved" status.
+-
+  !assert {model: purchase.order, id: purchase_order_ext_rfq2order1}:
+    - type == 'purchase'
+    - state == 'approved'

=== added directory 'purchase_extended/view'
=== added file 'purchase_extended/view/purchase_cancel.xml'
--- purchase_extended/view/purchase_cancel.xml	1970-01-01 00:00:00 +0000
+++ purchase_extended/view/purchase_cancel.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record model="ir.ui.view" id="view_purchase_cancelreason_form">
+            <field name="name">Purchase Cancel Reason</field>
+            <field name="model">purchase.cancelreason</field>
+            <field name="arch" type="xml">
+                <form string="Purchase Cancel Reason">
+                    <field name="name"/>
+                    <field name="type"/>
+                </form>
+            </field>
+        </record>
+        <record model="ir.ui.view" id="view_purchase_cancelreason_tree">
+            <field name="name">Purchase Cancel Reasons</field>
+            <field name="model">purchase.cancelreason</field>
+            <field name="arch" type="xml">
+                <tree string="Purchase Cancel Reasons">
+                    <field name="name"/>
+                    <field name="type"/>
+                </tree>
+            </field>
+        </record>
+        <record model="ir.actions.act_window" id="action_purchase_cancelreason">
+            <field name="name">Purchase Cancel Reasons</field>
+            <field name="res_model">purchase.cancelreason</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">tree,form</field>
+        </record>
+        <menuitem id="menu_purchase_config_cancelreason" parent="purchase.menu_purchase_config_purchase" name="Purchase Cancel Reasons" action="action_purchase_cancelreason" sequence="150" groups="purchase.group_purchase_manager"/>
+    </data>
+</openerp>

=== added file 'purchase_extended/view/purchase_order.xml'
--- purchase_extended/view/purchase_order.xml	1970-01-01 00:00:00 +0000
+++ purchase_extended/view/purchase_order.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record model="ir.ui.view" id="view_purchase_order_form">
+            <field name="name">purchase.order.form.inherit</field>
+            <field name="model">purchase.order</field>
+            <field name="inherit_id" ref="purchase.purchase_order_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//sheet/div[@class='oe_title']/h1/label[@string='Request for Quotation ']" position="replace"/>
+                <xpath expr="//sheet/div[@class='oe_title']/h1/label[@string='Purchase Order ']" position="replace"/>
+                <xpath expr="//sheet/div[@class='oe_title']/h1/field[@name='name']" position="before">
+                    <field name="type" nolabel="1" class="oe_inline"/>
+                    <label string=" "/>
+                </xpath>
+                <xpath expr="//sheet/div[@class='oe_title']/h1" position="after">
+                    <h2 attrs="{'invisible': [('state', '!=', 'cancel')]}">
+                        <label for="cancel_reason_id" string="Reason for Cancellation:"/>
+                        <field name="cancel_reason_id" class="oe_inline" />
+                    </h2>
+                </xpath>
+                <xpath expr="//field[@name='state']" position="attributes">
+                    <attribute name="statusbar_visible">draft,sent,bid,draftpo,approved,done</attribute>
+                </xpath>
+                <xpath expr="//button[@name='action_cancel_draft']" position="attributes">
+                    <attribute name="states">cancel,draftpo</attribute>
+                    <attribute name="string">Reset to Draft RFQ</attribute>
+                </xpath>
+                <xpath expr="//button[@string='Send PO by Email']" position="after">
+                    <button name="wkf_send_rfq" states="draftpo" string="Send Draft PO by Email" type="object" context="{'send_rfq':True}"/>
+                </xpath>
+                <xpath expr="//button[@name='bid_received']" position="attributes">
+                    <attribute name="string">Bid Encoded</attribute>
+                    <attribute name="type">object</attribute>
+                    <attribute name="states">sent,draftbid</attribute>
+                </xpath>
+                <xpath expr="//button[@id='bid_confirm']" position="attributes">
+                    <attribute name="states">draftpo</attribute>
+                </xpath>
+                <xpath expr="//button[@id='draft_confirm']" position="attributes">
+                    <attribute name="states">draftpo</attribute>
+                    <attribute name="invisible">1</attribute>
+                </xpath>
+                <xpath expr="//field[@name='incoterm_id']" position="after">
+                    <field name="incoterm_address"/>
+                </xpath>
+                <xpath expr="//field[@name='dest_address_id']" position="replace"/>
+                <field name="warehouse_id" position="before">
+                    <field name="consignee_id"/>
+                    <field name="dest_address_id" on_change="onchange_dest_address_id_mod(dest_address_id, warehouse_id)" string="Delivery Address"/>
+                </field>
+                <field name="warehouse_id" position="attributes">
+                    <attribute name="on_change">onchange_warehouse_id(warehouse_id)</attribute>
+                </field>
+                <button name="purchase_cancel" position="attributes">
+                    <attribute name="type">object</attribute>
+                    <attribute name="states">draft,sent,draftbid,bid,draftpo,confirmed</attribute>
+                </button>
+                <xpath expr="//button[@name='purchase_confirm']" position="after">
+                    <button name="draft_po" states="draft" string="Convert to PO"/>
+                    <button name="draft_po" states="bid" string="Convert to PO" class="oe_highlight"/>
+                </xpath>
+                <field name="product_id" position="replace">
+                    <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context,parent.state,parent.type)"/>
+                </field>
+                <field name="product_qty" position="replace">
+                    <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context,parent.state,parent.type)"/>
+                </field>
+            </field>
+        </record>
+        <record id="purchase.purchase_rfq" model="ir.actions.act_window">
+            <field name="domain">[('type','in',('rfq','bid'))]</field>
+        </record>
+        <record id="purchase.purchase_form_action" model="ir.actions.act_window">
+            <field name="context">{'draft_po':True}</field>
+            <field name="domain">[('type','=','purchase')]</field>
+        </record>
+
+        <record model="ir.ui.view" id="view_purchase_order_invoice_form">
+            <field name="name">purchase.order.form.invoice.inherit</field>
+            <field name="model">purchase.order</field>
+            <field name="inherit_id" ref="purchase.purchase_order_2_stock_picking"/>
+            <field name="arch" type="xml">
+                <xpath expr="//button[@name='invoice_open']" position="attributes">
+                    <attribute name="attrs">{'invisible': [('state', 'in', ['draft','draftbid','draftpo'])]}</attribute>
+                </xpath>
+            </field>
+        </record>
+
+        <record id="on_change_on_po_line_form" model="ir.ui.view">
+          <field name="name">on change on po line form</field>
+          <field name="model">purchase.order.line</field>
+          <field name="inherit_id" ref="purchase.purchase_order_line_form" />
+          <field name="arch" type="xml">
+              <field name="product_id" position="replace">
+                  <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context,parent.state,parent.type)"/>
+              </field>
+              <field name="product_qty" position="replace">
+                  <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context,parent.state,parent.type)"/>
+              </field>
+          </field>
+        </record>
+    </data>
+</openerp>

=== added directory 'purchase_extended/wizard'
=== added directory 'purchase_extended/wizard.moved'
=== added file 'purchase_extended/wizard/__init__.py'
--- purchase_extended/wizard/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_extended/wizard/__init__.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,2 @@
+import modal
+import action_cancel_reason

=== added file 'purchase_extended/wizard/action_cancel_reason.py'
--- purchase_extended/wizard/action_cancel_reason.py	1970-01-01 00:00:00 +0000
+++ purchase_extended/wizard/action_cancel_reason.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,9 @@
+from osv import fields, osv
+
+
+class action_modal_cancelreason(osv.TransientModel):
+    _name = "purchase.action_modal_cancelreason"
+    _inherit = "purchase.action_modal"
+    _columns = {
+        'reason_id': fields.many2one('purchase.cancelreason', 'Reason for Cancellation', required=True),
+    }

=== added file 'purchase_extended/wizard/action_cancel_reason.xml'
--- purchase_extended/wizard/action_cancel_reason.xml	1970-01-01 00:00:00 +0000
+++ purchase_extended/wizard/action_cancel_reason.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="action_modal_cancel_reason" model="ir.ui.view">
+            <field name="name">action.modal</field>
+            <field name="model">purchase.action_modal_cancelreason</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="Confirm" version="7.0">
+                    <p>
+                        Please confirm operation
+                    </p>
+                    <group>
+                        <field name="reason_id"/>
+                    </group>
+                    <footer>
+                        <button string="Confirm" class="oe_highlight" type="object" name="action"/>
+                        or
+                        <button special="cancel" string="Cancel" class="oe_link"/>
+                    </footer>
+                </form>
+            </field>
+        </record>
+    </data>
+</openerp>

=== added file 'purchase_extended/wizard/modal.py'
--- purchase_extended/wizard/modal.py	1970-01-01 00:00:00 +0000
+++ purchase_extended/wizard/modal.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,26 @@
+from osv import fields, osv
+
+
+class action_modal(osv.TransientModel):
+    _name = "purchase.action_modal"
+    _columns = {}
+
+    def action(self, cr, uid, ids, context):
+        for e in ('active_model', 'active_ids', 'action'):
+            if e not in context:
+                return False
+        ctx = context.copy()
+        ctx['active_ids'] = ids
+        ctx['active_id'] = ids[0]
+        res = getattr(self.pool.get(context['active_model']), context['action'])(cr, uid, context['active_ids'], context=ctx)
+        if isinstance(res, dict):
+            return res
+        return {'type': 'ir.actions.act_window_close'}
+
+
+class action_modal_datetime(osv.TransientModel):
+    _name = "purchase.action_modal_datetime"
+    _inherit = "purchase.action_modal"
+    _columns = {
+        'datetime': fields.datetime('Date'),
+    }

=== added file 'purchase_extended/wizard/modal.xml'
--- purchase_extended/wizard/modal.xml	1970-01-01 00:00:00 +0000
+++ purchase_extended/wizard/modal.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="action_modal_bid_date" model="ir.ui.view">
+            <field name="name">action.modal</field>
+            <field name="model">purchase.action_modal_datetime</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="Confirm" version="7.0">
+                    <p>
+                        Please confirm operation
+                    </p>
+                    <group>
+                        <field string="Bid received date" name="datetime"/>
+                    </group>
+                    <footer>
+                        <button string="Confirm" class="oe_highlight" type="object" name="action"/>
+                        or
+                        <button special="cancel" string="Cancel" class="oe_link"/>
+                    </footer>
+                </form>
+            </field>
+        </record>
+    </data>
+</openerp>

=== added directory 'purchase_extended/workflow'
=== added file 'purchase_extended/workflow/purchase_order.xml'
--- purchase_extended/workflow/purchase_order.xml	1970-01-01 00:00:00 +0000
+++ purchase_extended/workflow/purchase_order.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <!-- Prevent to send a RFQ without any line -->
+         <record id="purchase.trans_draft_sent" model="workflow.transition">
+            <field name="condition">_has_lines()</field>
+        </record>
+
+        <!-- Differenciate draft RFQ and draft PO. Add a state draft PO and adapt workflow -->
+        <record id="act_draft_po" model="workflow.activity">
+            <field name="wkf_id" ref="purchase.purchase_order"/>
+            <field name="name">draft PO</field>
+            <field name="kind">function</field>
+            <field name="action">wkf_draft_po()</field>
+        </record>
+        <record id="trans_draft_draftpo" model="workflow.transition">
+            <field name="act_from" ref="purchase.act_draft"/>
+            <field name="act_to" ref="act_draft_po"/>
+            <field name="signal">draft_po</field>
+        </record>
+        <record id="purchase.trans_draft_confirmed" model="workflow.transition">
+            <field name="act_from" ref="act_draft_po"/>
+        </record>
+        <record id="trans_bid_draftpo" model="workflow.transition">
+            <field name="act_from" ref="purchase.act_bid"/>
+            <field name="act_to" ref="act_draft_po"/>
+            <field name="signal">draft_po</field>
+        </record>
+        <record id="trans_draftpo_cancel" model="workflow.transition">
+            <field name="act_from" ref="act_draft_po"/>
+            <field name="act_to" ref="purchase.act_cancel"/>
+            <field name="signal">purchase_cancel</field>
+        </record>
+
+        <!-- Allow to create directly a draft Bid -->
+        <record id="act_draftbid" model="workflow.activity">
+            <field name="wkf_id" ref="purchase.purchase_order"/>
+            <field name="name">draft Bid</field>
+            <field name="kind">function</field>
+            <field name="action">write({'state': 'draftbid','type': 'bid'})</field>
+        </record>
+        <record id="trans_draft_draftbid" model="workflow.transition">
+            <field name="act_from" ref="purchase.act_draft"/>
+            <field name="act_to" ref="act_draftbid"/>
+            <field name="signal">draft_bid</field>
+        </record>
+        <record id="trans_draftbid_bid" model="workflow.transition">
+            <field name="act_from" ref="act_draftbid"/>
+            <field name="act_to" ref="purchase.act_bid"/>
+            <field name="signal">bid_received</field>
+        </record>
+        <record id="trans_draftbid_cancel" model="workflow.transition">
+            <field name="act_from" ref="act_draftbid"/>
+            <field name="act_to" ref="purchase.act_cancel"/>
+            <field name="signal">purchase_cancel</field>
+        </record>
+
+        <record id="act_po_requisition_selected" model="workflow.activity">
+          <field name="wkf_id" ref="purchase.purchase_order"/>
+          <field name="name">Bid selected</field>
+          <field name="kind">function</field>
+          <field name="action">po_tender_requisition_selected()</field>
+          <field name="flow_stop">True</field>
+        </record>
+        <record id="trans_po_requisition_selected" model="workflow.transition">
+          <field name="act_from" ref="purchase.act_bid"/>
+          <field name="act_to" ref="act_po_requisition_selected"/>
+          <field name="signal">select_requisition</field>
+        </record>
+
+
+        <!-- Rename some activities to make the workflow clear -->
+        <record id="purchase.act_draft" model="workflow.activity">
+            <field name="name">draft RFQ</field>
+        </record>
+        <record id="purchase.act_sent" model="workflow.activity">
+            <field name="name">RFQ sent</field>
+        </record>
+        <record id="purchase.act_bid" model="workflow.activity">
+            <field name="name">Bid encoded</field>
+        </record>
+
+    </data>
+</openerp>

=== added directory 'purchase_requisition_extended'
=== added file 'purchase_requisition_extended/__init__.py'
--- purchase_requisition_extended/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/__init__.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+from . import model
+from . import wizard

=== added file 'purchase_requisition_extended/__openerp__.py'
--- purchase_requisition_extended/__openerp__.py	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/__openerp__.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Copyright 2013 Camptocamp SA
+#
+#    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/>.
+#
+##############################################################################
+
+{"name": "Purchase Requisition Extended",
+ "version": "0.1",
+ "author": "Camptocamp",
+ "license": "AGPL-3",
+ "category": "Purchase Management",
+ "complexity": "normal",
+ "images": [],
+ "description": """
+This module improves the standard Purchase Requisition module.
+==============================================================
+This module allows to make calls for bids by generating RFQ for selected
+suppliers, encode the bids, compare and select bids, generate draft POs.
+
+First, a list of products is established. The call for bid is then confirmed
+and RFQs can be generated. They are in the state 'Draft RFQ' until it is send
+to the supplier and marked as 'RFQ Sent'. The bids has to be encoded and moved
+to state 'Bid Encoded'. When closing the call for bids in order to start the
+bids selection, all RFQ that have not been sent will be canceled. However,
+send RFQs can still be encoded. Bids that are not received will remain in state
+'RFQ Sent' and can be manually canceled.
+
+Afterwards, the bids selection can be started by choosing product lines. The
+workflow has been modified to allow to mark that the selection of bids has
+occurred but without having to generate the POs yet, those can be created at a
+new later state called 'Bids Selected'.
+
+When generating POs, the are created in the state 'Draft PO' introduced by the
+module purchase_extended.
+
+
+Some fields have been added to specify with more details the call for bids and
+prefill fields of the generated RFQs.
+
+
+A link has been added between a call for bids line and the corresponding line
+of each generated RFQ. This is used for the bids comparison in order to compare
+bid lines and group then properly.
+""",
+ "depends": ["purchase_requisition",
+             "stock",  # For incoterms
+             "purchase_extended",  # for field incoterms place
+             ],
+ "demo": [],
+ "data": ["wizard/modal.xml",
+          "wizard/purchase_requisition_partner_view.xml",
+          "view/purchase_requisition.xml",
+          "view/purchase_order.xml",
+          "workflow/purchase_requisition.xml",
+          "data/purchase.cancelreason.yml",
+          ],
+ "js": [
+        "static/src/js/web_addons.js",
+        ],
+ "auto_install": False,
+ "test": ["test/process/restricted.yml",
+          ],
+ "installable": True,
+ "certificate": "",
+ }

=== added directory 'purchase_requisition_extended/data'
=== added file 'purchase_requisition_extended/data/purchase.cancelreason.yml'
--- purchase_requisition_extended/data/purchase.cancelreason.yml	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/data/purchase.cancelreason.yml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,9 @@
+-
+  !context {noupdate: True}
+-
+  Creating the cancel reasons
+-
+  !record {model: purchase.cancelreason, id: purchase_cancelreason_callforbids_canceled}:
+    name: Call for bids canceled
+    type: rfq
+    nounlink: 1

=== added directory 'purchase_requisition_extended/model'
=== added file 'purchase_requisition_extended/model/__init__.py'
--- purchase_requisition_extended/model/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/model/__init__.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+import purchase_requisition
+import purchase_order

=== added file 'purchase_requisition_extended/model/purchase_order.py'
--- purchase_requisition_extended/model/purchase_order.py	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/model/purchase_order.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+from openerp.osv import fields, orm
+from openerp import SUPERUSER_ID
+
+
+class purchase_order(orm.Model):
+    _inherit = 'purchase.order'
+    _columns = {
+        'bid_partial': fields.boolean(
+            'Bid partially selected',
+            readonly=True,
+            help="True if the bid has been partially selected"),
+        'tender_bid_receipt_mode': fields.function(
+            lambda self, *args, **kwargs: self._get_tender(*args, **kwargs),
+            multi="callforbids",
+            readonly=True,
+            type='selection',
+            selection=[('open', 'Open'), ('sealed', 'Sealed')],
+            string='Bid Receipt Mode'),
+    }
+    #TODO: lines should not be deleted or created if linked to a callforbids
+
+    def _get_tender(self, cr, uid, ids, fields, args, context=None):
+        # when _classic_write load is used, the many2one are only the
+        # ids instead of the tuples (id, name_get)
+        purch_req_obj = self.pool.get('purchase.requisition')
+        orders = self.read(cr, uid, ids, ['requisition_id'],
+                           context=context, load='_classic_write')
+        req_ids = list(set(order['requisition_id'] for order in orders
+                           if order['requisition_id']))
+        # we'll read the fields without the 'tender_' prefix
+        # and copy their value in the fields with the prefix
+        read_fields = [x[len('tender_'):] for x in fields]
+        requisitions = purch_req_obj.read(cr, uid,
+                                          req_ids,
+                                          read_fields,
+                                          context=context,
+                                          load='_classic_write')
+        # copy the dict but rename the fields with 'tender_' prefix
+        tender_reqs = {}
+        for req in requisitions:
+            tender_reqs[req['id']] = dict(('tender_' + field, value)
+                                          for field, value
+                                          in req.iteritems()
+                                          if 'tender_' + field in fields)
+        res = {}
+        for po in orders:
+            if po['requisition_id']:
+                res[po['id']] = tender_reqs[po['requisition_id']]
+            else:
+                res[po['id']] = {}.fromkeys(fields, False)
+        return res
+
+    def _prepare_purchase_order(self, cr, uid, requisition, supplier, context=None):
+        values = super(purchase_order, self)._prepare_purchase_order(
+            cr, uid, requisition, supplier, context=context)
+        values.update({
+            'bid_validity': requisition.req_validity,
+            'payment_term_id': requisition.req_payment_term_id,
+            'incoterm_id': requisition.req_incoterm_id,
+            'incoterm_address': requisition.req_incoterm_address,
+            'transport_mode_id': requisition.req_transport_mode_id,
+        })
+        if requisition.pricelist_id:
+            values.update({'pricelist_id': requisition.pricelist_id.id})
+        return values
+
+    def copy(self, cr, uid, id, default=None, context=None):
+        """ Need to set origin after copy because original copy clears origin """
+        if default is None:
+            default = {}
+        initial_origin = default.get('origin')
+        newid = super(purchase_order, self).copy(cr, uid, id, default=default,
+                                                 context=context)
+        if initial_origin and 'requisition_id' in default:
+            self.write(cr, SUPERUSER_ID, [newid], {'origin': initial_origin}, context=context)
+        return newid
+
+
+class purchase_order_line(orm.Model):
+    _inherit = 'purchase.order.line'
+    _columns = {
+        'requisition_line_id': fields.many2one('purchase.requisition.line', 'Call for Bid Line', readonly=True),
+    }
+
+    def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
+        """Do not aggregate price and qty. We need to do it this way as there
+        is no group_operator that can be set to prevent aggregating float"""
+        result = super(purchase_order_line, self).read_group(cr, uid, domain, fields, groupby,
+                        offset=offset, limit=limit, context=context, orderby=orderby)
+        for res in result:
+            if 'price_unit' in res:
+                del res['price_unit']
+            if 'product_qty' in res:
+                del res['product_qty']
+            if 'lead_time' in res:
+                del res['lead_time']
+        return result

=== added file 'purchase_requisition_extended/model/purchase_requisition.py'
--- purchase_requisition_extended/model/purchase_requisition.py	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/model/purchase_requisition.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,421 @@
+# -*- coding: utf-8 -*-
+
+from openerp.osv import fields, orm
+import openerp.osv.expression as expression
+from openerp.tools.safe_eval import safe_eval as eval
+from openerp.tools.translate import _
+from openerp import netsvc
+from openerp.tools.float_utils import float_compare
+
+
+class PurchaseRequisition(orm.Model):
+    _inherit = "purchase.requisition"
+    _description = "Call for Bids"
+    _columns = {
+        # modified
+        'state': fields.selection([('draft', 'Draft'),
+                                   ('in_progress', 'Confirmed'),
+                                   ('open', 'Bids Selection'),
+                                   ('closed', 'Bids Selected'),  # added
+                                   ('done', 'PO Created'),
+                                   ('cancel', 'Canceled')],
+                                  'Status', track_visibility='onchange',
+                                  required=True),
+        'purchase_ids': fields.one2many('purchase.order', 'requisition_id',
+                                        'Purchase Orders',
+                                        states={'done': [('readonly', True)]},
+                                        domain=[('type', 'in', ('rfq', 'bid'))]),
+        # new
+        'req_validity': fields.date("Requested Bid's End of Validity",
+                                    help="Requested validity period requested to the bidder, "
+                                         "i.e. please send bids that stay valid until that "
+                                         "date.\n The bidder is allowed to send a bid with "
+                                         "another validity end date that gets encoded in the "
+                                         "bid."),
+        'bid_tendering_mode': fields.selection([('open', 'Open'),
+                                                ('restricted', 'Restricted')],
+                                               'Call for Bids Mode',
+                                               help="- Restricted : you select yourself the "
+                                                    "bidders and generate a RFQ for each of "
+                                                    "those. \n"
+                                                    "- Open : anybody can bid (you have to "
+                                                    "advertise the call for bids) and you "
+                                                    "directly encode the bids you received. "
+                                                    "You are still able to generate RFQ if "
+                                                    "you want to contact usual bidders."),
+        'bid_receipt_mode': fields.selection([('open', 'Open'),
+                                              ('sealed', 'Sealed')],
+                                             'Bid Receipt Mode',
+                                             required=True,
+                                             help="- Open : The bids can be opened when "
+                                                  "received and encoded. \n"
+                                                  "- Closed : The bids can be marked as "
+                                                  "received but they have to be opened \n"
+                                                  "all at the same time after an opening "
+                                                  "ceremony (probably specific to public "
+                                                  "sector)."),
+        'consignee_id': fields.many2one('res.partner',
+                                        'Consignee',
+                                        help="Person responsible of delivery"),
+        'dest_address_id': fields.many2one('res.partner',
+                                           'Delivery Address'),
+        'req_incoterm_id': fields.many2one(
+            'stock.incoterms',
+            'Requested Incoterms',
+            help="Default value requested to the supplier. "
+                 "International Commercial Terms are a series of predefined "
+                 "commercial terms used in international transactions."
+        ),
+        'req_incoterm_address': fields.char(
+            'Requested Incoterms Place',
+            help="Incoterm Place of Delivery. "
+                 "International Commercial Terms are a series of "
+                 "predefined commercial terms used in "
+                 "international transactions."),
+        'req_payment_term_id': fields.many2one(
+            'account.payment.term',
+            'Requested Payment Term',
+            help="Default value requested to the supplier."
+        ),
+        'pricelist_id': fields.many2one(
+            'product.pricelist',
+            'Pricelist',
+            domain=[('type', '=', 'purchase')],
+            help="If set that pricelist will be used to generate the RFQ."
+            "Mostely used to ask a requisition in a given currency."
+        ),
+        'date_end': fields.datetime('Bid Submission Deadline',
+                                    help="All bids received after that date won't be valid "
+                                         " (probably specific to public sector)."),
+    }
+    _defaults = {
+        'bid_receipt_mode': 'open',
+    }
+
+    def _has_product_lines(self, cr, uid, ids, context=None):
+        """
+        Check there are products lines when confirming Call for Bids.
+        Called from workflow transition draft->sent.
+        """
+        for callforbids in self.browse(cr, uid, ids, context=context):
+            if not callforbids.line_ids:
+                raise orm.except_orm(
+                        _('Error!'),
+                        _('You have to define some products before confirming the call for bids.'))
+        return True
+
+    def _prepare_purchase_order(self, cr, uid, requisition, supplier, context=None):
+        values = super(PurchaseRequisition, self)._prepare_purchase_order(
+            cr, uid, requisition, supplier, context=context)
+        values.update({
+            'dest_address_id': requisition.dest_address_id.id,
+            'consignee_id': requisition.consignee_id.id,
+            'bid_validity': requisition.req_validity,
+            'payment_term_id': requisition.req_payment_term_id.id,
+            'incoterm_id': requisition.req_incoterm_id.id,
+            'incoterm_address': requisition.req_incoterm_address,
+        })
+        if requisition.pricelist_id:
+            values['pricelist_id'] = requisition.pricelist_id.id
+        return values
+
+    def _prepare_purchase_order_line(self, cr, uid, requisition,
+                                     requisition_line, purchase_id,
+                                     supplier, context=None):
+        vals = super(PurchaseRequisition, self)._prepare_purchase_order_line(
+            cr, uid, requisition, requisition_line, purchase_id, supplier, context)
+        vals['price_unit'] = 0
+        vals['requisition_line_id'] = requisition_line.id
+        return vals
+
+    def onchange_dest_address_id(self, cr, uid, ids, dest_address_id,
+                                 warehouse_id, context=None):
+        warehouse_obj = self.pool.get('stock.warehouse')
+        dest_ids = warehouse_obj.search(cr, uid,
+                                        [('partner_id', '=', dest_address_id)],
+                                        context=context)
+        if dest_ids:
+            if warehouse_id not in dest_ids:
+                warehouse_id = dest_ids[0]
+        else:
+            warehouse_id = False
+        return {'value': {'warehouse_id': warehouse_id}}
+
+    def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):
+        if not warehouse_id:
+            return {}
+        warehouse_obj = self.pool.get('stock.warehouse')
+        warehouse = warehouse_obj.browse(cr, uid,
+                                         warehouse_id, context=context)
+        dest_id = warehouse.partner_id.id
+        return {'value': {'dest_address_id': dest_id}}
+
+    def trigger_validate_po(self, cr, uid, po_id, context=None):
+        wf_service = netsvc.LocalService("workflow")
+        wf_service.trg_validate(uid, 'purchase.order', po_id, 'draft_po', cr)
+        po_obj = self.pool.get('purchase.order')
+        po_obj.write(cr, uid, po_id, {'bid_partial': False}, context=context)
+        return True
+
+    def check_valid_quotation(self, cr, uid, quotation, context=None):
+        return False
+
+    def generate_po(self, cr, uid, ids, context=None):
+        if isinstance(ids, (list, tuple)):
+            assert len(ids) == 1, "Only 1 ID expected"
+            ids = ids[0]
+        tender = self.browse(cr, uid, ids, context=context)
+        po_obj = self.pool.get('purchase.order')
+        for po_line in tender.po_line_ids:
+            # set bid selected boolean to true on RFQ containing confirmed lines
+            if (po_line.state == 'confirmed' and
+                    not po_line.order_id.bid_partial):
+                po_obj.write(cr, uid,
+                             po_line.order_id.id,
+                             {'bid_partial': True},
+                             context=context)
+        return super(PurchaseRequisition, self).generate_po(cr, uid, [ids], context=context)
+
+    def quotation_selected(self, cr, uid, quotation, context=None):
+        """Predicate that checks if a quotation has at least one line chosen
+        :param quotation: record of 'purchase.order'
+
+        :returns: True if one line has been chosen
+
+        """
+        # This topic is subject to changes
+        return quotation.bid_partial
+
+    def cancel_quotation(self, cr, uid, tender, context=None):
+        """
+        Called from generate_po. Cancel only draft and sent rfq
+        """
+        po = self.pool.get('purchase.order')
+        wf_service = netsvc.LocalService("workflow")
+        tender.refresh()
+        for quotation in tender.purchase_ids:
+            if quotation.state in ['draft', 'sent', 'bid']:
+                if self.quotation_selected(cr, uid, quotation, context=context):
+                    wf_service.trg_validate(uid, 'purchase.order', quotation.id,
+                                            'select_requisition', cr)
+                else:
+                    wf_service.trg_validate(uid, 'purchase.order', quotation.id, 'purchase_cancel', cr)
+                    po.message_post(cr, uid, [quotation.id],
+                                    body=_('Canceled by the call for bids associated'
+                                           ' to this request for quotation.'),
+                                    context=context)
+
+        return True
+
+    def tender_open(self, cr, uid, ids, context=None):
+        """
+        Cancel RFQ that have not been sent. Ensure that there are RFQs."
+        """
+        cancel_ids = []
+        rfq_valid = False
+        for callforbids in self.browse(cr, uid, ids, context=context):
+            for purchase in callforbids.purchase_ids:
+                if purchase.state == 'draft':
+                    cancel_ids.append(purchase.id)
+                elif purchase.state != 'cancel':
+                    rfq_valid = True
+        if cancel_ids:
+            reason_id = self.pool.get('ir.model.data').get_object_reference(cr, uid,
+                            'purchase_extended', 'purchase_cancelreason_rfq_canceled')[1]
+            purchase_order_obj = self.pool.get('purchase.order')
+            purchase_order_obj.write(cr, uid, cancel_ids, {'cancel_reason': reason_id}, context=context)
+            purchase_order_obj.action_cancel(cr, uid, cancel_ids, context=context)
+        if not rfq_valid:
+            raise orm.except_orm(
+                        _('Error'),
+                        _('You do not have valid sent RFQs.'))
+        return super(PurchaseRequisition, self).tender_open(cr, uid, ids, context=context)
+
+    def _get_po_to_cancel(self, cr, uid, callforbids, context=None):
+        """Get the list of PO/RFQ that can be canceled on RFQ
+
+        :param callforbids: `purchase.requisition` record
+
+        :returns: List of candidate PO/RFQ record
+
+        """
+        res = []
+        for purchase in callforbids.purchase_ids:
+            if purchase.state in ('draft', 'sent'):
+                res.append(purchase)
+        return res
+
+    def _check_can_be_canceled(self, callforbids, context=None):
+        """Raise an exception if callforbids can not be cancelled
+        :param callforbids: `purchase.requisition` record
+
+        :returns: True or raise exception
+
+        """
+        for purchase in callforbids.purchase_ids:
+            if purchase.state not in ('draft', 'sent'):
+                raise orm.except_orm(
+                    _('Error'),
+                    _('You cannot cancel a call for bids which '
+                      'has already received bids.'))
+        return True
+
+    def _cancel_po_with_reason(self, cr, uid, po_list, reason_id, context=None):
+        """Cancel purchase order of a tender, using given reasons
+        :param po_list: list of po record to cancel
+        :param reason_id: reason id of cancelation
+
+        :returns: cancel po record list
+
+        """
+        purchase_order_obj = self.pool.get('purchase.order')
+        purchase_order_obj.write(cr, uid,
+                                 [x.id for x in po_list],
+                                 {'cancel_reason': reason_id},
+                                 context=context)
+        for order in po_list:
+            # passing full list raises assert error
+            purchase_order_obj.action_cancel_no_reason(cr, uid, [order.id],
+                                                       context=context)
+        return po_list
+
+    def _get_default_reason(self, cr, uid, context=None):
+        """Return default cancel reason"""
+        reason = self.pool.get('ir.model.data').get_object_reference(
+            cr,
+            uid,
+            'purchase_requisition_extended',
+            'purchase_cancelreason_callforbids_canceled'
+        )
+        return reason[1]
+
+    def tender_cancel(self, cr, uid, ids, context=None):
+        """
+        Cancel call for bids and try to cancelrelated  RFQs/PO
+
+        """
+        reason_id = self._get_default_reason(cr, uid, context=context)
+        for callforbids in self.browse(cr, uid, ids, context=context):
+            self._check_can_be_canceled(callforbids, context=context)
+            po_to_cancel = self._get_po_to_cancel(cr, uid, callforbids, context=context)
+            if po_to_cancel:
+                self._cancel_po_with_reason(cr, uid, po_to_cancel, reason_id,
+                                            context=context)
+        return self.write(cr, uid, ids, {'state': 'cancel'})
+
+    def tender_close(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'state': 'closed'}, context=context)
+
+    def open_rfq(self, cr, uid, ids, context=None):
+        """
+        This opens rfq view to view all generated rfq/bids associated to the call for bids
+        """
+        if context is None:
+            context = {}
+        res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'purchase', 'purchase_rfq', context=context)
+        res['domain'] = expression.AND([eval(res.get('domain', [])), [('requisition_id', 'in', ids)]])
+        # FIXME: need to disable create - temporarily set as invisible in view
+        return res
+
+    def open_po(self, cr, uid, ids, context=None):
+        """
+        This opens po view to view all generated po associated to the call for bids
+        """
+        if context is None:
+            context = {}
+        res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'purchase', 'purchase_form_action', context=context)
+        res['domain'] = expression.AND([eval(res.get('domain', [])), [('requisition_id', 'in', ids)]])
+        return res
+
+    def close_callforbids(self, cr, uid, ids, context=None):
+        """
+        Check all quantities have been sourced
+        """
+        # this method is called from a special JS event and ids is
+        # inferred from 'active_ids', in some cases, the webclient send
+        # no ids, so we prevent a crash
+        if not ids:
+            raise orm.except_orm(
+                _('Error'),
+                _('Impossible to proceed due to an error of the system.\n'
+                  'Please reopen the purchase requisition and try again '))
+        if isinstance(ids, (tuple, list)):
+            assert len(ids) == 1, "Only 1 ID expected, got %s" % ids
+            ids = ids[0]
+        purch_req = self.browse(cr, uid, ids, context=context)
+        dp_obj = self.pool.get('decimal.precision')
+        precision = dp_obj.precision_get(cr, uid, 'Product Unit of Measure')
+        for line in purch_req.line_ids:
+            qty = line.product_qty
+            for pol in line.purchase_line_ids:
+                if pol.state == 'confirmed':
+                    qty -= pol.quantity_bid
+            if qty == line.product_qty:
+                break  # nothing selected
+            compare = float_compare(qty, 0, precision_digits=precision)
+            if compare != 0:
+                break  # too much or too few selected
+        else:
+            return self.close_callforbids_ok(cr, uid, [ids], context=context)
+
+        # open a dialog to confirm that we want more / less or no qty
+        ctx = context.copy()
+        ctx['action'] = 'close_callforbids_ok'
+        ctx['active_model'] = self._name
+
+        get_ref = self.pool.get('ir.model.data').get_object_reference
+        view_id = get_ref(cr, uid, 'purchase_requisition_extended',
+                          'action_modal_close_callforbids')[1]
+        return {
+            'type': 'ir.actions.act_window',
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'purchase.action_modal',
+            'view_id': view_id,
+            'views': [(view_id, 'form')],
+            'target': 'new',
+            'context': ctx,
+        }
+
+    def open_product_line(self, cr, uid, ids, context=None):
+        """ Filter to show only lines from bids received. Group by requisition line instead of product for unicity
+        """
+        res = super(PurchaseRequisition, self).open_product_line(cr, uid, ids, context=context)
+        ctx = res.setdefault('context', {})
+        if 'search_default_groupby_product' in ctx:
+            del ctx['search_default_groupby_product']
+        if 'search_default_hide_cancelled' in ctx:
+            del ctx['search_default_hide_cancelled']
+        ctx['search_default_groupby_requisitionline'] = True
+        ctx['search_default_showbids'] = True
+        return res
+
+    def close_callforbids_ok(self, cr, uid, ids, context=None):
+        wf_service = netsvc.LocalService("workflow")
+        for id in ids:
+            wf_service.trg_validate(uid, 'purchase.requisition',
+                                    id, 'close_bid', cr)
+        return True
+
+
+class purchase_requisition_line(orm.Model):
+    _inherit = "purchase.requisition.line"
+    _columns = {
+        'remark': fields.text('Remark'),
+        'purchase_line_ids': fields.one2many('purchase.order.line',
+                                             'requisition_line_id',
+                                             'Bids Lines',
+                                             readonly=True),
+    }
+
+    def name_get(self, cr, uid, ids, context=None):
+        res = []
+        for line in self.read(cr, uid, ids,
+                              ['product_id', 'product_qty', 'schedule_date'],
+                              context=context):
+            name = ""
+            if line['schedule_date']:
+                name += '%s ' % line['schedule_date']
+            name += '%s %s' % (line['product_qty'], line['product_id'][1])
+            res.append((line['id'], name))
+        return res

=== added directory 'purchase_requisition_extended/static'
=== added directory 'purchase_requisition_extended/static/src'
=== added directory 'purchase_requisition_extended/static/src/js'
=== added file 'purchase_requisition_extended/static/src/js/web_addons.js'
--- purchase_requisition_extended/static/src/js/web_addons.js	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/static/src/js/web_addons.js	2015-01-19 14:42:10 +0000
@@ -0,0 +1,51 @@
+openerp.purchase_requisition_extended = function(instance) {
+    var QWeb = instance.web.qweb,
+    _t = instance.web._t;
+
+    instance.web.purchase_requisition.CompareListView.include(
+        {
+            init: function () {
+                var self = this;
+                this._super.apply(this, arguments);
+                this.on(
+                    'list_view_loaded',
+                    this,
+                    function() {
+                        if(self.$buttons.find('.oe_close_bid').length == 0){
+                            var button_body = "<button type='button' class='oe_button oe_highlight oe_close_bid'>Confirm Selection</button>"
+                            var return_link = '<a accesskey="D" class="oe_bold oe_form_button_cancel" href="#">Close</a>'
+                            var button = $(button_body).click(
+                                this.proxy('close_bids_selection')
+                            );
+                            button.after('<span class="oe_fade" style="margin:0 4px">or</span>');
+                            button.after(
+                                $(return_link).click(
+                                    function(){self.do_action('history_back')}
+                                )
+                            );
+                            self.$buttons.append(button);
+                        }
+                        self.$buttons.find('.oe_generate_po').remove();
+                    }
+                );
+            },
+            close_bids_selection: function () {
+                var self = this;
+                new instance.web.Model('purchase.requisition').call(
+                    "close_callforbids",
+                    [self.dataset.context.active_id, self.dataset.context]
+                ).then(
+                    function(result) {
+                        self.do_action(
+                            false,
+                            {
+                                on_close: function(){self.do_action('history_back')},
+                                context: {}
+                            }
+                        );
+                    }
+                );
+            },
+        }
+    );
+}

=== added directory 'purchase_requisition_extended/test'
=== added directory 'purchase_requisition_extended/test/process'
=== added file 'purchase_requisition_extended/test/process/restricted.yml'
--- purchase_requisition_extended/test/process/restricted.yml	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/test/process/restricted.yml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,194 @@
+-
+  Standard flow of a Call for Bids in mode restricted
+-
+  Create Call for Bids
+-
+  !record {model: purchase.requisition, id: purchase_requisition_ext_restricted1}:
+    date_start: '2013-08-02 00:00:00'
+    date_end: '2013-08-30 00:00:00'
+    bid_tendering_mode: 'restricted'
+    schedule_date: '2013-09-30'
+    req_validity: '2013-09-10'
+    line_ids:
+      - product_id: product.product_product_15
+        product_qty: 15.0
+      - product_id: product.product_product_25
+        product_qty: 5.0
+      - product_id: product.product_product_27
+        product_qty: 40.0
+-
+  Confirm Call
+-
+  !python {model: purchase.requisition}: |
+    import netsvc
+    wf_service = netsvc.LocalService("workflow")
+    wf_service.trg_validate(uid, 'purchase.requisition', ref("purchase_requisition_ext_restricted1"), 'sent_suppliers', cr)
+-
+  Create RFQ1. I run the 'Request a quotation' wizard. I fill the supplier.
+-
+  !record {model: purchase.requisition.partner, id: purchase_requisition_ext_restricted1_partner1_create}:
+    partner_id: base.res_partner_2
+-
+  Create RFQ1. I confirm the wizard.
+-
+  !python {model: purchase.requisition.partner}: |
+    self.create_order(cr, uid, [ref("purchase_requisition_ext_restricted1_partner1_create")],{
+        'active_model': 'purchase.requisition',
+        'active_id': ref("purchase_requisition_ext_restricted1"),
+        'active_ids': [ref("purchase_requisition_ext_restricted1")],
+        })
+-
+  Create RFQ2. I run the 'Request a quotation' wizard. I fill the supplier.
+-
+  !record {model: purchase.requisition.partner, id: purchase_requisition_ext_restricted1_partner2_create}:
+    partner_id: base.res_partner_3
+-
+  Create RFQ2. I confirm the wizard.
+-
+  !python {model: purchase.requisition.partner}: |
+    self.create_order(cr, uid, [ref("purchase_requisition_ext_restricted1_partner2_create")],{
+        'active_model': 'purchase.requisition',
+        'active_id': ref("purchase_requisition_ext_restricted1"),
+        'active_ids': [ref("purchase_requisition_ext_restricted1")],
+        })
+-
+  Check the RFQs. Type must be 'rfq', state 'draft', all prices 0 and the total untaxed amount 0.
+-
+  !python {model: purchase.requisition}: |
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    assert len(purchase_req.purchase_ids) == 2, "There must be 2 RFQs linked to this Call for bids"
+    for rfq in purchase_req.purchase_ids:
+        assert rfq.type == 'rfq', "The type must be rfq"
+        for line in rfq.order_line:
+            assert line.price_subtotal == 0, "The price must be 0 in the RFQ"
+        assert rfq.state == 'draft', "The state must be draft"
+        assert rfq.amount_untaxed == 0
+-
+  I send the RFQs. For this, I print the RFQ.
+-
+  !python {model: purchase.requisition}: |
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    for rfq in purchase_req.purchase_ids:
+        self.pool.get('purchase.order').print_quotation(cr, uid, [rfq.id])
+-
+  I check the RFQs are in "RFQ sent" state.
+-
+  !python {model: purchase.requisition}: |
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    assert len(purchase_req.purchase_ids) == 2, "There must be 2 RFQs linked to this Call for bids"
+    for rfq in purchase_req.purchase_ids:
+        assert rfq.type == 'rfq', "The type must be rfq"
+        for line in rfq.order_line:
+            assert line.price_subtotal == 0, "The price must be 0 in the RFQ line"
+        assert rfq.state == 'sent', "The state must be sent"
+        assert rfq.amount_untaxed == 0, "The total must be 0 in the RFQ"
+-
+  I encode the bids. I set a price on the lines.
+-
+  !python {model: purchase.requisition}: |
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    assert len(purchase_req.purchase_ids) == 2, "There must be 2 RFQs linked to this Call for bids"
+    price = 30
+    for rfq in purchase_req.purchase_ids:
+        for line in rfq.order_line:
+            self.pool.get('purchase.order.line').write(cr, uid, [line.id], {'price_unit': price})
+            price += 5
+-
+  I run the 'Bid encoded' wizard of bid1. I fill the date.
+-
+  !record {model: purchase.action_modal_datetime, id: purchase_requisition_ext_restricted1_bid1_bidencoded}:
+    datetime: '2013-08-13 00:00:00'
+-
+  I run the 'Bid encoded' wizard of bid1. I confirm the wizard.
+-
+  !python {model: purchase.requisition}: |
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    assert len(purchase_req.purchase_ids) == 2, "There must be 2 RFQs linked to this Call for bids"
+    for rfq in purchase_req.purchase_ids:
+        self.pool.get('purchase.order').bid_received_ok(cr, uid,
+            [rfq.id],
+            {'active_id': ref("purchase_requisition_ext_restricted1_bid1_bidencoded")
+             })
+-
+  I run the 'Bid encoded' wizard of bid2. I fill the date.
+-
+  !record {model: purchase.action_modal_datetime, id: purchase_requisition_ext_restricted1_bid2_bidencoded}:
+    datetime: '2013-08-13 00:10:00'
+-
+  I run the 'Bid encoded' wizard of bid2. I confirm the wizard.
+-
+  !python {model: purchase.requisition}: |
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    assert len(purchase_req.purchase_ids) == 2, "There must be 2 RFQs linked to this Call for bids"
+    for rfq in purchase_req.purchase_ids:
+        self.pool.get('purchase.order').bid_received_ok(cr, uid,
+            [rfq.id],
+            {'active_id': ref("purchase_requisition_ext_restricted1_bid2_bidencoded")
+             })
+-
+  I check the "Bid Encoded" status.
+-
+  !python {model: purchase.requisition}: |
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    assert len(purchase_req.purchase_ids) == 2, "There must be 2 bids linked to this Call for bids"
+    for rfq in purchase_req.purchase_ids:
+        assert rfq.type == 'bid', "The type must be bid"
+        for line in rfq.order_line:
+            assert line.price_subtotal != 0, "The price must not be 0 in the bid line"
+        assert rfq.state == 'bid', "The state must be bid"
+        assert rfq.amount_untaxed != 0, "The total amount must not be 0 in the bid"
+-
+  I close the Call for bids and move to bids selection
+-
+  !python {model: purchase.requisition}: |
+    import netsvc
+    wf_service = netsvc.LocalService("workflow")
+    wf_service.trg_validate(uid, 'purchase.requisition', ref("purchase_requisition_ext_restricted1"), 'open_bid', cr)
+-
+  In the bids selection, I confirm line 1 of bid 1 and line 2 of bid 2. For line 3, I select quantity 10 of bid 1 and confirm. I confirm line 3 of bid 2 for remaining quantity.
+-
+  !python {model: purchase.requisition}: |
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    assert len(purchase_req.purchase_ids) == 2, "There must be 2 bids linked to this Call for bids"
+    self.pool.get('purchase.order.line').action_confirm(cr, uid, [purchase_req.purchase_ids[0].order_line[0].id])
+    self.pool.get('purchase.order.line').action_confirm(cr, uid, [purchase_req.purchase_ids[1].order_line[1].id])
+    qtywiz = self.pool.get('bid.line.qty').create(cr, uid, {'qty': 5})
+    self.pool.get('bid.line.qty').change_qty(cr, uid, [qtywiz], {
+        'active_mode': 'purchase.order.line',
+        'active_id': purchase_req.purchase_ids[0].order_line[2].id,
+        'active_ids': [purchase_req.purchase_ids[0].order_line[2].id],
+        })
+    self.pool.get('purchase.order.line').action_confirm(cr, uid, [purchase_req.purchase_ids[1].order_line[2].id])
+-
+  For each call for bid lines, I check that the confirmed selection match the requested qty
+-
+  !python {model: purchase.requisition}: |
+    from openerp.tools.float_utils import float_is_zero
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    precision = self.pool.get('decimal.precision').precision_get(cr, 1, 'Product Unit of Measure')
+    for line in purchase_req.line_ids:
+        qty = line.product_qty
+        for pol in line.purchase_line_ids:
+            if pol.state == 'confirmed':
+                qty -= pol.quantity_bid
+        assert float_is_zero(qty,precision), "The confirmed amount is different from the requested amount"
+-
+  I close the call for bids
+-
+  !python {model: purchase.requisition}: |
+    self.close_callforbids(cr, uid, [ref("purchase_requisition_ext_restricted1")])
+-
+  I generate the POs
+-
+  !python {model: purchase.requisition}: |
+    self.generate_po(cr, uid, [ref("purchase_requisition_ext_restricted1")])
+-
+  I check the POs
+-
+  !python {model: purchase.requisition}: |
+    purchase_req = self.browse(cr, uid, ref("purchase_requisition_ext_restricted1"))
+    assert len(purchase_req.purchase_ids) == 2, "There must be 2 bids linked to this Call for bids"
+
+
+
+

=== added directory 'purchase_requisition_extended/view'
=== added file 'purchase_requisition_extended/view/purchase_order.xml'
--- purchase_requisition_extended/view/purchase_order.xml	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/view/purchase_order.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<openerp>
+<data>
+    <record model="ir.ui.view" id="view_purchase_order_form">
+        <field name="name">purchase.order.inherit</field>
+        <field name="model">purchase.order</field>
+        <field name="inherit_id" ref="purchase_extended.view_purchase_order_form"/>
+        <field name="arch" type="xml">
+            <xpath expr="//button[@name='draft_po'][1]" position="attributes">
+                <attribute name="attrs">{'invisible': ['|', ('requisition_id','!=',False)]}</attribute>
+            </xpath>
+            <xpath expr="//button[@name='draft_po'][2]" position="attributes">
+                <attribute name="attrs">{'invisible': ['|', ('requisition_id','!=',False)]}</attribute>
+            </xpath>
+        </field>
+    </record>
+    <record model="ir.ui.view" id="view_purchase_order_form2">
+        <field name="name">purchase.order.inherit</field>
+        <field name="model">purchase.order</field>
+        <field name="inherit_id" ref="purchase_requisition.purchase_order_form_inherit"/>
+        <field name="arch" type="xml">
+            <xpath expr="//field[@name='bid_validity']" position="after">
+                <field name="bid_partial"/>
+            </xpath>
+            <xpath expr="//field[@name='requisition_id']" position="after">
+                <field name="tender_bid_receipt_mode"/>
+            </xpath>
+        </field>
+    </record>
+
+    <record model="ir.ui.view" id="view_purchase_order_line_search">
+        <field name="name">purchase.order.line.inherit</field>
+        <field name="model">purchase.order.line</field>
+        <field name="inherit_id" ref="purchase.purchase_order_line_search"/>
+        <field name="arch" type="xml">
+            <xpath expr="//filter[@name='hide_cancelled']" position="before">
+                <filter name="showbids" string="Bids encoded" domain="[('order_id.state', '=', 'bid')]"/>
+            </xpath>
+            <xpath expr="//filter[@name='groupby_product']" position="after">
+                <filter name="groupby_requisitionline" string="Call for Bids line" domain="[]" context="{'group_by' : 'requisition_line_id'}" />
+            </xpath>
+        </field>
+    </record>
+    <record model="ir.ui.view" id="view_purchase_order_line_tree">
+        <field name="name">purchase.order.line.inherit</field>
+        <field name="model">purchase.order.line</field>
+        <field name="inherit_id" ref="purchase_requisition.purchase_order_line_tree_tender"/>
+        <field name="arch" type="xml">
+            <xpath expr="//field[@name='name']" position="before">
+                <field name="requisition_line_id" invisible="1"/>
+            </xpath>
+            <xpath expr="//field[@name='lead_time']" position="before">
+                <field name="date_planned"/>
+            </xpath>
+            <xpath expr="//field[@name='product_id']" position="attributes">
+                <attribute name="invisible">1</attribute>
+            </xpath>
+        </field>
+    </record>
+</data>
+</openerp>

=== added file 'purchase_requisition_extended/view/purchase_requisition.xml'
--- purchase_requisition_extended/view/purchase_requisition.xml	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/view/purchase_requisition.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record model="ir.ui.view" id="view_purchase_requisition_form">
+            <field name="name">purchase.requisition.form.inherit</field>
+            <field name="model">purchase.requisition</field>
+            <field name="inherit_id" ref="purchase_requisition.view_purchase_requisition_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//header/field[@name='state']" position="attributes">
+                    <attribute name="statusbar_visible">draft,in_progress,open,closed,done</attribute>
+                </xpath>
+                <xpath expr="//header/button[@name='generate_po']" position="attributes">
+                    <attribute name="states">closed</attribute>
+                    <attribute name="string">Generate PO</attribute>
+                </xpath>
+                <xpath expr="//sheet//button[@name='open_product_line']" position="attributes">
+                    <attribute name="attrs">{'invisible': ['|', ('state', 'not in', ('open','closed','done')), ('exclusive', '=', 'exclusive')]}</attribute>
+                </xpath>
+                <xpath expr="//sheet//button[@name='open_product_line']" position="after">
+                    <button name="open_po" type="object" string="View Generated PO" states="open,closed,done"/>
+                </xpath>
+                <xpath expr="//field[@name='user_id']" position="after">
+                    <field name="bid_tendering_mode" attrs="{'readonly': [('state','not in',('draft'))]}" required="1"/>
+                    <field name="pricelist_id" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+                </xpath>
+                <separator string="Requests for Quotation" position="attributes">
+                    <attribute name="string">Requests for Quotation / Bids</attribute>
+                </separator>
+                <xpath expr="//page[@string='Products']//button[@name='%(purchase_requisition.action_purchase_requisition_partner)d']" position="attributes">
+                    <attribute name="attrs">{'invisible': ['|','|',('bid_tendering_mode','=','open'),('line_ids','=',[]),('state','!=','in_progress')]}</attribute>
+                </xpath>
+                <xpath expr="//page[@string='Products']//button[@name='open_rfq']" position="attributes">
+                    <attribute name="attrs">{'invisible': ['|',('purchase_ids','=',[]),('state', 'in', ('draft'))]}</attribute>
+                </xpath>
+                <xpath expr="//page[@string='Products']//button[@name='%(purchase_requisition.action_purchase_requisition_partner)d']" position="after">
+                    <button name="%(action_purchase_requisition_partner_draftbid)d" type="action"
+                        string="Encode a Bid" icon="gtk-execute"
+                        attrs="{'invisible': ['|','|',('bid_tendering_mode','=','restricted'),('line_ids','=',[]),('state','!=','in_progress')]}"/>
+                </xpath>
+                <xpath expr="//page[@string='Products']" position="after">
+                    <page string="Options">
+                        <group>
+                            <group>
+                                <field name="req_validity" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+                                <field name="req_incoterm_id" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+                                <field name="req_incoterm_address" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+                                <field name="req_payment_term_id" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+                            </group>
+                            <group>
+                                <field name="bid_receipt_mode" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+                            </group>
+                        </group>
+                    </page>
+                </xpath>
+                <button name="open_bid" position="after">
+                    <!--<button name="close_bid" states="open" string="Close Bids Selection" class="oe_highlight" type="object"/>-->
+                    <button name="reopen_bid" states="closed" string="Re-Open Bids Selection"/>
+                </button>
+                <xpath expr="//field[@name='purchase_ids']//button[@name='purchase_cancel']" position="attributes">
+                    <attribute name="invisible">1</attribute>
+                </xpath>
+                <xpath expr="//field[@name='purchase_ids']//button[@name='purchase_confirm']" position="attributes">
+                    <attribute name="invisible">1</attribute>
+                </xpath>
+                <xpath expr="//field[@name='purchase_ids']//button[@name='purchase_approve']" position="attributes">
+                    <attribute name="invisible">1</attribute>
+                </xpath>
+                <xpath expr="//field[@name='purchase_ids']//button[@name='wkf_send_rfq']" position="attributes">
+                    <attribute name="icon">gtk-apply</attribute>
+                </xpath>
+                <xpath expr="//field[@name='exclusive']" position="attributes">
+                    <attribute name="invisible">1</attribute>
+                </xpath>
+                <field name="warehouse_id" position="before">
+                    <field name="consignee_id" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+                    <field name="dest_address_id" on_change="onchange_dest_address_id(dest_address_id, warehouse_id)" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+                </field>
+                <field name="warehouse_id" position="replace">
+                    <field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+                </field>
+            </field>
+        </record>
+     </data>
+</openerp>

=== added directory 'purchase_requisition_extended/wizard'
=== added directory 'purchase_requisition_extended/wizard.moved'
=== added file 'purchase_requisition_extended/wizard/__init__.py'
--- purchase_requisition_extended/wizard/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/wizard/__init__.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+import purchase_requisition_partner

=== added file 'purchase_requisition_extended/wizard/modal.xml'
--- purchase_requisition_extended/wizard/modal.xml	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/wizard/modal.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="action_modal_close_callforbids" model="ir.ui.view">
+            <field name="name">action.modal</field>
+            <field name="model">purchase.action_modal</field>
+            <field name="arch" type="xml">
+                <form string="Warning" version="7.0">
+                    <p>
+                        For some lines, the selected quantity does not match the requested quantity.
+                        Please confirm the operation.
+                    </p>
+                    <footer>
+                        <button string="Confirm" class="oe_highlight" type="object" name="action"/>
+                        or
+                        <button special="cancel" string="Cancel" class="oe_link"/>
+                    </footer>
+                </form>
+            </field>
+        </record>
+    </data>
+</openerp>

=== added file 'purchase_requisition_extended/wizard/purchase_requisition_partner.py'
--- purchase_requisition_extended/wizard/purchase_requisition_partner.py	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/wizard/purchase_requisition_partner.py	2015-01-19 14:42:10 +0000
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+from openerp.osv import fields, orm
+from openerp.tools.translate import _
+
+
+class purchase_requisition_partner(orm.TransientModel):
+    _inherit = "purchase.requisition.partner"
+
+    def create_order(self, cr, uid, ids, context=None):
+        if context is None:
+            context = {}
+        active_id = context and context.get('active_id', [])
+        data = self.browse(cr, uid, ids, context=context)[0]
+        po_id = self.pool.get('purchase.requisition').make_purchase_order(cr,
+                    uid, [active_id], data.partner_id.id,
+                    context=context)[active_id]
+        if not context.get('draft_bid', False):
+            return {'type': 'ir.actions.act_window_close'}
+        res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid,
+                    'purchase', 'purchase_rfq', context=context)
+        res.update({'res_id': po_id,
+                    'views': [(False, 'form')],
+                    })
+        return res

=== added file 'purchase_requisition_extended/wizard/purchase_requisition_partner_view.xml'
--- purchase_requisition_extended/wizard/purchase_requisition_partner_view.xml	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/wizard/purchase_requisition_partner_view.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,31 @@
+<openerp>
+<data>
+    <record id="view_purchase_requisition_partner_draftbid" model="ir.ui.view">
+        <field name="name">Choose Supplier</field>
+        <field name="model">purchase.requisition.partner</field>
+        <field name="priority">50</field>
+        <field name="arch" type="xml">
+            <form string="Choose Supplier" version="7.0">
+                <group>
+                    <field name="partner_id" context="{'default_supplier': 1, 'default_customer': 0}"/>
+                </group>
+                <footer>
+                    <button name="create_order" string="Create Bid" type="object" class="oe_highlight"/>
+                    or
+                    <button string="Cancel" class="oe_link" special="cancel" />
+                </footer>
+            </form>
+        </field>
+    </record>
+    <record id="action_purchase_requisition_partner_draftbid" model="ir.actions.act_window">
+        <field name="name">Choose Supplier</field>
+        <field name="type">ir.actions.act_window</field>
+        <field name="res_model">purchase.requisition.partner</field>
+        <field name="view_type">form</field>
+        <field name="view_mode">form</field>
+        <field name="view_id" ref="view_purchase_requisition_partner_draftbid"/>
+        <field name="context">{'record_id' : active_id, 'draft_bid': 1}</field>
+        <field name="target">new</field>
+    </record>
+</data>
+</openerp>

=== added directory 'purchase_requisition_extended/workflow'
=== added file 'purchase_requisition_extended/workflow/purchase_requisition.xml'
--- purchase_requisition_extended/workflow/purchase_requisition.xml	1970-01-01 00:00:00 +0000
+++ purchase_requisition_extended/workflow/purchase_requisition.xml	2015-01-19 14:42:10 +0000
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <!-- Add intermediate activity 'closed' between 'open' and 'done' -->
+        <record id="act_closed" model="workflow.activity">
+            <field name="wkf_id" ref="purchase_requisition.purchase_requisition_workflow"/>
+            <field name="name">Bids selected</field>
+            <field name="kind">function</field>
+            <field name="action">tender_close()</field>
+        </record>
+        <record id="purchase_requisition.trans_open_done" model="workflow.transition">
+            <field name="act_from" ref="act_closed"/>
+        </record>
+        <record id="trans_open_closed" model="workflow.transition">
+            <field name="act_from" ref="purchase_requisition.act_open"/>
+            <field name="act_to" ref="act_closed"/>
+            <field name="signal">close_bid</field>
+        </record>
+        <record id="trans_closed_open" model="workflow.transition">
+            <field name="act_from" ref="act_closed"/>
+            <field name="act_to" ref="purchase_requisition.act_open"/>
+            <field name="signal">reopen_bid</field>
+        </record>
+
+        <!-- Check there are product lines when confirming the Call for Bids -->
+        <record id="purchase_requisition.trans_draft_sent" model="workflow.transition">
+            <field name="condition">_has_product_lines()</field>
+        </record>
+    </data>
+</openerp>