[REM] purchase_requisition{_stock}: rem old call to tender pt2

This commit removes the call for tender feature via a
purchase.requisition. This feature is to be replaced with the ability to
directly compare prices/options of POs/RFQs within a PO to remove extra
steps to compare them. The linkage between POs previously provided by a
purchase.requistion is replaced by the POs being directly linked to each
other. Feature to auto-create call to tenders via a product option is
removed and the user is expected to know/be responsible for when they
should do a call to tender themselves.

All references to old Call to Tenders replaced with Blanket Order, and
we remove/rename the menu items since Blanket Order is now the only
purchase.requisition.type option (we expect minimal customizated types).
Follow-on refactoring to switch purchase.requisition to
purchase.blanket.order to come later. Follow-on refactoring to switch
purchase.requisition to purchase.blanket.order to come later.

Part of Task: 2695116
Upgrade PR: odoo/upgrade#3586

Part-of: odoo/odoo#87656
This commit is contained in:
Tiffany Chang (tic)
2022-06-20 12:15:58 +02:00
parent eabf047055
commit 00689e7fc6
16 changed files with 43 additions and 195 deletions
@@ -56,7 +56,7 @@
</div>
<div class="col-12 col-lg-6 o_setting_box"
id="manage_purchase_agreements"
title="Calls for tenders are used when you want to generate requests for quotations to several vendors for a given set of products. 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.">
title="Calls for tenders are when you want to generate requests for quotations with several vendors for a given set of products to compare offers.">
<div class="o_setting_left_pane">
<field name="module_purchase_requisition"/>
</div>
@@ -20,7 +20,6 @@ are agreements you have with vendors to benefit from a predetermined pricing.
'views/product_views.xml',
'views/purchase_views.xml',
'views/purchase_requisition_views.xml',
'views/res_config_settings_views.xml',
'report/purchase_requisition_report.xml',
'report/report_purchaserequisition.xml',
],
@@ -6,20 +6,6 @@
<field name="sequence">3</field>
<field name="quantity_copy">none</field>
</record>
<record id="type_multi" model="purchase.requisition.type">
<field name="name">Call for Tender</field>
<field name="sequence">1</field>
<field name="quantity_copy">copy</field>
</record>
<record id="seq_purchase_tender" model="ir.sequence">
<field name="name">Call for Tender</field>
<field name="code">purchase.requisition.purchase.tender</field>
<field name="prefix">TE</field>
<field name="padding">5</field>
<field name="company_id" eval="False"></field>
</record>
<record id="seq_blanket_order" model="ir.sequence">
<field name="name">Blanket Order</field>
<field name="code">purchase.requisition.blanket.order</field>
@@ -1,35 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="base.user_demo" model="res.users">
<field eval="[(4, ref('purchase.group_purchase_user'))]" name="groups_id"/>
</record>
<record id="product.product_product_13" model="product.product">
<field name="purchase_requisition">tenders</field>
</record>
<!--Resource: purchase.requisition-->
<record id="requisition1" model="purchase.requisition">
<!--Blanket Order Agreement-->
<record id="bo_requisition" model="purchase.requisition">
<field name="user_id" ref="base.user_admin"/>
<field name="vendor_id" ref="base.res_partner_1"/>
<field name="type_id" ref="type_single"/>
</record>
<record id="requisition_line1" model="purchase.requisition.line">
<field name="requisition_id" ref="requisition1"/>
<record id="bo_requisition_line" model="purchase.requisition.line">
<field name="requisition_id" ref="bo_requisition"/>
<field name="product_id" ref="product.product_product_13"/>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field name="product_qty">5</field>
<field name="product_qty">100</field>
<field name="price_unit">60</field>
</record>
<function model="purchase.requisition" name="action_in_progress" eval="[[ref('requisition1')]]"/>
<function model="purchase.requisition" name="action_in_progress" eval="[[ref('bo_requisition')]]"/>
<!--Resource: purchase.order-->
<record id="rfq1" model="purchase.order">
<field name="partner_id" ref="base.res_partner_4"/>
<field name="partner_id" ref="base.res_partner_1"/>
<field name="user_id" ref="base.user_admin"/>
<field name="requisition_id" ref="requisition1"/>
<field name="requisition_id" ref="bo_requisition"/>
</record>
<record id="rfq1_line" model="purchase.order.line">
@@ -39,13 +35,15 @@
<field name="product_id" ref="product.product_product_13"/>
<field name="product_uom" ref="uom.product_uom_unit"/>
<field name="price_unit">60</field>
<field name="product_qty">5</field>
<field name="product_qty">25</field>
</record>
<function model="purchase.order" name="button_confirm" eval="[[ref('rfq1')]]"/>
<record id="rfq2" model="purchase.order">
<field name="partner_id" ref="base.res_partner_1"/>
<field name="user_id" ref="base.user_admin"/>
<field name="requisition_id" ref="requisition1"/>
<field name="requisition_id" ref="bo_requisition"/>
</record>
<record id="rfq2_line" model="purchase.order.line">
@@ -54,10 +52,8 @@
<field name="date_planned" eval="time.strftime('%Y-%m-15')"/>
<field name="product_id" ref="product.product_product_13"/>
<field name="product_uom" ref="uom.product_uom_unit"/>
<field name="price_unit">50</field>
<field name="product_qty">3</field>
</record>
<field name="price_unit">60</field>
<field name="product_qty">10</field>
</record>
</data>
</odoo>
@@ -20,15 +20,3 @@ class ProductProduct(models.Model):
return sellers.filtered(lambda s: not s.purchase_requisition_id or s.purchase_requisition_id == params['order_id'].requisition_id)
else:
return sellers
class ProductTemplate(models.Model):
_inherit = 'product.template'
purchase_requisition = fields.Selection(
[('rfq', 'Create a draft purchase order'),
('tenders', 'Propose a call for tenders')],
string='Procurement', default='rfq',
help="Create a draft purchase order: Based on your product configuration, the system will create a draft "
"purchase order.Propose a call for tender : If the 'purchase_requisition' module is installed and this option "
"is selected, the system will create a draft call for tender.")
@@ -129,10 +129,7 @@ class PurchaseRequisition(models.Model):
self.write({'state': 'in_progress'})
# Set the sequence number regarding the requisition type
if self.name == 'New':
if self.is_quantity_copy != 'none':
self.name = self.env['ir.sequence'].next_by_code('purchase.requisition.purchase.tender')
else:
self.name = self.env['ir.sequence'].next_by_code('purchase.requisition.blanket.order')
self.name = self.env['ir.sequence'].next_by_code('purchase.requisition.blanket.order')
def action_open(self):
self.write({'state': 'open'})
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_report_purchase_requisitions" model="ir.actions.report">
<field name="name">Call for Tenders</field>
<field name="name">Purchase Agreements</field>
<field name="model">purchase.requisition</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">purchase_requisition.report_purchaserequisitions</field>
<field name="report_file">purchase_requisition.report.report_purchaserequisitions</field>
<field name="print_report_name">'Tender - %s' % (object.name)</field>
<field name="print_report_name">'Purchase Agreement - %s' % (object.name)</field>
<field name="binding_model_id" ref="model_purchase_requisition"/>
<field name="binding_type">report</field>
</record>
+2 -3
View File
@@ -53,16 +53,15 @@ class TestPurchaseRequisitionCommon(common.TransactionCase):
'uom_id': cls.product_uom_id.id,
'uom_po_id': cls.product_uom_id.id,
'default_code': 'FURN_1118',
'purchase_requisition': 'tenders',
})
# In order to test process of the purchase requisition ,create requisition
cls.requisition1 = cls.env['purchase.requisition'].create({
cls.bo_requisition = cls.env['purchase.requisition'].create({
'line_ids': [(0, 0, {
'product_id': cls.product_09.id,
'product_qty': 10.0,
'product_uom_id': cls.product_uom_id.id})]
})
})
cls.res_partner_1 = cls.env['res.partner'].create({
'name': 'Wood Corner',
@@ -11,14 +11,13 @@ class TestPurchaseRequisition(TestPurchaseRequisitionCommon):
self.assertTrue(self.user_purchase_requisition_user, 'User Should be created')
def test_01_cancel_purchase_requisition(self):
self.requisition1.with_user(self.user_purchase_requisition_user).action_cancel()
self.bo_requisition.with_user(self.user_purchase_requisition_user).action_cancel()
# Check requisition after cancelled.
self.assertEqual(self.requisition1.state, 'cancel', 'Requisition should be in cancelled state.')
self.assertEqual(self.bo_requisition.state, 'cancel', 'Requisition should be in cancelled state.')
# I reset requisition as "New".
self.requisition1.with_user(self.user_purchase_requisition_user).action_draft()
self.bo_requisition.with_user(self.user_purchase_requisition_user).action_draft()
# I duplicate requisition.
self.requisition1.with_user(self.user_purchase_requisition_user).copy()
self.bo_requisition.with_user(self.user_purchase_requisition_user).copy()
def test_02_purchase_requisition(self):
price_product09 = 34
@@ -42,7 +41,7 @@ class TestPurchaseRequisition(TestPurchaseRequisitionCommon):
# confirm the requisition
requisition_blanket.action_in_progress()
# Check for both product that the new supplier info(purchase.requisition.vendor_id) is added to the puchase tab
# Check for both product that the new supplier info(purchase.requisition.vendor_id) is added to the purchase tab
# and check the quantity
seller_partner1 = self.res_partner_1
supplierinfo09 = self.env['product.supplierinfo'].search([
@@ -50,16 +49,16 @@ class TestPurchaseRequisition(TestPurchaseRequisitionCommon):
('product_id', '=', self.product_09.id),
('purchase_requisition_id', '=', requisition_blanket.id),
])
self.assertEqual(supplierinfo09.partner_id, seller_partner1, 'The supplierinfo is not the good one')
self.assertEqual(supplierinfo09.price, price_product09, 'The supplierinfo is not the good one')
self.assertEqual(supplierinfo09.partner_id, seller_partner1, 'The supplierinfo is not correct')
self.assertEqual(supplierinfo09.price, price_product09, 'The supplierinfo is not correct')
supplierinfo13 = self.env['product.supplierinfo'].search([
('partner_id', '=', seller_partner1.id),
('product_id', '=', self.product_13.id),
('purchase_requisition_id', '=', requisition_blanket.id),
])
self.assertEqual(supplierinfo13.partner_id, seller_partner1, 'The supplierinfo is not the good one')
self.assertEqual(supplierinfo13.price, price_product13, 'The supplierinfo is not the good one')
self.assertEqual(supplierinfo13.partner_id, seller_partner1, 'The supplierinfo is not correct')
self.assertEqual(supplierinfo13.price, price_product13, 'The supplierinfo is not correct')
# Put the requisition in done Status
requisition_blanket.action_in_progress()
@@ -69,7 +68,7 @@ class TestPurchaseRequisition(TestPurchaseRequisitionCommon):
self.assertFalse(self.env['product.supplierinfo'].search([('id', '=', supplierinfo13.id)]), 'The supplier info should be removed')
def test_06_purchase_requisition(self):
""" Create a blanquet order for a product and a vendor already linked via
""" Create a blanket order for a product and a vendor already linked via
a supplier info"""
product = self.env['product.product'].create({
'name': 'test6',
@@ -85,7 +84,7 @@ class TestPurchaseRequisition(TestPurchaseRequisitionCommon):
'partner_id': vendor.id,
})
# create a empty blanquet order
# create an empty blanket order
requisition_type = self.env['purchase.requisition.type'].create({
'name': 'Blanket test',
'quantity_copy': 'none'
@@ -24,18 +24,4 @@
</field>
</field>
</record>
<record id="product_template_form_view_inherit" model="ir.ui.view">
<field name="name">product.template.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="purchase.view_product_supplier_inherit"/>
<field name="arch" type="xml">
<group name="bill" position="before">
<group string="Reordering" attrs="{'invisible': [('type', '=', 'service')]}">
<field name="purchase_requisition" widget="radio"/>
</group>
</group>
</field>
</record>
</odoo>
@@ -71,17 +71,6 @@
</search>
</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_mode">tree,kanban,form</field>
</record>
<menuitem
id="menu_purchase_requisition_type"
sequence="2"
parent="purchase.menu_purchase_config"
action="tender_type_action"/>
<!-- Purchase Orders -->
@@ -163,7 +152,7 @@
<page string="Products" name="products">
<field name="line_ids">
<tree string="Products" editable="bottom">
<field name="product_id" context="{'default_purchase_requisition': 'tenders'}"
<field name="product_id"
domain="[('purchase_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"/>
<field name="product_description_variants" attrs="{'invisible': [('product_description_variants', '=', '')], 'readonly': [('parent.state', '!=', 'draft')]}"/>
<field name="product_qty"/>
@@ -177,7 +166,7 @@
</tree>
<form string="Products">
<group>
<field name="product_id" context="{'default_purchase_requisition': 'tenders'}"
<field name="product_id"
domain="[('purchase_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]" />
<field name="product_qty"/>
<field name="qty_ordered"/>
@@ -289,7 +278,7 @@
<record model="ir.actions.act_window" id="action_purchase_requisition">
<field name="name">Purchase Agreements</field>
<field name="name">Blanket Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">purchase.requisition</field>
<field name="view_mode">tree,kanban,form</field>
@@ -299,15 +288,10 @@
<p class="o_view_nocontent_smiling_face">
Start a new purchase agreement
</p><p>
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.
An example of a purchase agreement is a blanket order.
</p><p>
For a blanket order, you can record an agreement for a specific period
(e.g. a year) and you order products within this agreement, benefiting
(e.g. a year) and you order products within this agreement to benefit
from the negotiated prices.
</p>
</field>
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.purchase.requisition</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="purchase.res_config_settings_view_form_purchase"/>
<field name="arch" type="xml">
<div id="use_purchase_requisition" position="replace">
<div class="mt8">
<button name="%(purchase_requisition.tender_type_action)d" icon="fa-arrow-right" type="action" string="Agreement Types" class="btn-link"/>
</div>
</div>
</field>
</record>
</odoo>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="purchase_requisition.requisition1" model="purchase.requisition">
<record id="purchase_requisition.bo_requisition" model="purchase.requisition">
<field name="warehouse_id" ref="stock.stock_warehouse_shop0"/>
</record>
</data>
@@ -14,24 +14,6 @@ class PurchaseRequisition(models.Model):
picking_type_id = fields.Many2one(
'stock.picking.type', 'Operation Type', required=True, default=_default_picking_type_id,
domain="['|',('warehouse_id', '=', False), ('warehouse_id.company_id', '=', company_id)]")
procurement_group_id = fields.Many2one('procurement.group', 'Procurement Group')
def _prepare_tender_values(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values):
return {
'origin': origin,
'date_end': values['date_planned'],
'user_id': False,
'warehouse_id': values.get('warehouse_id') and values['warehouse_id'].id or False,
'procurement_group_id': values.get('group_id') and values['group_id'].id or False,
'company_id': company_id.id,
'line_ids': [(0, 0, {
'product_id': product_id.id,
'product_uom_id': product_uom.id,
'product_qty': product_qty,
'product_description_variants': values.get('product_description_variants'),
'move_dest_id': values.get('move_dest_ids') and values['move_dest_ids'][0].id or False
})],
}
class PurchaseRequisitionLine(models.Model):
@@ -1,29 +1,12 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo import api, fields, models
from odoo import fields, models
class StockRule(models.Model):
_inherit = 'stock.rule'
@api.model
def _run_buy(self, procurements):
requisitions_values_by_company = defaultdict(list)
other_procurements = []
for procurement, rule in procurements:
if procurement.product_id.purchase_requisition == 'tenders':
values = self.env['purchase.requisition']._prepare_tender_values(*procurement)
values['picking_type_id'] = rule.picking_type_id.id
requisitions_values_by_company[procurement.company_id.id].append(values)
else:
other_procurements.append((procurement, rule))
for company_id, requisitions_values in requisitions_values_by_company.items():
self.env['purchase.requisition'].sudo().with_company(company_id).create(requisitions_values)
return super(StockRule, self)._run_buy(other_procurements)
def _prepare_purchase_order(self, company_id, origins, values):
res = super(StockRule, self)._prepare_purchase_order(company_id, origins, values)
values = values[0]
@@ -9,42 +9,6 @@ from odoo.addons.purchase_requisition.tests.common import TestPurchaseRequisitio
class TestPurchaseRequisitionStock(TestPurchaseRequisitionCommon):
def test_01_purchase_requisition_stock(self):
date_planned = fields.Datetime.now()
warehouse = self.env['stock.warehouse'].browse(self.ref('stock.warehouse0'))
self.env['procurement.group'].run([self.env['procurement.group'].Procurement(
self.product_13,
14,
self.env['uom.uom'].browse(self.ref('uom.product_uom_unit')),
warehouse.lot_stock_id,
'/',
'/',
self.env.company,
{
'warehouse_id': warehouse,
'date_planned': date_planned,
}
)])
# Check requisition details which created after run procurement.
line = self.env['purchase.requisition.line'].search([('product_id', '=', self.product_13.id), ('product_qty', '=', 14.0)])
requisition = line[0].requisition_id
self.assertEqual(requisition.date_end, date_planned, "End date does not correspond.")
self.assertEqual(len(requisition.line_ids), 1, "Requisition Lines should be one.")
self.assertEqual(line.product_uom_id.id, self.ref('uom.product_uom_unit'), "UOM is not correspond.")
# Give access rights of Purchase Requisition User to open requisition
# Set tender state to choose tendering line.
self.requisition1.with_user(self.user_purchase_requisition_user).action_in_progress()
self.requisition1.with_user(self.user_purchase_requisition_user).action_open()
# Vendor send one RFQ so I create a RfQ of that agreement.
PurchaseOrder = self.env['purchase.order']
purchase_order = PurchaseOrder.new({'partner_id': self.res_partner_1, 'requisition_id': self.requisition1.id})
purchase_order._onchange_requisition_id()
po_dict = purchase_order._convert_to_write({name: purchase_order[name] for name in purchase_order._cache})
self.po_requisition = PurchaseOrder.create(po_dict)
self.assertEqual(len(self.po_requisition.order_line), 1, 'Purchase order should have one line')
def test_02_purchase_requisition_stock(self):
"""Plays with the sequence of regular supplier infos and one created by blanket orders."""
# Product creation