summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorFabien Pinckaers <fp@openerp.com>2016-04-29 08:21:20 +0200
committerNicolas Martinelli <nim@odoo.com>2016-04-29 10:43:49 +0200
commitff110b0ed8f9e50e5e523bbad696e40a8a5555e3 (patch)
treee6e88a90352a6c3ed60d23afd146c7f2baceb0c8 /addons
parent3938fb27e51f96103bdc5864f906660eb7821b9f (diff)
[IMP] purchase_requisition: blanket orders and usability cleaning
Improved usability & code cleaning: - Support for blanket orders, in addition of purchase tenders ("Purchase Agreements") - Create a RfQ in one click, no wizard anymore - Control of quantities really purchased - Better control of side-effects thanks to purchase requisition types - JS code removed (not necessary) - Workflow removed Closes #11666
Diffstat (limited to 'addons')
-rw-r--r--addons/purchase/res_config.py6
-rw-r--r--addons/purchase/res_config_view.xml2
-rw-r--r--addons/purchase/security/purchase_security.xml5
-rw-r--r--addons/purchase_requisition/__init__.py1
-rw-r--r--addons/purchase_requisition/__openerp__.py20
-rw-r--r--addons/purchase_requisition/purchase_requisition.py458
-rw-r--r--addons/purchase_requisition/purchase_requisition_data.xml20
-rw-r--r--addons/purchase_requisition/purchase_requisition_demo.xml1
-rw-r--r--addons/purchase_requisition/purchase_requisition_sequence.xml12
-rw-r--r--addons/purchase_requisition/purchase_requisition_view.xml222
-rw-r--r--addons/purchase_requisition/purchase_requisition_workflow.xml69
-rw-r--r--addons/purchase_requisition/security/ir.model.access.csv2
-rw-r--r--addons/purchase_requisition/static/src/js/web_addons.js38
-rw-r--r--addons/purchase_requisition/static/src/xml/purchase_requisition.xml10
-rw-r--r--addons/purchase_requisition/test/cancel_purchase_requisition.yml4
-rw-r--r--addons/purchase_requisition/test/purchase_requisition.yml57
-rw-r--r--addons/purchase_requisition/test/purchase_requisition_demo.yml25
-rw-r--r--addons/purchase_requisition/views/purchase_requisition.xml10
-rw-r--r--addons/purchase_requisition/views/report_purchaserequisition.xml3
-rw-r--r--addons/purchase_requisition/wizard/__init__.py5
-rw-r--r--addons/purchase_requisition/wizard/bid_line_qty.py18
-rw-r--r--addons/purchase_requisition/wizard/bid_line_qty_view.xml30
-rw-r--r--addons/purchase_requisition/wizard/purchase_requisition_partner.py31
-rw-r--r--addons/purchase_requisition/wizard/purchase_requisition_partner_view.xml31
24 files changed, 350 insertions, 730 deletions
diff --git a/addons/purchase/res_config.py b/addons/purchase/res_config.py
index fb959e1d2fe..b65a4acbd63 100644
--- a/addons/purchase/res_config.py
+++ b/addons/purchase/res_config.py
@@ -34,12 +34,6 @@ class purchase_config_settings(osv.osv_memory):
help="Calls for tenders are used when you want to generate requests for quotations to several vendors for a given set of products.\n"
"You can configure per product if you directly do a Request for Quotation "
"to one vendor or if you want a Call for Tenders to compare offers from several vendors."),
- 'group_advance_purchase_requisition': fields.selection([
- (0, 'Simple call for tender (only choose from one RFQ)'),
- (1, 'Advanced call for tender (choose products from different RFQ)')
- ], "Advanced Calls for Tenders",
- implied_group='purchase.group_advance_bidding',
- help="""In the process of a public tendering, you can compare the tender lines and choose for each requested product which quantity you will buy from each bid."""),
'group_warning': fields.selection([
(0, 'All the products and the customers can be used in purchase orders'),
(1, 'An informative or blocking warning can be set on a product or a customer')
diff --git a/addons/purchase/res_config_view.xml b/addons/purchase/res_config_view.xml
index d29aa70a348..84bd592dc43 100644
--- a/addons/purchase/res_config_view.xml
+++ b/addons/purchase/res_config_view.xml
@@ -15,8 +15,6 @@
<field name="group_uom" widget="radio"/>
<field name="group_costing_method" widget="radio"/>
<field name="module_purchase_requisition" widget="radio"/>
- <field name="group_advance_purchase_requisition" widget="radio"
- attrs="{'invisible': [('module_purchase_requisition', '=', 0)]}"/>
<field name="group_manage_vendor_price" widget="radio"/>
<field name="group_warning" widget="radio"/>
</group>
diff --git a/addons/purchase/security/purchase_security.xml b/addons/purchase/security/purchase_security.xml
index 52d95abf237..3f7f4d750d0 100644
--- a/addons/purchase/security/purchase_security.xml
+++ b/addons/purchase/security/purchase_security.xml
@@ -25,11 +25,6 @@
<field name="implied_ids" eval="[(4, ref('analytic.group_analytic_accounting'))]"/>
</record>
- <record id="group_advance_bidding" model="res.groups">
- <field name="name">Advance bidding process</field>
- <field name="category_id" ref="base.module_category_hidden"/>
- </record>
-
<record id="group_manage_vendor_price" model="res.groups">
<field name="name">Manage Vendor Price</field>
<field name="category_id" ref="base.module_category_hidden"/>
diff --git a/addons/purchase_requisition/__init__.py b/addons/purchase_requisition/__init__.py
index 77f74ea0e52..5dc71cd70fe 100644
--- a/addons/purchase_requisition/__init__.py
+++ b/addons/purchase_requisition/__init__.py
@@ -1,4 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import purchase_requisition
-import wizard
diff --git a/addons/purchase_requisition/__openerp__.py b/addons/purchase_requisition/__openerp__.py
index 3ba618c1ac1..90f870c0399 100644
--- a/addons/purchase_requisition/__openerp__.py
+++ b/addons/purchase_requisition/__openerp__.py
@@ -14,16 +14,13 @@ keep track and order all your purchase orders.
""",
'depends' : ['purchase'],
'demo': ['purchase_requisition_demo.xml'],
- 'data': ['views/purchase_requisition.xml',
- 'security/purchase_tender.xml',
- 'wizard/purchase_requisition_partner_view.xml',
- 'wizard/bid_line_qty_view.xml',
- 'purchase_requisition_data.xml',
- 'purchase_requisition_view.xml',
- 'purchase_requisition_report.xml',
- 'purchase_requisition_workflow.xml',
- 'security/ir.model.access.csv','purchase_requisition_sequence.xml',
- 'views/report_purchaserequisition.xml',
+ 'data': [
+ 'security/purchase_tender.xml',
+ 'purchase_requisition_data.xml',
+ 'purchase_requisition_view.xml',
+ 'purchase_requisition_report.xml',
+ 'security/ir.model.access.csv',
+ 'views/report_purchaserequisition.xml',
],
'auto_install': False,
'test': [
@@ -32,8 +29,5 @@ keep track and order all your purchase orders.
'test/cancel_purchase_requisition.yml',
'test/purchase_requisition.yml',
],
- 'qweb': [
- 'static/src/xml/purchase_requisition.xml',
- ],
'installable': True,
}
diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py
index f4f8b013c1f..f9731f9070c 100644
--- a/addons/purchase_requisition/purchase_requisition.py
+++ b/addons/purchase_requisition/purchase_requisition.py
@@ -3,304 +3,120 @@
from datetime import datetime
from openerp.osv import fields, osv
-from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
from openerp.exceptions import UserError
+class purchase_requisition_type(osv.osv):
+ _name = "purchase.requisition.type"
+ _description = "Purchase Agreement Type"
+ _order = "sequence"
+ _columns = {
+ 'name': fields.char('Agreement Type', required=True, translate=True),
+ 'sequence': fields.integer('Sequence'),
+ 'exclusive': fields.selection([
+ ('exclusive', 'Select only one RFQ (exclusive)'), ('multiple', 'Select multiple RFQ')],\
+ 'Agreement Selection Type', required=True,\
+ help="""Select only one RFQ (exclusive): when a purchase order is confirmed, cancel the remaining purchase order.\n
+ Select multiple RFQ: allows multiple purchase orders. On confirmation of a purchase order it does not cancel the remaining orders"""),
+ 'quantity_copy': fields.selection([
+ ('copy', 'Use quantities of agreement'), ('none', 'Set quantities manually')],\
+ 'Quantities', required=True),
+ 'line_copy': fields.selection([
+ ('copy', 'Use lines of agreement'), ('none', 'Do not create RfQ lines automatically')],\
+ 'Lines', required=True),
+ }
+ _defaults = {
+ 'exclusive': 'multiple',
+ 'quantity_copy': 'none',
+ 'line_copy': 'copy',
+ 'sequence': 1
+ }
+
class purchase_requisition(osv.osv):
_name = "purchase.requisition"
_description = "Purchase Requisition"
_inherit = ['mail.thread', 'ir.needaction_mixin']
+ _order = "id desc"
- def _get_po_line(self, cr, uid, ids, field_names, arg=None, context=None):
- result = dict((res_id, []) for res_id in ids)
- for element in self.browse(cr, uid, ids, context=context):
- for po in element.purchase_ids:
- result[element.id] += [po_line.id for po_line in po.order_line]
+ def _compute_orders_number(self, cr, uid, ids, fieldnames, args, context=None):
+ result = dict.fromkeys(ids, 0)
+ for order in self.browse(cr, uid, ids, context=context):
+ result[order.id] = len(order.purchase_ids)
return result
_columns = {
- 'name': fields.char('Call for Tenders Reference', required=True, copy=False),
+ 'name': fields.char('Agreement Reference', required=True, copy=False),
'origin': fields.char('Source Document'),
- 'ordering_date': fields.date('Scheduled Ordering Date'),
- 'date_end': fields.datetime('Tender Closing Deadline'),
- 'schedule_date': fields.date('Scheduled Date', select=True, help="The expected and scheduled delivery date where all the products are received"),
+ 'order_count': fields.function(_compute_orders_number, 'Number of Orders', type='integer'),
+ 'vendor_id': fields.many2one('res.partner', string="Vendor"),
+ 'type_id': fields.many2one('purchase.requisition.type', string="Agreement Type", required=True),
+ 'ordering_date': fields.date('Ordering Date'),
+ 'date_end': fields.datetime('Agreement Deadline'),
+ 'schedule_date': fields.date('Delivery Date', select=True, help="The expected and scheduled delivery date where all the products are received"),
'user_id': fields.many2one('res.users', 'Responsible'),
- 'exclusive': fields.selection([('exclusive', 'Select only one RFQ (exclusive)'), ('multiple', 'Select multiple RFQ')], 'Tender Selection Type', required=True, help="Select only one RFQ (exclusive): On the confirmation of a purchase order, it cancels the remaining purchase order.\nSelect multiple RFQ: It allows to have multiple purchase orders.On confirmation of a purchase order it does not cancel the remaining orders"""),
'description': fields.text('Description'),
'company_id': fields.many2one('res.company', 'Company', required=True),
'purchase_ids': fields.one2many('purchase.order', 'requisition_id', 'Purchase Orders', states={'done': [('readonly', True)]}),
- 'po_line_ids': fields.function(_get_po_line, method=True, type='one2many', relation='purchase.order.line', string='Products by vendor'),
'line_ids': fields.one2many('purchase.requisition.line', 'requisition_id', 'Products to Purchase', states={'done': [('readonly', True)]}, copy=True),
'procurement_id': fields.many2one('procurement.order', 'Procurement', ondelete='set null', copy=False),
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
'state': fields.selection([('draft', 'Draft'), ('in_progress', 'Confirmed'),
- ('open', 'Bid Selection'), ('done', 'PO Created'),
+ ('open', 'Bid Selection'), ('done', 'Done'),
('cancel', 'Cancelled')],
'Status', track_visibility='onchange', required=True,
copy=False),
- 'multiple_rfq_per_supplier': fields.boolean('Multiple RFQ per vendor'),
'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', required=True),
+
}
def _get_picking_in(self, cr, uid, context=None):
obj_data = self.pool.get('ir.model.data')
return obj_data.get_object_reference(cr, uid, 'stock', 'picking_type_in')[1]
+ def _get_type_id(self, cr, uid, context=None):
+ types = self.pool.get('purchase.requisition.type').search(cr, uid, [], context=context, limit=1)
+ return types[0] if types else False
+
_defaults = {
'state': 'draft',
- 'exclusive': 'multiple',
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.requisition', context=c),
'user_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).id,
'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').next_by_code(cr, uid, 'purchase.order.requisition'),
+ 'type_id': _get_type_id,
'picking_type_id': _get_picking_in,
}
- def tender_cancel(self, cr, uid, ids, context=None):
+ def action_cancel(self, cr, uid, ids, context=None):
purchase_order_obj = self.pool.get('purchase.order')
# try to set all associated quotations to cancel state
- for tender in self.browse(cr, uid, ids, context=context):
- for purchase_order in tender.purchase_ids:
- purchase_order_obj.button_cancel(cr, uid, [purchase_order.id], context=context)
- purchase_order_obj.message_post(cr, uid, [purchase_order.id], body=_('Cancelled by the tender associated to this quotation.'), context=context)
- return self.write(cr, uid, ids, {'state': 'cancel'})
+ for requisition in self.browse(cr, uid, ids, context=context):
+ for po in requisition.purchase_ids:
+ purchase_order_obj.button_cancel(cr, uid, [po.id], context=context)
+ purchase_order_obj.message_post(cr, uid, [po.id], body=_('Cancelled by the agreement associated to this quotation.'), context=context)
+ return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
- def tender_in_progress(self, cr, uid, ids, context=None):
+ def action_in_progress(self, cr, uid, ids, context=None):
if not all(obj.line_ids for obj in self.pool['purchase.requisition'].browse(cr, uid, ids, context=context)):
- raise UserError(_('You can not confirm call because there is no product line.'))
+ raise UserError(_('You cannot confirm call because there is no product line.'))
return self.write(cr, uid, ids, {'state': 'in_progress'}, context=context)
- def tender_open(self, cr, uid, ids, context=None):
+ def action_open(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
- def tender_reset(self, cr, uid, ids, context=None):
- self.write(cr, uid, ids, {'state': 'draft'})
- for p_id in ids:
- # Deleting the existing instance of workflow for PO
- self.delete_workflow(cr, uid, [p_id])
- self.create_workflow(cr, uid, [p_id])
- return True
-
- def tender_done(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state': 'done'}, context=context)
+ def action_draft(self, cr, uid, ids, context=None):
+ return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
- def open_product_line(self, cr, uid, ids, context=None):
- """ This opens product line view to view all lines from the different quotations, groupby default by product and partner to show comparaison
- between vendor price
- @return: the product line tree view
+ def action_done(self, cr, uid, ids, context=None):
"""
- if context is None:
- context = {}
- res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'purchase_requisition', 'purchase_line_tree', context=context)
- res['context'] = context
- po_lines = self.browse(cr, uid, ids, context=context)[0].po_line_ids
- res['context'] = {
- 'search_default_groupby_product': True,
- 'search_default_hide_cancelled': True,
- 'tender_id': ids[0],
- }
- res['domain'] = [('id', 'in', [line.id for line in po_lines])]
- return res
-
- def open_rfq(self, cr, uid, ids, context=None):
- """ This opens rfq view to view all quotations associated to the call for tenders
- @return: the RFQ tree view
+ Generate all purchase order based on selected lines, should only be called on one agreement at a time
"""
- if context is None:
- context = {}
- res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'purchase', 'purchase_rfq', context=context)
- res['context'] = context
- po_ids = [po.id for po in self.browse(cr, uid, ids, context=context)[0].purchase_ids]
- res['domain'] = [('id', 'in', po_ids)]
- return res
-
- def _prepare_purchase_order(self, cr, uid, requisition, supplier, context=None):
- return {
- 'origin': requisition.name,
- 'date_order': requisition.date_end or fields.datetime.now(),
- 'partner_id': supplier.id,
- 'currency_id': requisition.company_id and requisition.company_id.currency_id.id,
- 'company_id': requisition.company_id.id,
- 'fiscal_position_id': self.pool.get('account.fiscal.position').get_fiscal_position(cr, uid, supplier.id, context=context),
- 'requisition_id': requisition.id,
- 'notes': requisition.description,
- 'picking_type_id': requisition.picking_type_id.id
- }
-
- def _prepare_purchase_order_line(self, cr, uid, requisition, requisition_line, purchase_id, supplier, context=None):
- if context is None:
- context = {}
- po_obj = self.pool.get('purchase.order')
- po_line_obj = self.pool.get('purchase.order.line')
- product_uom = self.pool.get('product.uom')
- product = requisition_line.product_id
- default_uom_po_id = product.uom_po_id.id
- ctx = context.copy()
- ctx['tz'] = requisition.user_id.tz
- date_order = requisition.ordering_date and fields.date.date_to_datetime(self, cr, uid, requisition.ordering_date, context=ctx) or fields.datetime.now()
- qty = product_uom._compute_qty(cr, uid, requisition_line.product_uom_id.id, requisition_line.product_qty, default_uom_po_id)
-
- taxes = product.supplier_taxes_id
- fpos = supplier.property_account_position_id
- taxes_id = fpos.map_tax(taxes).ids if fpos else []
-
- po = po_obj.browse(cr, uid, [purchase_id], context=context)
- seller = requisition_line.product_id._select_seller(
- requisition_line.product_id,
- partner_id=supplier,
- quantity=qty,
- date=date_order and date_order[:10],
- uom_id=product.uom_po_id)
-
- price_unit = seller.price if seller else 0.0
- if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id:
- price_unit = seller.currency_id.compute(price_unit, po.currency_id)
-
- date_planned = po_line_obj._get_date_planned(cr, uid, seller, po=po, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-
- product_lang = requisition_line.product_id.with_context({
- 'lang': supplier.lang,
- 'partner_id': supplier.id,
- })
- name = product_lang.display_name
- if product_lang.description_purchase:
- name += '\n' + product_lang.description_purchase
-
- vals = {
- 'name': name,
- 'order_id': purchase_id,
- 'product_qty': qty,
- 'product_id': product.id,
- 'product_uom': default_uom_po_id,
- 'price_unit': price_unit,
- 'date_planned': date_planned,
- 'taxes_id': [(6, 0, taxes_id)],
- 'account_analytic_id': requisition_line.account_analytic_id.id,
- }
-
- return vals
-
- def make_purchase_order(self, cr, uid, ids, partner_id, context=None):
- """
- Create New RFQ for Vendor
- """
- context = dict(context or {})
- assert partner_id, 'Vendor should be specified'
- purchase_order = self.pool.get('purchase.order')
- purchase_order_line = self.pool.get('purchase.order.line')
- res_partner = self.pool.get('res.partner')
- supplier = res_partner.browse(cr, uid, partner_id, context=context)
- res = {}
- for requisition in self.browse(cr, uid, ids, context=context):
- if not requisition.multiple_rfq_per_supplier and supplier.id in filter(lambda x: x, [rfq.state != 'cancel' and rfq.partner_id.id or None for rfq in requisition.purchase_ids]):
- raise UserError(_('You have already one %s purchase order for this partner, you must cancel this purchase order to create a new quotation.') % rfq.state)
- context.update({'mail_create_nolog': True})
- purchase_id = purchase_order.create(cr, uid, self._prepare_purchase_order(cr, uid, requisition, supplier, context=context), context=context)
- purchase_order.message_post(cr, uid, [purchase_id], body=_("RFQ created"), context=context)
- res[requisition.id] = purchase_id
- for line in requisition.line_ids:
- purchase_order_line.create(cr, uid, self._prepare_purchase_order_line(cr, uid, requisition, line, purchase_id, supplier, context=context), context=context)
- return res
-
- def check_valid_quotation(self, cr, uid, quotation, context=None):
- """
- Check if a quotation has all his order lines bid in order to confirm it if its the case
- return True if all order line have been selected during tendering process, else return False
-
- args : 'quotation' must be a browse record
- """
- for line in quotation.order_line:
- if line.product_qty != line.quantity_tendered:
- return False
- return True
-
- def _prepare_po_from_tender(self, cr, uid, tender, context=None):
- """ Prepare the values to write in the purchase order
- created from a tender.
-
- :param tender: the source tender from which we generate a purchase order
- """
- return {'order_line': [],
- 'requisition_id': tender.id,
- 'origin': tender.name}
-
- def _prepare_po_line_from_tender(self, cr, uid, tender, line, purchase_id, context=None):
- """ Prepare the values to write in the purchase order line
- created from a line of the tender.
-
- :param tender: the source tender from which we generate a purchase order
- :param line: the source tender's line from which we generate a line
- :param purchase_id: the id of the new purchase
- """
- return {'product_qty': line.quantity_tendered,
- 'order_id': purchase_id}
-
- def generate_po(self, cr, uid, ids, context=None):
- """
- Generate all purchase order based on selected lines, should only be called on one tender at a time
- """
- po = self.pool.get('purchase.order')
- poline = self.pool.get('purchase.order.line')
- id_per_supplier = {}
- for tender in self.browse(cr, uid, ids, context=context):
- if tender.state == 'done':
- raise UserError(_('You have already generate the purchase order(s).'))
-
- confirm = False
- #check that we have at least confirm one line
- for po_line in tender.po_line_ids:
- if po_line.quantity_tendered > 0:
- confirm = True
- break
- if not confirm:
- raise UserError(_('You have no line selected for buying.'))
-
- #check for complete RFQ
- for quotation in tender.purchase_ids:
- if (self.check_valid_quotation(cr, uid, quotation, context=context)):
- #Set PO state to confirm
- po.button_confirm(cr, uid, [quotation.id], context=context)
-
- #get other confirmed lines per supplier
- for po_line in tender.po_line_ids:
- #only take into account confirmed line that does not belong to already confirmed purchase order
- if po_line.quantity_tendered > 0 and po_line.order_id.state in ['draft', 'sent', 'to approve']:
- if id_per_supplier.get(po_line.partner_id.id):
- id_per_supplier[po_line.partner_id.id].append(po_line)
- else:
- id_per_supplier[po_line.partner_id.id] = [po_line]
-
- #generate po based on supplier and cancel all previous RFQ
- ctx = dict(context or {}, force_requisition_id=True)
- for supplier, product_line in id_per_supplier.items():
- #copy a quotation for this supplier and change order_line then validate it
- quotation_id = po.search(cr, uid, [('requisition_id', '=', tender.id), ('partner_id', '=', supplier)], limit=1)[0]
- vals = self._prepare_po_from_tender(cr, uid, tender, context=context)
- new_po = po.copy(cr, uid, quotation_id, default=vals, context=context)
- #duplicate po_line and change product_qty if needed and associate them to newly created PO
- for line in product_line:
- vals = self._prepare_po_line_from_tender(cr, uid, tender, line, new_po, context=context)
- poline.copy(cr, uid, line.id, default=vals, context=context)
- #use workflow to set new PO state to confirm
- po.button_confirm(cr, uid, [new_po], context=context)
-
- #cancel other orders
- self.cancel_unconfirmed_quotations(cr, uid, tender, context=context)
-
- #set tender to state done
- self.signal_workflow(cr, uid, [tender.id], 'done')
- return True
-
- def cancel_unconfirmed_quotations(self, cr, uid, tender, context=None):
- #cancel other orders
- po = self.pool.get('purchase.order')
- for quotation in tender.purchase_ids:
- if quotation.state in ['draft', 'sent', 'to approve']:
- self.pool.get('purchase.order').button_cancel(cr, uid, [quotation.id], context=context)
- po.message_post(cr, uid, [quotation.id], body=_('Cancelled by the call for tenders associated to this request for quotation.'), context=context)
- return True
+ requisitions = self.browse(cr, uid, ids, context=context)
+ if requisitions.mapped('purchase_ids') and any(r.state in ['draft', 'sent', 'to approve'] for r in requisitions.mapped('purchase_ids')):
+ raise UserError(_('You have to cancel or validate every RfQ before closing the purchase requisition.'))
+ return self.write(cr, uid, ids, {'state': 'done'}, context=context)
class purchase_requisition_line(osv.osv):
@@ -308,17 +124,32 @@ class purchase_requisition_line(osv.osv):
_description = "Purchase Requisition Line"
_rec_name = 'product_id'
+ def _compute_ordered_qty(self, cr, uid, ids, fieldnames, args, context=None):
+ result = dict.fromkeys(ids, 0)
+ for line in self.browse(cr, uid, ids, context=context):
+ total = 0.0
+ for po in line.requisition_id.purchase_ids.filtered(lambda r: r.state in ['purchase', 'done']):
+ for po_line in po.order_line.filtered(lambda r: r.product_id == line.product_id):
+ if po_line.product_uom != line.product_uom_id:
+ total += self.pool.get('product.uom')._compute_qty_obj(po_line.product_uom, po_line.product_qty, line.product_uom_id)
+ else:
+ total += po_line.product_qty
+ result[line.id] = total
+ return result
+
_columns = {
- 'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok', '=', True)]),
+ 'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok', '=', True)], required=True),
'product_uom_id': fields.many2one('product.uom', 'Product Unit of Measure'),
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure')),
- 'requisition_id': fields.many2one('purchase.requisition', 'Call for Tenders', ondelete='cascade'),
+ 'price_unit': fields.float('Unit Price', digits_compute=dp.get_precision('Product Price')),
+ 'qty_ordered': fields.function(_compute_ordered_qty, string='Ordered Quantities', type='float'),
+ 'requisition_id': fields.many2one('purchase.requisition', 'Purchase Agreement', ondelete='cascade'),
'company_id': fields.related('requisition_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
'schedule_date': fields.date('Scheduled Date'),
}
- def onchange_product_id(self, cr, uid, ids, product_id, product_uom_id, parent_analytic_account, analytic_account, parent_date, date, context=None):
+ def _onchange_product_id(self, cr, uid, ids, product_id, product_uom_id, parent_analytic_account, analytic_account, parent_date, date, context=None):
""" Changes UoM and name if product_id changes.
@param name: Name of the field
@param product_id: Changed product_id
@@ -338,62 +169,117 @@ class purchase_requisition_line(osv.osv):
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.requisition.line', context=c),
}
+
class purchase_order(osv.osv):
_inherit = "purchase.order"
_columns = {
- 'requisition_id': fields.many2one('purchase.requisition', 'Call for Tenders', copy=False),
+ 'requisition_id': fields.many2one('purchase.requisition', 'Purchase Agreement', copy=False),
}
- def button_confirm(self, cr, uid, ids, context=None):
- res = super(purchase_order, self).button_confirm(cr, uid, ids, context=context)
- proc_obj = self.pool.get('procurement.order')
- for po in self.browse(cr, uid, ids, context=context):
- if po.requisition_id and (po.requisition_id.exclusive == 'exclusive'):
- for order in po.requisition_id.purchase_ids:
- if order.id != po.id:
- proc_ids = proc_obj.search(cr, uid, [('purchase_id', '=', order.id)])
- if proc_ids and po.state == 'confirmed':
- proc_obj.write(cr, uid, proc_ids, {'purchase_id': po.id})
- order.button_cancel()
- po.requisition_id.tender_done(context=context)
- for element in po.order_line:
- if not element.quantity_tendered:
- element.write({'quantity_tendered': element.product_qty})
- return res
-
+ def _onchange_requisition_id(self, cr, uid, ids, requisition_id, partner_id, context=None):
+ if not requisition_id:
+ return {}
+
+ requisition = self.pool.get('purchase.requisition').browse(cr, uid, [requisition_id], context=context)
+ if partner_id:
+ partner = self.pool.get('res.partner').browse(cr, uid, [partner_id], context=context)
+ else:
+ partner = requisition.vendor_id
+ payment_term = partner.property_supplier_payment_term_id
+ currency = partner.property_purchase_currency_id or requisition.company_id.currency_id
+
+ fpos_obj = self.pool.get('account.fiscal.position')
+ fpos = fpos_obj.get_fiscal_position(cr, uid, partner.id, context=context)
+ fpos = fpos_obj.browse(cr, uid, [fpos], context=context)
+
+ value = {
+ 'partner_id': partner.id,
+ 'fiscal_position_id': fpos.id,
+ 'payment_term_id': payment_term.id,
+ 'company_id': requisition.company_id.id,
+ 'currency_id': currency.id,
+ 'origin': requisition.name,
+ 'partner_ref': requisition.name, # to control vendor bill based on agreement reference
+ 'notes': requisition.description,
+ 'date_order': requisition.date_end or fields.datetime.now(),
+ 'picking_type_id': requisition.picking_type_id.id,
+ }
-class purchase_order_line(osv.osv):
- _inherit = 'purchase.order.line'
+ if requisition.type_id.line_copy != 'copy':
+ return {'value': value}
+
+ # Create PO lines if necessary
+ order_lines = []
+ for line in requisition.line_ids:
+ # Compute name
+ product_lang = line.product_id.with_context({
+ 'lang': partner.lang,
+ 'partner_id': partner.id,
+ })
+ name = product_lang.display_name
+ if product_lang.description_purchase:
+ name += '\n' + product_lang.description_purchase
+
+ # Compute taxes
+ if fpos:
+ taxes_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos,\
+ line.product_id.supplier_taxes_id.filtered(lambda r: r.company_id == requisition.company_id), context=context)
+ else:
+ taxes_ids = line.product_id.supplier_taxes_id.filtered(lambda r: r.company_id == requisition.company_id).ids
+
+ # Compute quantity and price_unit
+ if requisition.type_id.quantity_copy != 'copy':
+ product_qty = 0
+ price_unit = line.price_unit
+ elif line.product_uom_id != line.product_id.uom_po_id:
+ product_uom_obj = self.pool.get('product.uom')
+ product_qty = product_uom_obj._compute_qty_obj(
+ cr, uid, line.product_uom_id, line.product_qty, line.product_id.uom_po_id, context=context)
+ price_unit = product_uom_obj._compute_price(
+ cr, uid, line.product_uom_id.id, line.price_unit, to_uom_id=line.product_id.uom_po_id.id)
+ else:
+ product_qty = line.product_qty
+ price_unit = line.price_unit
+
+ # Compute price_unit in appropriate currency
+ if requisition.company_id.currency_id != currency:
+ price_unit = requisition.company_id.currency_id.compute(price_unit, currency)
+
+ # Create PO line
+ order_lines.append((0, 0, {
+ 'name': name,
+ 'product_id': line.product_id.id,
+ 'product_uom': line.product_id.uom_po_id.id,
+ 'product_qty': product_qty,
+ 'price_unit': price_unit,
+ 'taxes_id': [(6, 0, taxes_ids)],
+ 'date_planned': requisition.schedule_date or datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
+ 'procurement_ids': [(6, 0, [requisition.procurement_id.id])] if requisition.procurement_id else False,
+ 'account_analytic_id': line.account_analytic_id and line.account_analytic_id.id or False,
+ }))
+ value['order_line'] = order_lines
- _columns = {
- 'quantity_tendered': fields.float('Quantity Tendered', digits_compute=dp.get_precision('Product Unit of Measure'), help="Technical field for not loosing the initial information about the quantity proposed in the tender", oldname='quantity_bid'),
- }
-
- def generate_po(self, cr, uid, tender_id, context=None):
- #call generate_po from tender with active_id. Called from js widget
- return self.pool.get('purchase.requisition').generate_po(cr, uid, [tender_id], context=context)
+ return {'value': value}
def button_confirm(self, cr, uid, ids, context=None):
- for element in self.browse(cr, uid, ids, context=context):
- self.write(cr, uid, element.id, {'quantity_tendered': element.product_qty}, context=context)
- return True
-
- def button_cancel(self, cr, uid, ids, context=None):
- self.write(cr, uid, ids, {'quantity_tendered': 0}, context=context)
- return True
+ res = super(purchase_order, self).button_confirm(cr, uid, ids, context=context)
+ for po in self.browse(cr, uid, ids, context=context):
+ if po.requisition_id and (po.requisition_id.type_id.exclusive == 'exclusive'):
+ others_po = po.requisition_id.mapped('purchase_ids').filtered(lambda r: r.id != po.id)
+ others_po.button_cancel()
+ po.requisition_id.action_done(context=context)
+ return res
class product_template(osv.osv):
_inherit = 'product.template'
-
_columns = {
'purchase_requisition': fields.selection(
[('rfq', 'Create a draft purchase order'),
('tenders', 'Propose a call for tenders')],
string='Procurement'),
}
-
_defaults = {
'purchase_requisition': 'rfq',
}
diff --git a/addons/purchase_requisition/purchase_requisition_data.xml b/addons/purchase_requisition/purchase_requisition_data.xml
index 4ccc45d1edc..39bb2fd5146 100644
--- a/addons/purchase_requisition/purchase_requisition_data.xml
+++ b/addons/purchase_requisition/purchase_requisition_data.xml
@@ -6,5 +6,23 @@
id="purchase_default_set"
model="ir.values"
name="set"/>
+
+ <record id="type_single" model="purchase.requisition.type">
+ <field name="name">Blanket Order</field>
+ <field name="sequence">3</field>
+ <field name="quantity_copy">none</field>
+ </record>
+ <record id="type_multi" model="purchase.requisition.type">
+ <field name="name">Purchase Tender</field>
+ <field name="sequence">1</field>
+ <field name="quantity_copy">copy</field>
+ </record>
+
+ <record id="seq_purchase_requisition" model="ir.sequence">
+ <field name="name">Purchase Requisition Order</field>
+ <field name="code">purchase.order.requisition</field>
+ <field name="prefix">TE</field>
+ <field name="padding">5</field>
+ </record>
</data>
-</openerp> \ No newline at end of file
+</openerp>
diff --git a/addons/purchase_requisition/purchase_requisition_demo.xml b/addons/purchase_requisition/purchase_requisition_demo.xml
index 0d12518c5a0..e09ac689017 100644
--- a/addons/purchase_requisition/purchase_requisition_demo.xml
+++ b/addons/purchase_requisition/purchase_requisition_demo.xml
@@ -14,7 +14,6 @@
<record id="requisition1" model="purchase.requisition">
<field name="user_id" ref="base.user_root"/>
- <field name="exclusive">exclusive</field>
<field name="warehouse_id" ref="stock.stock_warehouse_shop0"/>
</record>
diff --git a/addons/purchase_requisition/purchase_requisition_sequence.xml b/addons/purchase_requisition/purchase_requisition_sequence.xml
deleted file mode 100644
index 16f3619fbb1..00000000000
--- a/addons/purchase_requisition/purchase_requisition_sequence.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data noupdate="1">
- <!-- Sequences for purchase.order -->
- <record id="seq_purchase_requisition" model="ir.sequence">
- <field name="name">Purchase Requisition Order</field>
- <field name="code">purchase.order.requisition</field>
- <field name="prefix">TE</field>
- <field name="padding">5</field>
- </record>
- </data>
-</openerp>
diff --git a/addons/purchase_requisition/purchase_requisition_view.xml b/addons/purchase_requisition/purchase_requisition_view.xml
index 1e50f21606d..19ddad4be4b 100644
--- a/addons/purchase_requisition/purchase_requisition_view.xml
+++ b/addons/purchase_requisition/purchase_requisition_view.xml
@@ -2,14 +2,66 @@
<openerp>
<data>
+ <!-- Purchase Requisition Type -->
+
+ <record model="ir.ui.view" id="view_purchase_requisition_type_tree">
+ <field name="name">purchase.requisition.type.tree</field>
+ <field name="model">purchase.requisition.type</field>
+ <field name="arch" type="xml">
+ <tree string="Purchase Agreement Types">
+ <field name="sequence" widget="handle"/>
+ <field name="name"/>
+ <field name="exclusive"/>
+ </tree>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="view_purchase_requisition_type_form">
+ <field name="name">purchase.requisition.type.form</field>
+ <field name="model">purchase.requisition.type</field>
+ <field name="arch" type="xml">
+ <form string="Purchase Agreement Types">
+ <sheet>
+ <group>
+ <group string="Agreement Type">
+ <field name="name"/>
+ <field name="exclusive" widget="radio"/>
+ </group>
+ <group string="Data for new quotations">
+ <field name="line_copy" widget="radio"/>
+ <field name="quantity_copy" widget="radio"/>
+ </group>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+ <record id="tender_type_action" model="ir.actions.act_window">
+ <field name="name">Purchase Agreement Types</field>
+ <field name="res_model">purchase.requisition.type</field>
+ <field name="context">{}</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem
+ id="menu_purchase_requisition_type"
+ sequence="100"
+ parent="purchase.menu_purchase_config"
+ action="tender_type_action"/>
+
+ <!-- Purchase Orders -->
+
<record model="ir.ui.view" id="purchase_order_form_inherit">
<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">
- <field name="payment_term_id" position="before">
- <field name="requisition_id"/>
+ <field name="partner_ref" position="after">
+ <field name="requisition_id" on_change="_onchange_requisition_id(requisition_id, partner_id)"
+ domain="[('state', 'in', ('in_progress', 'open')), ('vendor_id', 'in', (partner_id, False))]"/>
</field>
+ <xpath expr="//field[@name='order_line']/tree" position="inside">
+ <field name="procurement_ids" invisible="1"/>
+ </xpath>
</field>
</record>
@@ -19,31 +71,55 @@
<field name="inherit_id" ref="purchase.view_purchase_order_filter"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='approved']" position="after">
- <filter string="Requisition" domain="[('requisition_id','!=',False)]" help="Purchase Orders with requisition"/>
+ <filter string="Requisition" domain="[('requisition_id', '!=', False)]" help="Purchase Orders with requisition"/>
</xpath>
</field>
</record>
+ <record model="ir.actions.act_window" id="action_purchase_requisition_to_so">
+ <field name="name">Request for Quotation</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">purchase.order</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form,tree</field>
+ <field name="domain">[('requisition_id','=',active_id)]</field>
+ <field name="context">{"default_requisition_id":active_id}</field>
+ </record>
+ <record model="ir.actions.act_window" id="action_purchase_requisition_list">
+ <field name="name">Request for Quotations</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">purchase.order</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="domain">[('requisition_id','=',active_id)]</field>
+ <field name="context">{"default_requisition_id":active_id}</field>
+ </record>
+
<record model="ir.ui.view" id="view_purchase_requisition_form">
<field name="name">purchase.requisition.form</field>
<field name="model">purchase.requisition</field>
<field name="arch" type="xml">
- <form string="Call for Tenders">
- <header>
- <button name="sent_suppliers" states="draft" string="Confirm Call" class="oe_highlight"/>
- <button name="open_bid" states="in_progress" string="Close Call for Tenders" class="oe_highlight"/>
- <button name="tender_reset" states="cancel" string="Reset to Draft" type="object" />
- <button name="open_product_line" states="open" string="Choose product lines" type="object" class="oe_highlight" groups="purchase.group_advance_bidding"/>
- <button name="generate_po" states="open" string="Done" type="object" class="oe_highlight"/>
- <button name="cancel_requisition" states="draft,in_progress" string="Cancel Call"/>
- <field name="state" widget="statusbar" statusbar_visible="draft,in_progress,open,done" statusbar_colors='{"in_progress":"blue"}'/>
+ <form string="Purchase Agreements">
+ <header>
+ <button name="%(action_purchase_requisition_to_so)d" type="action"
+ string="New Quotation"
+ attrs="{'invisible': [('state', '!=', 'open')]}"/>
+ <button name="%(action_purchase_requisition_to_so)d" type="action"
+ string="New Quotation" class="btn-primary"
+ attrs="{'invisible': [('state', '!=', 'in_progress')]}"/>
+ <button name="action_in_progress" states="draft" string="Confirm" type="object" class="btn-primary"/>
+ <button name="action_open" states="in_progress" string="Validate" type="object" class="btn-primary"/>
+ <button name="action_done" states="open" string="Done" type="object" class="btn-primary"/>
+ <button name="action_draft" states="cancel" string="Reset to Draft" type="object"/>
+ <button name="action_cancel" states="draft,in_progress" string="Cancel Call" type="object"/>
+ <field name="state" widget="statusbar" statusbar_visible="draft,in_progress,open,done"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
- <button name="open_product_line" type="object" string="Product Lines" class="oe_stat_button" icon="fa-list"
- attrs="{'invisible': ['|', ('state', 'not in', ('open','done')), ('exclusive', '=', 'exclusive')]}" groups="purchase.group_advance_bidding"/>
- <button name="open_rfq" type="object" string="RFQs/Bids" class="oe_stat_button" icon="fa-list-alt"
- attrs="{'invisible': [('state', '=', 'draft')]}"/>
+ <button name="%(action_purchase_requisition_list)d" type="action" class="oe_stat_button" icon="fa-list-alt"
+ attrs="{'invisible': [('state', '=', 'draft')]}">
+ <field name="order_count" widget="statinfo" string="RFQs/Orders"/>
+ </button>
</div>
<div class="pull-left">
<label for="name" class="oe_edit_only oe_inline"/>
@@ -53,15 +129,15 @@
</div>
<group>
<group>
- <field name="user_id" attrs="{'readonly': [('state','not in',('draft'))]}"/>
- <field name="exclusive" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+ <field name="user_id" attrs="{'readonly': [('state','not in',('draft','in_progress','open'))]}"/>
+ <field name="type_id" attrs="{'readonly': [('state','not in',('draft','in_progress','open'))]}" widget="selection"/>
+ <field name="vendor_id"/>
<field name="account_analytic_id" colspan="2" groups="purchase.group_analytic_accounting" attrs="{'readonly': [('state','not in',('draft'))]}"/>
- <field name="multiple_rfq_per_supplier" attrs="{'readonly': [('state','not in',('draft'))]}"/>
</group>
<group>
- <field name="date_end" attrs="{'readonly': [('state','not in',('draft'))]}"/>
- <field name="ordering_date" attrs="{'readonly': [('state','not in',('draft'))]}"/>
- <field name="schedule_date" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+ <field name="date_end" attrs="{'readonly': [('state','not in',('draft','in_progress','open'))]}"/>
+ <field name="ordering_date" attrs="{'readonly': [('state','not in',('draft','in_progress','open'))]}"/>
+ <field name="schedule_date" attrs="{'readonly': [('state','not in',('draft','in_progress','open'))]}"/>
<field name="origin" placeholder="e.g. PO0025" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="picking_type_id" widget="selection" groups="stock.group_stock_adv_location" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" attrs="{'readonly': [('state','not in',('draft'))]}"/>
@@ -69,19 +145,21 @@
</group>
<notebook>
<page string="Products">
- <field name="line_ids" attrs="{'readonly': [('state','not in',('draft'))]}">
+ <field name="line_ids">
<tree string="Products" editable="bottom">
- <field name="product_id" on_change="onchange_product_id(product_id,product_uom_id,parent.account_analytic_id,account_analytic_id,parent.schedule_date,schedule_date)"/>
+ <field name="product_id" on_change="_onchange_product_id(product_id,product_uom_id,parent.account_analytic_id,account_analytic_id,parent.schedule_date,schedule_date)"/>
<field name="product_qty"/>
+ <field name="qty_ordered"/>
<field name="product_uom_id" groups="product.group_uom"/>
- <field name="schedule_date"/>
+ <field name="schedule_date" groups="base.group_no_one"/>
<field name="account_analytic_id" groups="purchase.group_analytic_accounting"/>
-
+ <field name="price_unit"/>
</tree>
<form string="Products">
<group>
<field name="product_id" />
<field name="product_qty"/>
+ <field name="qty_ordered"/>
<field name="product_uom_id" />
<field name="schedule_date"/>
<field name="account_analytic_id" groups="purchase.group_analytic_accounting"/>
@@ -89,28 +167,8 @@
</group>
</form>
</field>
- <separator string="Requests for Quotation" attrs="{'invisible': [('state', '=', 'draft')]}"/>
- <button name="%(action_purchase_requisition_partner)d" type="action"
- string="Request a Quotation" icon="fa-cogs"
- attrs="{'invisible': ['|', ('line_ids','=',[]),('state', 'in', ('draft' ,'open','done'))]}"/>
- <field name="purchase_ids" options="{'reload_on_button': true}" attrs="{'invisible': [('state', '=', 'draft')]}" readonly="1">
- <tree string="Purchase Order">
- <field name="name" string="Reference"/>
- <field name="date_order" string="Order Date"/>
- <field name="partner_id"/>
- <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
- <field name="dest_address_id" groups="stock.group_stock_multi_locations"/>
- <field name="date_planned" invisible="1"/>
- <field name="origin"/>
- <field name="state"/>
- <button name="button_cancel" states="draft,sent,to approve,purchase" string="Cancel Purchase Order" type="object" icon="fa-times"/>
- <button name="button_confirm" states="draft" string="Confirm Purchase Order" type="object" icon="fa-check"/>
- <button name="button_approve" states="to approve" string="Approved by Vendor" type="object" icon="fa-check-square-o"/>
- <button name="action_rfq_send" states="draft" string="Send RFQ by Email" type="object" context="{'send_rfq':True}" icon="fa-envelope-o"/>
- </tree>
- </field>
<separator string="Terms and Conditions"/>
- <field name="description" attrs="{'readonly': [('state','not in',('draft'))]}"/>
+ <field name="description" attrs="{'readonly': [('state','not in',('draft','in_progress','open'))]}"/>
</page>
</notebook>
</sheet>
@@ -125,12 +183,12 @@
<field name="name">purchase.requisition.tree</field>
<field name="model">purchase.requisition</field>
<field name="arch" type="xml">
- <tree decoration-bf="message_needaction==True" decoration-muted="state == 'cancel'" decoration-danger="date_end and date_end&lt;current_date" string="Call for Tenders">
+ <tree decoration-bf="message_needaction==True" decoration-muted="state == 'cancel'" decoration-danger="date_end and date_end&lt;current_date" string="Purchase Agreements">
<field name="message_needaction" invisible="1"/>
<field name="name"/>
- <field name="ordering_date"/>
<field name="user_id"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
+ <field name="ordering_date"/>
<field name="date_end"/>
<field name="origin"/>
<field name="state"/>
@@ -142,12 +200,12 @@
<field name="name">purchase.requisition.list.select</field>
<field name="model">purchase.requisition</field>
<field name="arch" type="xml">
- <search string="Search Call for Tenders">
- <field name="name" string="Call for Tenders"/>
- <field name="exclusive" />
- <filter name="draft" string="New" domain="[('state','=','draft')]" help="New Call for Tenders"/>
- <filter string="Confirmed" domain="[('state','=','in_progress')]" help="Call for Tenders in negotiation"/>
- <filter string="Closed Tenders" domain="[('state','=','open')]" help="Call for Tenders where tenders are closed"/>
+ <search string="Search Purchase Agreements">
+ <field name="name" string="Purchase Agreements" filter_domain="['|', ('name','ilike',self), ('origin','ilike',self)]"/>
+ <field name="type_id"/>
+ <filter name="draft" string="Draft" domain="[('state','=','draft')]" help="New Agreements"/>
+ <filter string="Confirmed" domain="[('state','in',('in_progress','open'))]" help="In negotiation"/>
+ <filter string="Done" domain="[('state','=','done')]"/>
<separator/>
<filter string="Unassigned" domain="[('user_id','=', False)]" help="Unassigned Requisition"/>
<field name="user_id" />
@@ -164,7 +222,7 @@
<record model="ir.actions.act_window" id="action_purchase_requisition">
- <field name="name">Purchase Tenders</field>
+ <field name="name">Purchase Agreements</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">purchase.requisition</field>
<field name="view_type">form</field>
@@ -173,13 +231,18 @@
<field name="search_view_id" ref="view_purchase_requisition_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
- Click to start a new Call for Tenders process.
+ Click to start a new purchase agreement.
+ </p><p>
+ Example of purchase agreements include call for tenders and blanket orders.
</p><p>
- A Call for Tenders is a procedure for generating competing offers from
- different bidders. In the call for tenders, you can record the
- products you need to buy and generate the creation of RfQs to
- vendors. Once the tenders have been registered, you can review and
- compare them and you can validate some and cancel others.
+ In a call for tenders, you can record the products you need to buy
+ and generate the creation of RfQs to vendors. Once the tenders have
+ been registered, you can review and compare them and you can
+ validate some and cancel others.
+ </p><p>
+ For a blanket order, you can record an agreement for a specifc period
+ (e.g. a year) and you order products within this agreement, benefiting
+ from the negociated prices.
</p>
</field>
</record>
@@ -219,40 +282,5 @@
res_model="purchase.order"
src_model="purchase.requisition"/>
-
- <record id="purchase_order_line_tree_tender" model="ir.ui.view">
- <field name="name">purchase.order.line.tree.tender</field>
- <field name="model">purchase.order.line</field>
- <field eval="1" name="priority"/>
- <field name="arch" type="xml">
- <tree string="Purchase Order Lines" create="false" decoration-info="quantity_tendered&gt;0">
- <field name="name"/>
- <field name="partner_id" string="Vendor"/>
- <field name="product_id"/>
- <field name="price_unit"/>
- <field name="product_qty"/>
- <field name="quantity_tendered"/>
- <field name="product_uom" groups="product.group_uom"/>
- <field name="price_subtotal"/>
- <field name="order_id" invisible="1"/>
- <button name="button_cancel" type="object" string="Cancel Choice" icon="fa-times"
- attrs="{'invisible': [('quantity_tendered', '=', 0)]}"/>
- <button name="%(action_bid_line_qty)d" type="action" string="Change Quantity" icon="fa-plus-square-o"/>
- <button name="button_confirm" type="object" string="Confirm Order" icon="fa-check"
- attrs="{'invisible': [('quantity_tendered', '>', 0)]}"/>
- </tree>
- </field>
- </record>
-
- <record id="purchase_line_tree" model="ir.actions.act_window">
- <field name="name">Tender Lines</field>
- <field name="res_model">purchase.order.line</field>
- <field name="context">{"search_default_groupby_product" : True,}</field>
- <field name="view_type">form</field>
- <field name="view_mode">tree_purchase_order_line_compare</field>
- <field name="view_id" ref="purchase_order_line_tree_tender"/>
- <field name="search_view_id" ref="purchase.purchase_order_line_search"/>
- </record>
-
</data>
</openerp>
diff --git a/addons/purchase_requisition/purchase_requisition_workflow.xml b/addons/purchase_requisition/purchase_requisition_workflow.xml
deleted file mode 100644
index fb2524562c1..00000000000
--- a/addons/purchase_requisition/purchase_requisition_workflow.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data>
-
- <record id="purchase_requisition_workflow" model="workflow">
- <field name="name">Purchase Requisition Basic Workflow</field>
- <field name="osv">purchase.requisition</field>
- <field name="on_create">True</field>
- </record>
-
- <record id="act_draft" model="workflow.activity">
- <field name="wkf_id" ref="purchase_requisition_workflow"/>
- <field name="flow_start">True</field>
- <field name="name">draft</field>
- </record>
- <record id="act_sent" model="workflow.activity">
- <field name="wkf_id" ref="purchase_requisition_workflow"/>
- <field name="name">sent</field>
- <field name="kind">function</field>
- <field name="action">tender_in_progress()</field>
- </record>
- <record id="act_open" model="workflow.activity">
- <field name="wkf_id" ref="purchase_requisition_workflow"/>
- <field name="name">open</field>
- <field name="kind">function</field>
- <field name="action">tender_open()</field>
- </record>
- <record id="act_cancel" model="workflow.activity">
- <field name="wkf_id" ref="purchase_requisition_workflow"/>
- <field name="name">cancel</field>
- <field name="kind">function</field>
- <field name="flow_stop">True</field>
- <field name="action">tender_cancel()</field>
- </record>
- <record id="act_done" model="workflow.activity">
- <field name="wkf_id" ref="purchase_requisition_workflow"/>
- <field name="name">done</field>
- <field name="action">tender_done()</field>
- <field name="kind">function</field>
- <field name="flow_stop">True</field>
- </record>
-
- <record id="trans_draft_sent" model="workflow.transition">
- <field name="act_from" ref="act_draft"/>
- <field name="act_to" ref="act_sent"/>
- <field name="signal">sent_suppliers</field>
- </record>
- <record id="trans_sent_open" model="workflow.transition">
- <field name="act_from" ref="act_sent"/>
- <field name="act_to" ref="act_open"/>
- <field name="signal">open_bid</field>
- </record>
- <record id="trans_draft_cancel" model="workflow.transition">
- <field name="act_from" ref="act_draft"/>
- <field name="act_to" ref="act_cancel"/>
- <field name="signal">cancel_requisition</field>
- </record>
- <record id="trans_open_done" model="workflow.transition">
- <field name="act_from" ref="act_open"/>
- <field name="act_to" ref="act_done"/>
- <field name="signal">done</field>
- </record>
- <record id="trans_sent_cancel" model="workflow.transition">
- <field name="act_from" ref="act_sent"/>
- <field name="act_to" ref="act_cancel"/>
- <field name="signal">cancel_requisition</field>
- </record>
- </data>
-</openerp>
diff --git a/addons/purchase_requisition/security/ir.model.access.csv b/addons/purchase_requisition/security/ir.model.access.csv
index 4c6b8d8cdde..f31808d0139 100644
--- a/addons/purchase_requisition/security/ir.model.access.csv
+++ b/addons/purchase_requisition/security/ir.model.access.csv
@@ -1,4 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_purchase_requisition_type,purchase.requisition.type,model_purchase_requisition_type,purchase.group_purchase_user,1,0,0,0
+access_purchase_requisition_type_manager,purchase.requisition.type,model_purchase_requisition_type,purchase.group_purchase_manager,1,1,1,1
access_purchase_requisition,purchase.requisition,model_purchase_requisition,purchase.group_purchase_user,1,1,1,1
access_purchase_requisition_line_purchase_user,purchase.requisition.line,model_purchase_requisition_line,purchase.group_purchase_user,1,1,1,1
access_purchase_requisition_manager,purchase.requisition manager,model_purchase_requisition,purchase.group_purchase_manager,1,0,0,0
diff --git a/addons/purchase_requisition/static/src/js/web_addons.js b/addons/purchase_requisition/static/src/js/web_addons.js
deleted file mode 100644
index fd74006a850..00000000000
--- a/addons/purchase_requisition/static/src/js/web_addons.js
+++ /dev/null
@@ -1,38 +0,0 @@
-odoo.define('purchase_requisition.purchase_requisition', function (require) {
-"use strict";
-
-var core = require('web.core');
-var Model = require('web.DataModel');
-var ListView = require('web.ListView');
-
-var QWeb = core.qweb;
-var _t = core._t;
-
-
-var CompareListView = ListView.extend({
- render_buttons: function ($node) {
- if (!this.$buttons) {
- this.$buttons = $(QWeb.render("CompareListView.buttons", {'widget': this}));
-
- this.$buttons.find('.oe_generate_po').click(this.proxy('generate_purchase_order'));
-
- $node = $node || this.options.$buttons;
- if ($node) {
- this.$buttons.appendTo($node);
- } else {
- this.$('.oe_list_buttons').replaceWith(this.$buttons);
- }
- }
- },
-
- generate_purchase_order: function () {
- var self = this;
- new Model(self.dataset.model).call("generate_po",[self.dataset.context.tender_id,self.dataset.context]).then(function(result) {
- self.ViewManager.action_manager.history_back();
- });
- },
-});
-
-core.view_registry.add('tree_purchase_order_line_compare', CompareListView);
-
-});
diff --git a/addons/purchase_requisition/static/src/xml/purchase_requisition.xml b/addons/purchase_requisition/static/src/xml/purchase_requisition.xml
deleted file mode 100644
index 0706f2877fd..00000000000
--- a/addons/purchase_requisition/static/src/xml/purchase_requisition.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates>
- <t t-name="CompareListView.buttons">
- <div class="oe_list_buttons">
- <button type="button" class="btn btn-sm btn-primary oe_generate_po">
- Generate PO
- </button>
- </div>
- </t>
-</templates> \ No newline at end of file
diff --git a/addons/purchase_requisition/test/cancel_purchase_requisition.yml b/addons/purchase_requisition/test/cancel_purchase_requisition.yml
index 71111d89e0f..d0ebd43a7ec 100644
--- a/addons/purchase_requisition/test/cancel_purchase_requisition.yml
+++ b/addons/purchase_requisition/test/cancel_purchase_requisition.yml
@@ -7,7 +7,7 @@
I cancel requisition.
-
!python {model: purchase.requisition}: |
- self.tender_cancel(cr, uid, [ref("requisition1")])
+ self.action_cancel(cr, uid, [ref("requisition1")])
-
I check requisition after cancelled.
-
@@ -17,7 +17,7 @@
I reset requisition as "New".
-
!python {model: purchase.requisition}: |
- self.tender_reset(cr, uid, [ref('requisition1')])
+ self.action_draft(cr, uid, [ref('requisition1')])
-
I duplicate requisition.
-
diff --git a/addons/purchase_requisition/test/purchase_requisition.yml b/addons/purchase_requisition/test/purchase_requisition.yml
index ad8b5914a42..64accb7de5d 100644
--- a/addons/purchase_requisition/test/purchase_requisition.yml
+++ b/addons/purchase_requisition/test/purchase_requisition.yml
@@ -31,60 +31,3 @@
assert line.product_id.id == procurement.product_id.id, "Product is not correspond."
assert line.product_uom_id.id == procurement.product_uom.id, "UOM is not correspond."
assert float_compare(line.product_qty, procurement.product_qty, precision_digits=2) == 0, "Quantity is not correspond."
--
- I send the purchase order associated to the requisition.
--
- !python {model: purchase.requisition}: |
- po = self.pool.get('purchase.order')
- for element in self.browse(cr, uid, [ref("requisition1")], context=context):
- for purchase in element.purchase_ids:
- po.write(cr, uid, purchase.id, {'state': 'sent'}, context=context)
-
--
- Give access rights of Purchase Requisition User to open requisition
--
- !context
- uid: 'res_users_purchase_requisition_user'
--
- I open another requisition and set tender state to choose tendering line.
--
- !python {model: purchase.requisition}: |
- self.tender_in_progress(cr, uid, [ref("requisition1")], context=context)
- self.tender_open(cr, uid, [ref("requisition1")], context=context)
--
- Vendor send one RFQ so I create requisition request of that supplier.
--
- !python {model: purchase.requisition.partner}: |
- context.update({"active_model": "purchase.requisition","active_ids": [ref("requisition1")],"active_id": ref("requisition1")})
--
- !record {model: purchase.requisition.partner, id: requisition_partner_0}:
- partner_ids: [base.res_partner_12]
--
- !python {model: purchase.requisition.partner}: |
- context.update({"mail_create_nolog" : True })
- self.create_order(cr, uid, [ref("requisition_partner_0")], context=context)
--
- I check that the RFQ details which created for supplier.
--
- !python {model: purchase.order}: |
- purchase_requisition = self.pool.get("purchase.requisition")
- purchase_ids = self.search(cr, uid, [('requisition_id','=',ref("requisition1"))])
- assert purchase_ids, "RFQ is not created."
- for rfq in self.browse(cr, uid, purchase_ids, context=context):
- if rfq.partner_id.id == ref('base.res_partner_12'):
- break
- else:
- assert False, "No PO found for res_partner_12."
--
- I confirmed RFQ which has best price.
--
- !python {model: purchase.order}: |
- purchase = self.browse(cr, uid, ref('rfq2'), context=context)
- purchase.signal_workflow('purchase_confirm')
-
--
- I check status of requisition after confirmed best RFQ.
--
- !python {model: purchase.requisition}: |
- requisition = self.browse(cr, uid, ref('requisition1'), context=context)
- requisition.state == 'done', "Requisition should be closed."
diff --git a/addons/purchase_requisition/test/purchase_requisition_demo.yml b/addons/purchase_requisition/test/purchase_requisition_demo.yml
index 8265e6c72da..e7526895ba6 100644
--- a/addons/purchase_requisition/test/purchase_requisition_demo.yml
+++ b/addons/purchase_requisition/test/purchase_requisition_demo.yml
@@ -5,11 +5,30 @@
uid: 'res_users_purchase_requisition_user'
-
In order to test process of the purchase requisition ,I create requisition
--
+-
!record {model: purchase.requisition, id: requisition1}:
- exclusive: exclusive
line_ids:
- product_id: product.product_product_9
product_qty: 10.0
product_uom_id: product.product_uom_unit
-
+-
+ I create another requisition and set tender state to choose tendering line.
+-
+ !record {model: purchase.requisition, id: requisition2}:
+ line_ids:
+ - product_id: product.product_product_9
+ product_qty: 10.0
+ product_uom_id: product.product_uom_unit
+-
+ !python {model: purchase.requisition}: |
+ self.action_in_progress(cr, uid, [ref("requisition2")], context=context)
+ self.action_open(cr, uid, [ref("requisition2")], context=context)
+-
+ Vendor send one RFQ so I create a RfQ of that agreement.
+-
+ !record {model: purchase.order, id: po_requisition}:
+ partner_id: base.res_partner_1
+ requisition_id: purchase_requisition.requisition2
+-
+ !python {model: purchase.order, id: po_requisition}: |
+ assert len(self.order_line)==1, 'Purchase order should have one line'
diff --git a/addons/purchase_requisition/views/purchase_requisition.xml b/addons/purchase_requisition/views/purchase_requisition.xml
deleted file mode 100644
index c3619497f89..00000000000
--- a/addons/purchase_requisition/views/purchase_requisition.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data>
- <template id="assets_backend" name="purchase requisition assets" inherit_id="web.assets_backend">
- <xpath expr="." position="inside">
- <script type="text/javascript" src="/purchase_requisition/static/src/js/web_addons.js"></script>
- </xpath>
- </template>
- </data>
-</openerp> \ No newline at end of file
diff --git a/addons/purchase_requisition/views/report_purchaserequisition.xml b/addons/purchase_requisition/views/report_purchaserequisition.xml
index 940079b0cdc..1250b305a0e 100644
--- a/addons/purchase_requisition/views/report_purchaserequisition.xml
+++ b/addons/purchase_requisition/views/report_purchaserequisition.xml
@@ -21,8 +21,7 @@
</div>
<div class="col-xs-3">
<strong>Selection Type:</strong><br/>
- <span t-if="o.exclusive=='exclusive'">Purchase Requisitions (exclusive)</span>
- <span t-if="o.exclusive=='multiple'">Multiple Requisitions</span>
+ <span t-esc="o.type_id.name">Multiple Requisitions</span>
</div>
<div class="col-xs-3">
<strong>Source:</strong><br/>
diff --git a/addons/purchase_requisition/wizard/__init__.py b/addons/purchase_requisition/wizard/__init__.py
deleted file mode 100644
index 533c9061d0b..00000000000
--- a/addons/purchase_requisition/wizard/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import purchase_requisition_partner
-import bid_line_qty
diff --git a/addons/purchase_requisition/wizard/bid_line_qty.py b/addons/purchase_requisition/wizard/bid_line_qty.py
deleted file mode 100644
index a564c2ab3df..00000000000
--- a/addons/purchase_requisition/wizard/bid_line_qty.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from openerp.osv import fields, osv
-import openerp.addons.decimal_precision as dp
-
-class bid_line_qty(osv.osv_memory):
- _name = "bid.line.qty"
- _description = "Change Bid line quantity"
- _columns = {
- 'qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
- }
-
- def change_qty(self, cr, uid, ids, context=None):
- active_ids = context and context.get('active_ids', [])
- data = self.browse(cr, uid, ids, context=context)[0]
- self.pool.get('purchase.order.line').write(cr, uid, active_ids, {'quantity_tendered': data.qty})
- return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/purchase_requisition/wizard/bid_line_qty_view.xml b/addons/purchase_requisition/wizard/bid_line_qty_view.xml
deleted file mode 100644
index 160b44964e1..00000000000
--- a/addons/purchase_requisition/wizard/bid_line_qty_view.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<openerp>
- <data>
-
- <record id="view_bid_line_qty" model="ir.ui.view">
- <field name="name">Quantity to Purchase</field>
- <field name="model">bid.line.qty</field>
- <field name="arch" type="xml">
- <form string="Quantity">
- <group>
- <field name="qty" context="{'default_qty': 0}"/>
- </group>
- <footer>
- <button name="change_qty" string="Change Quantity" type="object" class="btn-primary"/>
- <button string="Cancel" class="btn-default" special="cancel" />
- </footer>
- </form>
- </field>
- </record>
-
- <record id="action_bid_line_qty" model="ir.actions.act_window">
- <field name="name">Bid Line Qty</field>
- <field name="type">ir.actions.act_window</field>
- <field name="res_model">bid.line.qty</field>
- <field name="view_type">form</field>
- <field name="view_mode">form</field>
- <field name="context">{'record_id' : active_id}</field>
- <field name="target">new</field>
- </record>
-</data>
-</openerp>
diff --git a/addons/purchase_requisition/wizard/purchase_requisition_partner.py b/addons/purchase_requisition/wizard/purchase_requisition_partner.py
deleted file mode 100644
index 2fabb1fe31a..00000000000
--- a/addons/purchase_requisition/wizard/purchase_requisition_partner.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from openerp.osv import fields, osv
-from openerp.tools.translate import _
-from openerp.exceptions import UserError
-
-class purchase_requisition_partner(osv.osv_memory):
- _name = "purchase.requisition.partner"
- _description = "Purchase Requisition Partner"
- _columns = {
- 'partner_ids': fields.many2many('res.partner', 'purchase_requisition_supplier_rel', 'requisition_id', 'partner_id', string='Vendors', required=True, domain=[('supplier', '=', True)])
- }
-
- def view_init(self, cr, uid, fields_list, context=None):
- if context is None:
- context = {}
- res = super(purchase_requisition_partner, self).view_init(cr, uid, fields_list, context=context)
- record_id = context and context.get('active_id', False) or False
- tender = self.pool.get('purchase.requisition').browse(cr, uid, record_id, context=context)
- if not tender.line_ids:
- raise UserError(_('Define product(s) you want to include in the call for tenders.'))
- return res
-
- def create_order(self, cr, uid, ids, context=None):
- active_ids = context and context.get('active_ids', [])
- purchase_requisition = self.pool.get('purchase.requisition')
- for wizard in self.browse(cr, uid, ids, context=context):
- for partner_id in wizard.partner_ids:
- purchase_requisition.make_purchase_order(cr, uid, active_ids, partner_id.id, context=context)
- return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/purchase_requisition/wizard/purchase_requisition_partner_view.xml b/addons/purchase_requisition/wizard/purchase_requisition_partner_view.xml
deleted file mode 100644
index a01a5f7d15d..00000000000
--- a/addons/purchase_requisition/wizard/purchase_requisition_partner_view.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<openerp>
- <data>
-
- <record id="view_purchase_requisition_partner" model="ir.ui.view">
- <field name="name">Choose Vendor</field>
- <field name="model">purchase.requisition.partner</field>
- <field name="arch" type="xml">
- <form string="Choose Vendor">
- <group>
- <field name="partner_ids" widget="many2many_tags" context="{'default_supplier': 1, 'default_customer': 0}"/>
- </group>
- <footer>
- <button name="create_order" string="Create Request for Quotation" type="object" class="btn-primary"/>
- <button string="Cancel" class="btn-default" special="cancel" />
- </footer>
- </form>
- </field>
- </record>
-
- <record id="action_purchase_requisition_partner" model="ir.actions.act_window">
- <field name="name">Choose Vendor</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="context">{'record_id' : active_id}</field>
- <field name="target">new</field>
- </record>
-
-</data>
-</openerp>