diff options
| author | Haresh Shyara <hsh@odoo.com> | 2018-11-27 10:53:48 +0000 |
|---|---|---|
| committer | Haresh Shyara <hsh@odoo.com> | 2019-01-17 13:00:21 +0000 |
| commit | 826f927a1ed54f6e2468e8f3b8649c1b7cfdf04b (patch) | |
| tree | 4b8c8ad62560a6853df0fa4df3218aa5c6fb2ffc /addons | |
| parent | 22efdfbecc1e8c688000be98903a5a355d1ece8a (diff) | |
[MOV] purchase_requisition: make requisition services friendly
The purpose of this commit is to make requisition usable with only services. Stock part is extracted in a new module `purchase_requisition_stock` which allows to create requisition based on product availbility or demand, also
we have moved all security access related stock in this bridge module.
Task ID : 1851286
Diffstat (limited to 'addons')
24 files changed, 367 insertions, 258 deletions
diff --git a/addons/purchase_requisition/__manifest__.py b/addons/purchase_requisition/__manifest__.py index 1aedefb2675..5f6e3a4e801 100644 --- a/addons/purchase_requisition/__manifest__.py +++ b/addons/purchase_requisition/__manifest__.py @@ -11,7 +11,7 @@ Manage calls for tenders and blanket orders. Calls for tenders are used to get competing offers from different vendors and select the best ones. Blanket orders are agreements you have with vendors to benefit from a predetermined pricing. """, - 'depends': ['purchase_stock'], + 'depends': ['purchase'], 'demo': ['data/purchase_requisition_demo.xml'], 'data': [ 'security/purchase_requisition_security.xml', diff --git a/addons/purchase_requisition/data/purchase_requisition_data.xml b/addons/purchase_requisition/data/purchase_requisition_data.xml index 84194b8d89b..ceafd213f6e 100644 --- a/addons/purchase_requisition/data/purchase_requisition_data.xml +++ b/addons/purchase_requisition/data/purchase_requisition_data.xml @@ -1,11 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <odoo> <data noupdate="1"> - - <function - model="ir.default" name="set" - eval="('purchase.requisition', 'warehouse_id', ref('stock.warehouse0'))"/> - <record id="type_single" model="purchase.requisition.type"> <field name="name">Blanket Order</field> <field name="sequence">3</field> diff --git a/addons/purchase_requisition/data/purchase_requisition_demo.xml b/addons/purchase_requisition/data/purchase_requisition_demo.xml index e74e7a9926a..a57a55823eb 100644 --- a/addons/purchase_requisition/data/purchase_requisition_demo.xml +++ b/addons/purchase_requisition/data/purchase_requisition_demo.xml @@ -14,7 +14,6 @@ <record id="requisition1" model="purchase.requisition"> <field name="user_id" ref="base.user_admin"/> - <field name="warehouse_id" ref="stock.stock_warehouse_shop0"/> </record> <record id="requisition_line1" model="purchase.requisition.line"> diff --git a/addons/purchase_requisition/models/__init__.py b/addons/purchase_requisition/models/__init__.py index a45382e1957..a12e95fe7b5 100644 --- a/addons/purchase_requisition/models/__init__.py +++ b/addons/purchase_requisition/models/__init__.py @@ -3,4 +3,3 @@ from . import purchase from . import product from . import purchase_requisition -from . import stock diff --git a/addons/purchase_requisition/models/purchase.py b/addons/purchase_requisition/models/purchase.py index 7e4d5359e2c..5379a3bf29c 100644 --- a/addons/purchase_requisition/models/purchase.py +++ b/addons/purchase_requisition/models/purchase.py @@ -39,7 +39,6 @@ class PurchaseOrder(models.Model): self.origin = requisition.name self.notes = requisition.description self.date_order = requisition.date_end or fields.Datetime.now() - self.picking_type_id = requisition.picking_type_id.id if requisition.type_id.line_copy != 'copy': return diff --git a/addons/purchase_requisition/models/purchase_requisition.py b/addons/purchase_requisition/models/purchase_requisition.py index 33b3f3c1a62..d5ccac07f4d 100644 --- a/addons/purchase_requisition/models/purchase_requisition.py +++ b/addons/purchase_requisition/models/purchase_requisition.py @@ -43,16 +43,6 @@ class PurchaseRequisition(models.Model): _inherit = ['mail.thread', 'mail.activity.mixin'] _order = "id desc" - def _get_picking_in(self): - pick_in = self.env.ref('stock.picking_type_in', raise_if_not_found=False) - company = self.env['res.company']._company_default_get('purchase.requisition') - if not pick_in or pick_in.sudo().warehouse_id.company_id.id != company.id: - pick_in = self.env['stock.picking.type'].search( - [('warehouse_id.company_id', '=', company.id), ('code', '=', 'incoming')], - limit=1, - ) - return pick_in - def _get_type_id(self): return self.env['purchase.requisition.type'].search([], limit=1) @@ -69,12 +59,10 @@ class PurchaseRequisition(models.Model): company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env['res.company']._company_default_get('purchase.requisition')) purchase_ids = fields.One2many('purchase.order', 'requisition_id', string='Purchase Orders', states={'done': [('readonly', True)]}) line_ids = fields.One2many('purchase.requisition.line', 'requisition_id', string='Products to Purchase', states={'done': [('readonly', True)]}, copy=True) - warehouse_id = fields.Many2one('stock.warehouse', string='Warehouse') state = fields.Selection(PURCHASE_REQUISITION_STATES, 'Status', tracking=True, required=True, copy=False, default='draft') state_blanket_order = fields.Selection(PURCHASE_REQUISITION_STATES, compute='_set_state') - picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type', required=True, default=_get_picking_in) is_quantity_copy = fields.Selection(related='type_id.quantity_copy', readonly=True) currency_id = fields.Many2one('res.currency', 'Currency', required=True, default=lambda self: self.env.user.company_id.currency_id.id) @@ -169,7 +157,6 @@ class PurchaseRequisition(models.Model): 'product_id': product_id.id, 'product_uom_id': product_uom.id, 'product_qty': product_qty, - 'move_dest_id': values.get('move_dest_ids') and values['move_dest_ids'][0].id or False, })], } @@ -196,7 +183,6 @@ class PurchaseRequisitionLine(models.Model): account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account') analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags') schedule_date = fields.Date(string='Scheduled Date') - move_dest_id = fields.Many2one('stock.move', 'Downstream Move') supplier_info_ids = fields.One2many('product.supplierinfo', 'purchase_requisition_line_id') @api.model @@ -281,5 +267,4 @@ class PurchaseRequisitionLine(models.Model): 'date_planned': date_planned, 'account_analytic_id': self.account_analytic_id.id, 'analytic_tag_ids': self.analytic_tag_ids.ids, - 'move_dest_ids': self.move_dest_id and [(4, self.move_dest_id.id)] or [] } diff --git a/addons/purchase_requisition/security/ir.model.access.csv b/addons/purchase_requisition/security/ir.model.access.csv index f31808d0139..504a88faa04 100644 --- a/addons/purchase_requisition/security/ir.model.access.csv +++ b/addons/purchase_requisition/security/ir.model.access.csv @@ -5,5 +5,3 @@ access_purchase_requisition,purchase.requisition,model_purchase_requisition,purc 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
access_purchase_requisition_line_manager,purchase.requisition.line manager,model_purchase_requisition_line,purchase.group_purchase_manager,1,0,0,0
-access_purchase_requisition_stock_manager,purchase.requisition,model_purchase_requisition,stock.group_stock_manager,1,0,1,0
-access_purchase_requisition_line_stock_manager,purchase.requisition.line,model_purchase_requisition_line,stock.group_stock_manager,1,0,1,0
diff --git a/addons/purchase_requisition/tests/__init__.py b/addons/purchase_requisition/tests/__init__.py index bcd6590145c..e3d5157369b 100644 --- a/addons/purchase_requisition/tests/__init__.py +++ b/addons/purchase_requisition/tests/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import common from . import test_purchase_requisition diff --git a/addons/purchase_requisition/tests/common.py b/addons/purchase_requisition/tests/common.py new file mode 100644 index 00000000000..865216d45a4 --- /dev/null +++ b/addons/purchase_requisition/tests/common.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from odoo.tests import common + + +class TestPurchaseRequisitionCommon(common.TransactionCase): + + def setUp(self): + super(TestPurchaseRequisitionCommon, self).setUp() + + self.product_09_id = self.ref('product.product_product_9') + self.product_09_uom_id = self.ref('uom.product_uom_unit') + self.product_13_id = self.ref('product.product_product_13') + self.res_partner_1_id = self.ref('base.res_partner_1') + self.res_company_id = self.ref('base.main_company') + self.env.user.company_id.currency_id = self.env.ref("base.USD").id + + self.ResUser = self.env['res.users'] + # Create a user as 'Purchase Requisition Manager' + self.res_users_purchase_requisition_manager = self.ResUser.create({'company_id': self.res_company_id, 'name': 'Purchase requisition Manager', 'login': 'prm', 'email': 'requisition_manager@yourcompany.com'}) + # Added groups for Purchase Requisition Manager. + self.res_users_purchase_requisition_manager.group_id = self.ref('purchase.group_purchase_manager') + # Create a user as 'Purchase Requisition User' + self.res_users_purchase_requisition_user = self.ResUser.create({'company_id': self.res_company_id, 'name': 'Purchase requisition User', 'login': 'pru', 'email': 'requisition_user@yourcompany.com'}) + # Added groups for Purchase Requisition User. + self.res_users_purchase_requisition_user.group_id = self.ref('purchase.group_purchase_user') + + # In order to test process of the purchase requisition ,create requisition + self.requisition1 = self.env['purchase.requisition'].create({'line_ids': [(0, 0, {'product_id': self.product_09_id, 'product_qty': 10.0, 'product_uom_id': self.product_09_uom_id})]}) diff --git a/addons/purchase_requisition/tests/test_purchase_requisition.py b/addons/purchase_requisition/tests/test_purchase_requisition.py index 453fd83bdc6..324d013f60f 100644 --- a/addons/purchase_requisition/tests/test_purchase_requisition.py +++ b/addons/purchase_requisition/tests/test_purchase_requisition.py @@ -1,36 +1,10 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.tests import common -from odoo import fields -from datetime import datetime -from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +from odoo.addons.purchase_requisition.tests.common import TestPurchaseRequisitionCommon -class TestPurchaseRequisition(common.TransactionCase): - - def setUp(self): - super(TestPurchaseRequisition, self).setUp() - - self.product_09_id = self.ref('product.product_product_9') - self.product_09_uom_id = self.ref('uom.product_uom_unit') - self.product_13_id = self.ref('product.product_product_13') - self.res_partner_1_id = self.ref('base.res_partner_1') - self.res_company_id = self.ref('base.main_company') - self.env.user.company_id.currency_id = self.env.ref("base.USD").id - - self.ResUser = self.env['res.users'] - # Create a user as 'Purchase Requisition Manager' - self.res_users_purchase_requisition_manager = self.ResUser.create({'company_id': self.res_company_id, 'name': 'Purchase requisition Manager', 'login': 'prm', 'email': 'requisition_manager@yourcompany.com'}) - # Added groups for Purchase Requisition Manager. - self.res_users_purchase_requisition_manager.group_id = self.ref('purchase.group_purchase_manager') - # Create a user as 'Purchase Requisition User' - self.res_users_purchase_requisition_user = self.ResUser.create({'company_id': self.res_company_id, 'name': 'Purchase requisition User', 'login': 'pru', 'email': 'requisition_user@yourcompany.com'}) - # Added groups for Purchase Requisition User. - self.res_users_purchase_requisition_user.group_id = self.ref('purchase.group_purchase_user') - - # In order to test process of the purchase requisition ,create requisition - self.requisition1 = self.env['purchase.requisition'].create({'line_ids': [(0, 0, {'product_id': self.product_09_id, 'product_qty': 10.0, 'product_uom_id': self.product_09_uom_id})]}) +class TestPurchaseRequisition(TestPurchaseRequisitionCommon): def test_00_purchase_requisition_users(self): self.assertTrue(self.res_users_purchase_requisition_manager, 'Manager Should be created') @@ -45,38 +19,8 @@ class TestPurchaseRequisition(common.TransactionCase): # I duplicate requisition. self.requisition1.sudo(self.res_users_purchase_requisition_user.id).copy() - def test_02_purchase_requisition(self): - date_planned = fields.Datetime.now() - warehouse = self.env['stock.warehouse'].browse(self.ref('stock.warehouse0')) - product = self.env['product.product'].browse(self.product_13_id) - product.write({'route_ids': [(4, self.ref('purchase_stock.route_warehouse0_buy'))]}) - self.env['procurement.group'].run(product, 14, self.env['uom.uom'].browse(self.ref('uom.product_uom_unit')), warehouse.lot_stock_id, '/', '/', - { - '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.sudo(self.res_users_purchase_requisition_user.id).action_in_progress() - self.requisition1.sudo(self.res_users_purchase_requisition_user.id).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_id, '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_03_purchase_requisition(self): + def test_02_purchase_requisition(self): price_product09 = 34 price_product13 = 62 quantity = 26 @@ -125,177 +69,6 @@ class TestPurchaseRequisition(common.TransactionCase): self.assertFalse(self.env['product.supplierinfo'].search([('id', '=', supplierinfo09.id)]), 'The supplier info should be removed') self.assertFalse(self.env['product.supplierinfo'].search([('id', '=', supplierinfo13.id)]), 'The supplier info should be removed') - def test_04_purchase_requisition(self): - """ Set a static supplier info and make a delivery order for a "to buy" product, check that the purchase order created is consistent. Create a blanket order for this product, create a new delivery order for this product and check that the create purchase order set its values according to the blanket order. - """ - - # Product creation - unit = self.ref("uom.product_uom_unit") - warehouse1 = self.env.ref('stock.warehouse0') - route_buy = self.ref('purchase_stock.route_warehouse0_buy') - route_mto = warehouse1.mto_pull_id.route_id.id - vendor1 = self.env['res.partner'].create({'name': 'AAA', 'email': 'from.test@example.com'}) - supplier_info1 = self.env['product.supplierinfo'].create({ - 'name': vendor1.id, - 'price': 50, - }) - product_test = self.env['product.product'].create({ - 'name': 'Usb Keyboard', - 'type': 'product', - 'uom_id': unit, - 'uom_po_id': unit, - 'seller_ids': [(6, 0, [supplier_info1.id])], - 'route_ids': [(6, 0, [route_buy, route_mto])] - }) - - # Stock picking - stock_location = self.env.ref('stock.stock_location_stock') - customer_location = self.env.ref('stock.stock_location_customers') - move1 = self.env['stock.move'].create({ - 'name': '10 in', - 'procure_method': 'make_to_order', - 'location_id': stock_location.id, - 'location_dest_id': customer_location.id, - 'product_id': product_test.id, - 'product_uom': unit, - 'product_uom_qty': 10.0, - 'price_unit': 10, - }) - move1._action_confirm() - - # Verification : there should be a purchase order created with the good price - purchase1 = self.env['purchase.order'].search([('partner_id', '=', vendor1.id)]) - self.assertEqual(purchase1.order_line.price_unit, 50, 'The price on the purchase order is not the supplierinfo one') - - # Blanket order creation - line1 = (0, 0, {'product_id': product_test.id, 'product_qty': 18, 'product_uom_id': product_test.uom_po_id.id, 'price_unit': 42}) - requisition_type = self.env['purchase.requisition.type'].create({ - 'name': 'Blanket test', - 'quantity_copy': 'none', - }) - requisition_blanket = self.env['purchase.requisition'].create({ - 'line_ids': [line1], - 'type_id': requisition_type.id, - 'vendor_id': vendor1.id, - 'currency_id': self.env.ref("base.USD").id, - }) - requisition_blanket.action_in_progress() - - # Second stock move - move2 = self.env['stock.move'].create({ - 'name': '10 in', - 'procure_method': 'make_to_order', - 'location_id': stock_location.id, - 'location_dest_id': customer_location.id, - 'product_id': product_test.id, - 'product_uom': unit, - 'product_uom_qty': 10.0, - 'price_unit': 10 - }) - move2._action_confirm() - - # Verifications - purchase2 = self.env['purchase.order'].search([('partner_id', '=', vendor1.id), ('requisition_id', '=', requisition_blanket.id)]) - self.assertEqual(purchase2.order_line.price_unit, 42, 'The price on the purchase order is not the blanquet order one') - - def test_05_purchase_requisition(self): - """ Two blanket orders on different 'make to order' products must generate - two different purchase orders - """ - - # Product creation - unit = self.ref("uom.product_uom_unit") - warehouse1 = self.env.ref('stock.warehouse0') - route_buy = self.ref('purchase_stock.route_warehouse0_buy') - route_mto = warehouse1.mto_pull_id.route_id.id - vendor1 = self.env['res.partner'].create({'name': 'AAA', 'email': 'from.test@example.com'}) - supplier_info1 = self.env['product.supplierinfo'].create({ - 'name': vendor1.id, - 'price': 50, - }) - product_1 = self.env['product.product'].create({ - 'name': 'product1', - 'type': 'product', - 'uom_id': unit, - 'uom_po_id': unit, - 'seller_ids': [(6, 0, [supplier_info1.id])], - 'route_ids': [(6, 0, [route_buy, route_mto])] - }) - product_2 = self.env['product.product'].create({ - 'name': 'product2', - 'type': 'product', - 'uom_id': unit, - 'uom_po_id': unit, - 'seller_ids': [(6, 0, [supplier_info1.id])], - 'route_ids': [(6, 0, [route_buy, route_mto])] - }) - # Blanket orders creation - requisition_type = self.env['purchase.requisition.type'].create({ - 'name': 'Blanket test', - 'quantity_copy': 'none', - }) - line1 = (0, 0, {'product_id': product_1.id, 'product_qty': 18, 'product_uom_id': product_1.uom_po_id.id, 'price_unit': 41}) - line2 = (0, 0, {'product_id': product_2.id, 'product_qty': 18, 'product_uom_id': product_2.uom_po_id.id, 'price_unit': 42}) - requisition_1 = self.env['purchase.requisition'].create({ - 'line_ids': [line1], - 'type_id': requisition_type.id, - 'vendor_id': vendor1.id, - 'currency_id': self.env.ref("base.USD").id, - }) - requisition_2 = self.env['purchase.requisition'].create({ - 'line_ids': [line2], - 'type_id': requisition_type.id, - 'vendor_id': vendor1.id, - 'currency_id': self.env.ref("base.USD").id, - }) - requisition_1.action_in_progress() - requisition_2.action_in_progress() - # Stock moves - stock_location = self.env.ref('stock.stock_location_stock') - customer_location = self.env.ref('stock.stock_location_customers') - move1 = self.env['stock.move'].create({ - 'name': '10 in', - 'procure_method': 'make_to_order', - 'location_id': stock_location.id, - 'location_dest_id': customer_location.id, - 'product_id': product_1.id, - 'product_uom': unit, - 'product_uom_qty': 10.0, - 'price_unit': 100, - }) - move2 = self.env['stock.move'].create({ - 'name': '10 in', - 'procure_method': 'make_to_order', - 'location_id': stock_location.id, - 'location_dest_id': customer_location.id, - 'product_id': product_2.id, - 'product_uom': unit, - 'product_uom_qty': 10.0, - 'price_unit': 100, - }) - move1._action_confirm() - move2._action_confirm() - # Verifications - POL1 = self.env['purchase.order.line'].search([('product_id', '=', product_1.id)]).order_id - POL2 = self.env['purchase.order.line'].search([('product_id', '=', product_2.id)]).order_id - self.assertFalse(POL1 == POL2, 'The two blanket orders should generate two purchase different purchase orders') - POL1.write({'order_line': [ - (0, 0, { - 'name': product_2.name, - 'product_id': product_2.id, - 'product_qty': 5.0, - 'product_uom': product_2.uom_po_id.id, - 'price_unit': 0, - 'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT), - }) - ]}) - order_line = self.env['purchase.order.line'].search([ - ('product_id', '=', product_2.id), - ('product_qty', '=', 5.0), - ]) - order_line._onchange_quantity() - self.assertEqual(order_line.price_unit, 50, 'The supplier info chosen should be the one without requisition id') - def test_06_purchase_requisition(self): """ Create a blanquet order for a product and a vendor already linked via a supplier info""" diff --git a/addons/purchase_requisition/views/purchase_requisition_views.xml b/addons/purchase_requisition/views/purchase_requisition_views.xml index cb4bb9ed82c..82496969d51 100644 --- a/addons/purchase_requisition/views/purchase_requisition_views.xml +++ b/addons/purchase_requisition/views/purchase_requisition_views.xml @@ -151,7 +151,6 @@ <field name="ordering_date" attrs="{'readonly': [('state','not in',('draft','in_progress','open','ongoing'))]}"/> <field name="schedule_date" attrs="{'readonly': [('state','not in',('draft','in_progress','open','ongoing'))]}"/> <field name="origin" placeholder="e.g. PO0025" attrs="{'readonly': [('state', '!=', 'draft')]}"/> - <field name="picking_type_id" widget="selection" groups="stock.group_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'))]}"/> </group> </group> diff --git a/addons/purchase_requisition/views/res_config_settings_views.xml b/addons/purchase_requisition/views/res_config_settings_views.xml index d95cd506b29..ba0e177460c 100644 --- a/addons/purchase_requisition/views/res_config_settings_views.xml +++ b/addons/purchase_requisition/views/res_config_settings_views.xml @@ -3,7 +3,7 @@ <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_stock.res_config_settings_view_form_purchase"/> + <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="mt16"> diff --git a/addons/purchase_requisition_stock/__init__.py b/addons/purchase_requisition_stock/__init__.py new file mode 100644 index 00000000000..d6210b1285d --- /dev/null +++ b/addons/purchase_requisition_stock/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/purchase_requisition_stock/__manifest__.py b/addons/purchase_requisition_stock/__manifest__.py new file mode 100644 index 00000000000..86cceb302b9 --- /dev/null +++ b/addons/purchase_requisition_stock/__manifest__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Purchase Requisition Stock', + 'version': '1.2', + 'category': 'Purchases', + 'sequence': 70, + 'summary': '', + 'description': "", + 'depends': ['purchase_requisition', 'purchase_stock'], + 'demo': [ + 'data/purchase_requisition_stock_demo.xml' + ], + 'data': [ + 'security/ir.model.access.csv', + 'data/purchase_requisition_stock_data.xml', + 'views/purchase_requisition_views.xml', + ], + 'installable': True, + 'auto_install': True, +} diff --git a/addons/purchase_requisition_stock/data/purchase_requisition_stock_data.xml b/addons/purchase_requisition_stock/data/purchase_requisition_stock_data.xml new file mode 100644 index 00000000000..59f7eb22a44 --- /dev/null +++ b/addons/purchase_requisition_stock/data/purchase_requisition_stock_data.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data noupdate="1"> + <function + model="ir.default" name="set" + eval="('purchase.requisition', 'warehouse_id', ref('stock.warehouse0'))"/> + </data> +</odoo> diff --git a/addons/purchase_requisition_stock/data/purchase_requisition_stock_demo.xml b/addons/purchase_requisition_stock/data/purchase_requisition_stock_demo.xml new file mode 100644 index 00000000000..a7480a3dd80 --- /dev/null +++ b/addons/purchase_requisition_stock/data/purchase_requisition_stock_demo.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data noupdate="1"> + <record id="purchase_requisition.requisition1" model="purchase.requisition"> + <field name="warehouse_id" ref="stock.stock_warehouse_shop0"/> + </record> + </data> +</odoo> diff --git a/addons/purchase_requisition_stock/models/__init__.py b/addons/purchase_requisition_stock/models/__init__.py new file mode 100644 index 00000000000..1590d87846c --- /dev/null +++ b/addons/purchase_requisition_stock/models/__init__.py @@ -0,0 +1,5 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import purchase +from . import stock +from . import purchase_requisition diff --git a/addons/purchase_requisition_stock/models/purchase.py b/addons/purchase_requisition_stock/models/purchase.py new file mode 100644 index 00000000000..344fc1b8ea7 --- /dev/null +++ b/addons/purchase_requisition_stock/models/purchase.py @@ -0,0 +1,13 @@ +# -*- encoding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class PurchaseOrder(models.Model): + _inherit = 'purchase.order' + + @api.onchange('requisition_id') + def _onchange_requisition_id(self): + super(PurchaseOrder, self)._onchange_requisition_id() + self.picking_type_id = self.requisition_id.picking_type_id.id diff --git a/addons/purchase_requisition_stock/models/purchase_requisition.py b/addons/purchase_requisition_stock/models/purchase_requisition.py new file mode 100644 index 00000000000..ae68c0e72ec --- /dev/null +++ b/addons/purchase_requisition_stock/models/purchase_requisition.py @@ -0,0 +1,38 @@ +# -*- encoding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ + + +class PurchaseRequisition(models.Model): + _inherit = 'purchase.requisition' + + def _get_picking_in(self): + pick_in = self.env.ref('stock.picking_type_in', raise_if_not_found=False) + company = self.env['res.company']._company_default_get('purchase.requisition') + if not pick_in or pick_in.sudo().warehouse_id.company_id.id != company.id: + pick_in = self.env['stock.picking.type'].search( + [('warehouse_id.company_id', '=', company.id), ('code', '=', 'incoming')], + limit=1, + ) + return pick_in + + warehouse_id = fields.Many2one('stock.warehouse', string='Warehouse') + picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type', required=True, default=_get_picking_in) + + def _prepare_tender_values(self, product_id, product_qty, product_uom, location_id, name, origin, values): + res = super(PurchaseRequisition, self)._prepare_tender_values(product_id, product_qty, product_uom, location_id, name, origin, values) + res['line_ids'][0][2]['move_dest_id'] = values.get('move_dest_ids') and values['move_dest_ids'][0].id or False + return res + + +class PurchaseRequisitionLine(models.Model): + _inherit = "purchase.requisition.line" + + move_dest_id = fields.Many2one('stock.move', 'Downstream Move') + + @api.multi + def _prepare_purchase_order_line(self, name, product_qty=0.0, price_unit=0.0, taxes_ids=False): + res = super(PurchaseRequisitionLine, self)._prepare_purchase_order_line(name, product_qty, price_unit, taxes_ids) + res['move_dest_ids'] = self.move_dest_id and [(4, self.move_dest_id.id)] or [] + return res diff --git a/addons/purchase_requisition/models/stock.py b/addons/purchase_requisition_stock/models/stock.py index fd42537b8a1..fd42537b8a1 100644 --- a/addons/purchase_requisition/models/stock.py +++ b/addons/purchase_requisition_stock/models/stock.py diff --git a/addons/purchase_requisition_stock/security/ir.model.access.csv b/addons/purchase_requisition_stock/security/ir.model.access.csv new file mode 100644 index 00000000000..4599e4d742e --- /dev/null +++ b/addons/purchase_requisition_stock/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_purchase_requisition_stock_manager,purchase.requisition,purchase_requisition.model_purchase_requisition,stock.group_stock_manager,1,0,1,0 +access_purchase_requisition_line_stock_manager,purchase.requisition.line,purchase_requisition.model_purchase_requisition_line,stock.group_stock_manager,1,0,1,0 + diff --git a/addons/purchase_requisition_stock/tests/__init__.py b/addons/purchase_requisition_stock/tests/__init__.py new file mode 100644 index 00000000000..cb417b11e8e --- /dev/null +++ b/addons/purchase_requisition_stock/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_purchase_requisition_stock diff --git a/addons/purchase_requisition_stock/tests/test_purchase_requisition_stock.py b/addons/purchase_requisition_stock/tests/test_purchase_requisition_stock.py new file mode 100644 index 00000000000..2e395357592 --- /dev/null +++ b/addons/purchase_requisition_stock/tests/test_purchase_requisition_stock.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields +from datetime import datetime +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +from odoo.addons.purchase_requisition.tests.common import TestPurchaseRequisitionCommon + + +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')) + product = self.env['product.product'].browse(self.product_13_id) + product.write({'route_ids': [(4, self.ref('purchase_stock.route_warehouse0_buy'))]}) + self.env['procurement.group'].run(product, 14, self.env['uom.uom'].browse(self.ref('uom.product_uom_unit')), warehouse.lot_stock_id, '/', '/', + { + '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.sudo(self.res_users_purchase_requisition_user.id).action_in_progress() + self.requisition1.sudo(self.res_users_purchase_requisition_user.id).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_id, '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): + """ Set a static supplier info and make a delivery order for a "to buy" product, check that the purchase order created is consistent. Create a blanket order for this product, create a new delivery order for this product and check that the create purchase order set its values according to the blanket order. + """ + + # Product creation + unit = self.ref("uom.product_uom_unit") + warehouse1 = self.env.ref('stock.warehouse0') + route_buy = self.ref('purchase_stock.route_warehouse0_buy') + route_mto = warehouse1.mto_pull_id.route_id.id + vendor1 = self.env['res.partner'].create({'name': 'AAA', 'email': 'from.test@example.com'}) + supplier_info1 = self.env['product.supplierinfo'].create({ + 'name': vendor1.id, + 'price': 50, + }) + product_test = self.env['product.product'].create({ + 'name': 'Usb Keyboard', + 'type': 'product', + 'uom_id': unit, + 'uom_po_id': unit, + 'seller_ids': [(6, 0, [supplier_info1.id])], + 'route_ids': [(6, 0, [route_buy, route_mto])] + }) + + # Stock picking + stock_location = self.env.ref('stock.stock_location_stock') + customer_location = self.env.ref('stock.stock_location_customers') + move1 = self.env['stock.move'].create({ + 'name': '10 in', + 'procure_method': 'make_to_order', + 'location_id': stock_location.id, + 'location_dest_id': customer_location.id, + 'product_id': product_test.id, + 'product_uom': unit, + 'product_uom_qty': 10.0, + 'price_unit': 10, + }) + move1._action_confirm() + + # Verification : there should be a purchase order created with the good price + purchase1 = self.env['purchase.order'].search([('partner_id', '=', vendor1.id)]) + self.assertEqual(purchase1.order_line.price_unit, 50, 'The price on the purchase order is not the supplierinfo one') + + # Blanket order creation + line1 = (0, 0, {'product_id': product_test.id, 'product_qty': 18, 'product_uom_id': product_test.uom_po_id.id, 'price_unit': 42}) + requisition_type = self.env['purchase.requisition.type'].create({ + 'name': 'Blanket test', + 'quantity_copy': 'none', + }) + requisition_blanket = self.env['purchase.requisition'].create({ + 'line_ids': [line1], + 'type_id': requisition_type.id, + 'vendor_id': vendor1.id, + 'currency_id': self.env.ref("base.USD").id, + }) + requisition_blanket.action_in_progress() + + # Second stock move + move2 = self.env['stock.move'].create({ + 'name': '10 in', + 'procure_method': 'make_to_order', + 'location_id': stock_location.id, + 'location_dest_id': customer_location.id, + 'product_id': product_test.id, + 'product_uom': unit, + 'product_uom_qty': 10.0, + 'price_unit': 10 + }) + move2._action_confirm() + + # Verifications + purchase2 = self.env['purchase.order'].search([('partner_id', '=', vendor1.id), ('requisition_id', '=', requisition_blanket.id)]) + self.assertEqual(purchase2.order_line.price_unit, 42, 'The price on the purchase order is not the blanquet order one') + + def test_03_purchase_requisition_stock(self): + """ Two blanket orders on different 'make to order' products must generate + two different purchase orders + """ + + # Product creation + unit = self.ref("uom.product_uom_unit") + warehouse1 = self.env.ref('stock.warehouse0') + route_buy = self.ref('purchase_stock.route_warehouse0_buy') + route_mto = warehouse1.mto_pull_id.route_id.id + vendor1 = self.env['res.partner'].create({'name': 'AAA', 'email': 'from.test@example.com'}) + supplier_info1 = self.env['product.supplierinfo'].create({ + 'name': vendor1.id, + 'price': 50, + }) + product_1 = self.env['product.product'].create({ + 'name': 'product1', + 'type': 'product', + 'uom_id': unit, + 'uom_po_id': unit, + 'seller_ids': [(6, 0, [supplier_info1.id])], + 'route_ids': [(6, 0, [route_buy, route_mto])] + }) + product_2 = self.env['product.product'].create({ + 'name': 'product2', + 'type': 'product', + 'uom_id': unit, + 'uom_po_id': unit, + 'seller_ids': [(6, 0, [supplier_info1.id])], + 'route_ids': [(6, 0, [route_buy, route_mto])] + }) + # Blanket orders creation + requisition_type = self.env['purchase.requisition.type'].create({ + 'name': 'Blanket test', + 'quantity_copy': 'none', + }) + line1 = (0, 0, {'product_id': product_1.id, 'product_qty': 18, 'product_uom_id': product_1.uom_po_id.id, 'price_unit': 41}) + line2 = (0, 0, {'product_id': product_2.id, 'product_qty': 18, 'product_uom_id': product_2.uom_po_id.id, 'price_unit': 42}) + requisition_1 = self.env['purchase.requisition'].create({ + 'line_ids': [line1], + 'type_id': requisition_type.id, + 'vendor_id': vendor1.id, + 'currency_id': self.env.ref("base.USD").id, + }) + requisition_2 = self.env['purchase.requisition'].create({ + 'line_ids': [line2], + 'type_id': requisition_type.id, + 'vendor_id': vendor1.id, + 'currency_id': self.env.ref("base.USD").id, + }) + requisition_1.action_in_progress() + requisition_2.action_in_progress() + # Stock moves + stock_location = self.env.ref('stock.stock_location_stock') + customer_location = self.env.ref('stock.stock_location_customers') + move1 = self.env['stock.move'].create({ + 'name': '10 in', + 'procure_method': 'make_to_order', + 'location_id': stock_location.id, + 'location_dest_id': customer_location.id, + 'product_id': product_1.id, + 'product_uom': unit, + 'product_uom_qty': 10.0, + 'price_unit': 100, + }) + move2 = self.env['stock.move'].create({ + 'name': '10 in', + 'procure_method': 'make_to_order', + 'location_id': stock_location.id, + 'location_dest_id': customer_location.id, + 'product_id': product_2.id, + 'product_uom': unit, + 'product_uom_qty': 10.0, + 'price_unit': 100, + }) + move1._action_confirm() + move2._action_confirm() + # Verifications + POL1 = self.env['purchase.order.line'].search([('product_id', '=', product_1.id)]).order_id + POL2 = self.env['purchase.order.line'].search([('product_id', '=', product_2.id)]).order_id + self.assertFalse(POL1 == POL2, 'The two blanket orders should generate two purchase different purchase orders') + POL1.write({'order_line': [ + (0, 0, { + 'name': product_2.name, + 'product_id': product_2.id, + 'product_qty': 5.0, + 'product_uom': product_2.uom_po_id.id, + 'price_unit': 0, + 'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT), + }) + ]}) + order_line = self.env['purchase.order.line'].search([ + ('product_id', '=', product_2.id), + ('product_qty', '=', 5.0), + ]) + order_line._onchange_quantity() + self.assertEqual(order_line.price_unit, 50, 'The supplier info chosen should be the one without requisition id') diff --git a/addons/purchase_requisition_stock/views/purchase_requisition_views.xml b/addons/purchase_requisition_stock/views/purchase_requisition_views.xml new file mode 100644 index 00000000000..195863d6c35 --- /dev/null +++ b/addons/purchase_requisition_stock/views/purchase_requisition_views.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data> + <record id="view_purchase_requisition_form_inherit" model="ir.ui.view"> + <field name="name">purchase.requisition.form.inherit</field> + <field name="model">purchase.requisition</field> + <field name="inherit_id" ref="purchase_requisition.view_purchase_requisition_form" /> + <field name="arch" type="xml"> + <field name="origin" position="after"> + <field name="picking_type_id" widget="selection" groups="stock.group_adv_location" attrs="{'readonly': [('state', '!=', 'draft')]}"/> + </field> + </field> + </record> + </data> +</odoo> |
