From b65c67a2598fd3ad433af05ed49d398c473b76ae Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli <nim@odoo.com> Date: Mon, 21 Sep 2015 17:50:03 +0200 Subject: [PATCH] [IMP] product: new product_id field on supplierinfo We introduce a new field on supplierinfo: product_id. Thanks to this field, the vendor information which will be used in a purchase order can be specific to the product variant. --- addons/product/product.py | 61 +++++++++---------- addons/product/product_view.xml | 3 +- addons/product/test/product_pricelist.yml | 4 +- addons/purchase/purchase.py | 8 +-- .../purchase/test/process/run_scheduler.yml | 2 +- addons/sale_mrp/test/sale_mrp.yml | 3 +- addons/stock/procurement.py | 2 +- addons/stock/test/packing.yml | 2 +- addons/stock/test/packingneg.yml | 2 +- .../test/orderpoint_calendar.yml | 3 +- .../test/cancellation_propagated.yml | 2 +- addons/stock_dropshipping/test/crossdock.yml | 2 +- addons/stock_dropshipping/test/dropship.yml | 2 +- .../test/procurementexception.yml | 2 +- 14 files changed, 48 insertions(+), 50 deletions(-) diff --git a/addons/product/product.py b/addons/product/product.py index b65c53757ef6..e435ec43c567 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -453,28 +453,6 @@ class product_template(osv.osv): r = self.pool['product.product'].read(cr, uid, ids, ['product_tmpl_id'], context=context) return [x['product_tmpl_id'][0] for x in r] - def _select_seller(self, cr, uid, ids, name, arg, context=None): - if context is None: - context = {} - res = {} - partner = context.get('partner_id') - minimal_quantity = context.get('quantity', 0.0) - date = context.get('date', time.strftime(DEFAULT_SERVER_DATE_FORMAT)) - for product in self.browse(cr, uid, ids, context=context): - res[product.id] = False - for seller in product.seller_ids: - if seller.date_start and seller.date_start > date: - continue - if seller.date_end and seller.date_end < date: - continue - if partner and seller.name.id != partner: - continue - if minimal_quantity and minimal_quantity < seller.qty: - continue - res[product.id] = seller - break - return res - def _get_product_template_type(self, cr, uid, context=None): return [('consu', 'Consumable'), ('service', 'Service')] _get_product_template_type_wrapper = lambda self, *args, **kwargs: self._get_product_template_type(*args, **kwargs) @@ -547,14 +525,6 @@ class product_template(osv.osv): help="Gives the different ways to package the same product. This has no impact on " "the picking order and is mainly used if you use the EDI module."), 'seller_ids': fields.one2many('product.supplierinfo', 'product_tmpl_id', 'Vendor'), - 'selected_seller_id': fields.function(_select_seller, type='many2one', relation='product.supplierinfo', string='Selected Seller', help='Technical field that selects a seller based on priority (sequence) and an optional partner and/or a minimal quantity in the context'), - 'seller_delay': fields.related('selected_seller_id','delay', type='integer', string='Vendor Lead Time', - help="This is the average delay in days between the purchase order confirmation and the receipts for this product and for the default vendor. It is used by the scheduler to order requests based on reordering delays."), - 'seller_qty': fields.related('selected_seller_id','qty', type='float', string='Vendor Quantity', - help="This is minimum quantity to purchase from Main Vendor."), - 'seller_id': fields.related('selected_seller_id','name', type='many2one', relation='res.partner', string='Main Vendor', - help="Main vendor who has highest priority in vendor list."), - 'seller_price': fields.related('selected_seller_id','price', type='float', string='Vendor Price', help="Purchase price from main vendor."), 'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the product without removing it."), 'color': fields.integer('Color Index'), @@ -934,6 +904,30 @@ class product_product(osv.osv): result[product.id] = price_extra return result + def _select_seller(self, cr, uid, ids, name, arg, context=None): + if context is None: + context = {} + res = {} + partner = context.get('partner_id') + minimal_quantity = context.get('quantity', 0.0) + date = context.get('date', time.strftime(DEFAULT_SERVER_DATE_FORMAT)) + for product in self.browse(cr, uid, ids, context=context): + res[product.id] = False + for seller in product.seller_ids: + if seller.date_start and seller.date_start > date: + continue + if seller.date_end and seller.date_end < date: + continue + if partner and seller.name.id != partner: + continue + if minimal_quantity and minimal_quantity < seller.qty: + continue + if seller.product_id and seller.product_id != product: + continue + res[product.id] = seller + break + return res + _columns = { 'price': fields.function(_product_price, fnct_inv=_set_product_lst_price, type='float', string='Price', digits_compute=dp.get_precision('Product Price')), 'price_extra': fields.function(_get_price_extra, type='float', string='Variant Extra Price', help="This is the sum of the extra price of all attributes", digits_compute=dp.get_precision('Product Price')), @@ -970,6 +964,7 @@ class product_product(osv.osv): groups="base.group_user", string="Cost"), 'volume': fields.float('Volume', help="The volume in m3."), 'weight': fields.float('Gross Weight', digits_compute=dp.get_precision('Stock Weight'), help="The weight of the contents in Kg, not including any packaging, etc."), + 'selected_seller_id': fields.function(_select_seller, type='many2one', relation='product.supplierinfo', string='Selected Seller', help='Technical field that selects a seller based on priority (sequence) and an optional partner and/or a minimal quantity in the context'), } _defaults = { @@ -1047,7 +1042,10 @@ class product_product(osv.osv): name = variant and "%s (%s)" % (product.name, variant) or product.name sellers = [] if partner_ids: - sellers = filter(lambda x: x.name.id in partner_ids, product.seller_ids) + if variant: + sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and (x.product_id == product)] + if not sellers: + sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and not x.product_id] if sellers: for s in sellers: seller_variant = s.product_name and ( @@ -1238,6 +1236,7 @@ class product_supplierinfo(osv.osv): 'product_tmpl_id': fields.many2one('product.template', 'Product Template', ondelete='cascade', select=True, oldname='product_id'), 'delay': fields.integer('Delivery Lead Time', required=True, help="Lead time in days between the confirmation of the purchase order and the receipt of the products in your warehouse. Used by the scheduler for automatic computation of the purchase order planning."), 'company_id': fields.many2one('res.company', string='Company', select=1), + 'product_id': fields.many2one('product.product', string='Product Variant'), } _defaults = { 'min_qty': 0.0, diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 88b03fd2f964..71cd5c023da7 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -133,7 +133,7 @@ </group> </group> <separator string="Vendors"/> - <field name="seller_ids"/> + <field name="seller_ids" context="{'default_product_tmpl_id': active_id}"/> <group name="packaging" string="Packaging" colspan="4" attrs="{'invisible':[('type', 'not in', ['product', 'consu'])]}" @@ -769,6 +769,7 @@ <field name="name" context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}"/> <field name="product_name"/> <field name="product_code"/> + <field name="product_id" domain="[('product_tmpl_id', '=', product_tmpl_id)]" invisible="1"/> <label for="delay"/> <div> <field name="delay" class="oe_inline"/> days diff --git a/addons/product/test/product_pricelist.yml b/addons/product/test/product_pricelist.yml index 30f5f4b9e505..dc79502cc1a1 100644 --- a/addons/product/test/product_pricelist.yml +++ b/addons/product/test/product_pricelist.yml @@ -53,7 +53,7 @@ from openerp.tools import float_compare context.update({'quantity':1, 'date': False, 'partner_id': ref('base.res_partner_4')}) product = self.browse(cr, uid, ref("product_product_6"), context=context) - assert float_compare(product.seller_price, 790, precision_digits=2) == 0, "Wrong cost price: LCD Monitor." + assert float_compare(product.selected_seller_id.price, 790, precision_digits=2) == 0, "Wrong cost price: LCD Monitor." - I check cost price of LCD Monitor if more than 3 Unit. - @@ -61,7 +61,7 @@ from openerp.tools import float_compare context.update({'quantity':3}) product = self.browse(cr, uid, ref("product_product_6"), context=context) - assert float_compare(product.seller_price, 785, precision_digits=2) == 0, "Wrong cost price: LCD Monitor if more than 3 Unit." + assert float_compare(product.selected_seller_id.price, 785, precision_digits=2) == 0, "Wrong cost price: LCD Monitor if more than 3 Unit." - I print the sale prices report. - diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 2f8b6bf0b851..d54d1a5d140c 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -543,7 +543,7 @@ class PurchaseOrderLine(models.Model): :rtype: datetime :return: desired Schedule Date for the PO line """ - supplier_delay = int(product.seller_delay) if product else 0 + supplier_delay = int(product.selected_seller_id.delay) if product else 0 if po: date_order = po.date_order else: @@ -635,7 +635,7 @@ class PurchaseOrderLine(models.Model): 'quantity': quantity_seller_uom_id, }) - price_unit = product.seller_price + price_unit = product.selected_seller_id.price if price_unit and seller_currency_id != currency_id: price_unit = seller_currency_id.compute(price_unit, currency_id) @@ -736,7 +736,7 @@ class ProcurementOrder(models.Model): @api.v8 def _get_purchase_order_date(self, schedule_date): self.ensure_one() - seller_delay = int(self.product_id.seller_delay) + seller_delay = int(self.product_id.selected_seller_id.delay) return schedule_date - relativedelta(days=seller_delay) @api.v7 @@ -750,7 +750,7 @@ class ProcurementOrder(models.Model): :rtype: datetime :return: the desired Order Date for the PO """ - seller_delay = int(procurement.product_id.seller_delay) + seller_delay = int(procurement.product_id.selected_seller_id.delay) return schedule_date - relativedelta(days=seller_delay) @api.multi diff --git a/addons/purchase/test/process/run_scheduler.yml b/addons/purchase/test/process/run_scheduler.yml index 83c5c6a099b3..83255849b596 100644 --- a/addons/purchase/test/process/run_scheduler.yml +++ b/addons/purchase/test/process/run_scheduler.yml @@ -1,7 +1,7 @@ - In order to test the scheduler to generate RFQ, I create a new product - - !record {model: product.product, id: scheduler_product}: + !record {model: product.product, id: scheduler_product, view: False}: name: scheduler prod type: product seller_ids: diff --git a/addons/sale_mrp/test/sale_mrp.yml b/addons/sale_mrp/test/sale_mrp.yml index eddf48a20bb9..faa3b1a3e9a0 100644 --- a/addons/sale_mrp/test/sale_mrp.yml +++ b/addons/sale_mrp/test/sale_mrp.yml @@ -24,11 +24,10 @@ - I define a product Slider Mobile - - !record {model: product.product, id: product_product_slidermobile0}: + !record {model: product.product, id: product_product_slidermobile0, view: False}: categ_id: product_category_allproductssellable0 list_price: 200.0 name: Slider Mobile - seller_delay: '1' seller_ids: - delay: 1 name: base.res_partner_2 diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py index c2aac6a75a8d..8f6ef3265d4d 100644 --- a/addons/stock/procurement.py +++ b/addons/stock/procurement.py @@ -425,7 +425,7 @@ class procurement_order(osv.osv): days = orderpoint.lead_days or 0.0 if orderpoint.lead_type=='purchase': # These days will be substracted when creating the PO - days += orderpoint.product_id.seller_delay or 0.0 + days += orderpoint.product_id.selected_seller_id.delay or 0.0 date_planned = start_date + relativedelta(days=days) return date_planned.strftime(DEFAULT_SERVER_DATE_FORMAT) diff --git a/addons/stock/test/packing.yml b/addons/stock/test/packing.yml index 1fbef789380b..43bd4a640b0e 100644 --- a/addons/stock/test/packing.yml +++ b/addons/stock/test/packing.yml @@ -1,7 +1,7 @@ - Create a new stockable product - - !record {model: product.product, id: product1}: + !record {model: product.product, id: product1, view: False}: name: Nice product type: product categ_id: product.product_category_1 diff --git a/addons/stock/test/packingneg.yml b/addons/stock/test/packingneg.yml index 154dcf8197ea..2863b048f6ff 100644 --- a/addons/stock/test/packingneg.yml +++ b/addons/stock/test/packingneg.yml @@ -1,7 +1,7 @@ - Create a new "negative" stockable product - - !record {model: product.product, id: product_neg}: + !record {model: product.product, id: product_neg, view: False}: name: Negative product type: product categ_id: product.product_category_1 diff --git a/addons/stock_calendar/test/orderpoint_calendar.yml b/addons/stock_calendar/test/orderpoint_calendar.yml index 23e358b79eb4..8215f6246e35 100644 --- a/addons/stock_calendar/test/orderpoint_calendar.yml +++ b/addons/stock_calendar/test/orderpoint_calendar.yml @@ -17,7 +17,7 @@ - Create a product A with orderpoint with a calendar - - !record {model: product.product, id: product_calendar}: + !record {model: product.product, id: product_calendar, view: False}: name: Calendar Product seller_ids: - name: res_partner_cal @@ -96,4 +96,3 @@ assert datetime.datetime.strptime(procurement.next_delivery_date, DEFAULT_SERVER_DATETIME_FORMAT).weekday() == 3, 'The next delivery date should be on a Thursday' purchase_line_id_date_planned = datetime.datetime.strptime(procurement.purchase_line_id.date_planned, DEFAULT_SERVER_DATETIME_FORMAT).weekday() assert purchase_line_id_date_planned == 3, 'Check it has been put on the purchase line also, got %d' %purchase_line_id_date_planned - diff --git a/addons/stock_dropshipping/test/cancellation_propagated.yml b/addons/stock_dropshipping/test/cancellation_propagated.yml index 15af60ebe839..3461a8767615 100644 --- a/addons/stock_dropshipping/test/cancellation_propagated.yml +++ b/addons/stock_dropshipping/test/cancellation_propagated.yml @@ -9,7 +9,7 @@ - Next I create a new product in this warehouse - - !record {model: product.product, id: product_mto}: + !record {model: product.product, id: product_mto, view: False}: name: "My Product" type: product uom_id: product.product_uom_unit diff --git a/addons/stock_dropshipping/test/crossdock.yml b/addons/stock_dropshipping/test/crossdock.yml index 2b284df1365b..10bebfe09885 100644 --- a/addons/stock_dropshipping/test/crossdock.yml +++ b/addons/stock_dropshipping/test/crossdock.yml @@ -11,7 +11,7 @@ - Create new product without any routes. - - !record {model: product.product, id: cross_shop_product}: + !record {model: product.product, id: cross_shop_product, view: False}: name: PCE type: product categ_id: product.product_category_1 diff --git a/addons/stock_dropshipping/test/dropship.yml b/addons/stock_dropshipping/test/dropship.yml index 1f51983c46e1..481435d64b45 100644 --- a/addons/stock_dropshipping/test/dropship.yml +++ b/addons/stock_dropshipping/test/dropship.yml @@ -6,7 +6,7 @@ - Create new product without any routes - - !record {model: product.product, id: drop_shop_product}: + !record {model: product.product, id: drop_shop_product, view: False}: name: Pen drive type: product categ_id: product.product_category_1 diff --git a/addons/stock_dropshipping/test/procurementexception.yml b/addons/stock_dropshipping/test/procurementexception.yml index 83c4467ea430..edf857603d64 100644 --- a/addons/stock_dropshipping/test/procurementexception.yml +++ b/addons/stock_dropshipping/test/procurementexception.yml @@ -35,7 +35,7 @@ - I set the at least one supplier on the product. - - !record {model: product.product, id: product_with_no_seller}: + !record {model: product.product, id: product_with_no_seller, view: False}: seller_ids: - delay: 1 name: base.res_partner_2 -- GitLab