[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
This commit is contained in:
Fabien Pinckaers
2016-04-29 10:43:49 +02:00
committed by Nicolas Martinelli
parent 3938fb27e5
commit ff110b0ed8
24 changed files with 351 additions and 731 deletions
-6
View File
@@ -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')
-2
View File
@@ -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>
@@ -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"/>
-1
View File
@@ -1,4 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import purchase_requisition
import wizard
+7 -13
View File
@@ -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,
}
@@ -3,322 +3,153 @@
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 action_draft(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
def tender_done(self, cr, uid, ids, context=None):
def action_done(self, cr, uid, ids, context=None):
"""
Generate all purchase order based on selected lines, should only be called on one agreement at a time
"""
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)
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
"""
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
"""
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
class purchase_requisition_line(osv.osv):
_name = "purchase.requisition.line"
_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 _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,
}
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
return {'value': value}
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})
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 purchase_order_line(osv.osv):
_inherit = 'purchase.order.line'
_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)
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
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',
}
@@ -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>
</openerp>
@@ -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>
@@ -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>
@@ -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>
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.
Example of purchase agreements include call for tenders and blanket orders.
</p><p>
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>
@@ -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>
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_purchase_requisition_type purchase.requisition.type model_purchase_requisition_type purchase.group_purchase_user 1 0 0 0
3 access_purchase_requisition_type_manager purchase.requisition.type model_purchase_requisition_type purchase.group_purchase_manager 1 1 1 1
4 access_purchase_requisition purchase.requisition model_purchase_requisition purchase.group_purchase_user 1 1 1 1
5 access_purchase_requisition_line_purchase_user purchase.requisition.line model_purchase_requisition_line purchase.group_purchase_user 1 1 1 1
6 access_purchase_requisition_manager purchase.requisition manager model_purchase_requisition purchase.group_purchase_manager 1 0 0 0
@@ -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);
});
@@ -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>
@@ -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.
-
@@ -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."
@@ -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'
@@ -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>
@@ -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/>
@@ -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
@@ -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'}
@@ -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>
@@ -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'}
@@ -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>