diff options
| author | amoyaux <arm@odoo.com> | 2017-09-04 15:29:35 +0200 |
|---|---|---|
| committer | amoyaux <arm@odoo.com> | 2017-09-08 17:08:06 +0200 |
| commit | c914df5a369eb49e4e72a698c0811200b5825f22 (patch) | |
| tree | 8e894c0821a0d8d7f8e95e1a4930384b19c6b241 | |
| parent | 283242dbea30f6acac00fb5e6a500f1c29b6ee9f (diff) | |
[IMP] stock,mrp,purchase: split _run method and move it to rules
Previously mrp, stock and purchase used _run methon on procurement
group and start with a if checking for the action type. This commit
instead launch a specific method depending the rule's action's type
Also _run method and their submethod used in procurement group
was always called with a rule. Thus we choose to move this method
on the rule object himself.
| -rw-r--r-- | addons/mrp/models/procurement.py | 84 | ||||
| -rw-r--r-- | addons/procurement_jit/sale.py | 4 | ||||
| -rw-r--r-- | addons/purchase/models/purchase.py | 172 | ||||
| -rw-r--r-- | addons/purchase/tests/common.py | 8 | ||||
| -rw-r--r-- | addons/purchase/tests/test_purchase_lead_time.py | 12 | ||||
| -rw-r--r-- | addons/purchase_requisition/models/purchase_requisition.py | 38 | ||||
| -rw-r--r-- | addons/purchase_requisition/tests/test_purchase_requisition.py | 17 | ||||
| -rw-r--r-- | addons/sale/models/sale.py | 8 | ||||
| -rw-r--r-- | addons/sale_order_dates/models/sale_order.py | 4 | ||||
| -rw-r--r-- | addons/sale_stock/models/sale_order.py | 40 | ||||
| -rw-r--r-- | addons/sale_stock/models/stock.py | 30 | ||||
| -rw-r--r-- | addons/stock/models/procurement.py | 203 | ||||
| -rw-r--r-- | addons/stock/models/stock_move.py | 18 | ||||
| -rw-r--r-- | addons/stock/models/stock_warehouse.py | 11 | ||||
| -rw-r--r-- | addons/stock_dropshipping/models/purchase.py | 8 |
15 files changed, 322 insertions, 335 deletions
diff --git a/addons/mrp/models/procurement.py b/addons/mrp/models/procurement.py index ccf430be369..3bf96934b83 100644 --- a/addons/mrp/models/procurement.py +++ b/addons/mrp/models/procurement.py @@ -11,67 +11,57 @@ class ProcurementRule(models.Model): _inherit = 'procurement.rule' action = fields.Selection(selection_add=[('manufacture', 'Manufacture')]) -class ProcurementGroup(models.Model): - _inherit = 'procurement.group' - @api.multi - def _run(self, values, rule, doraise=True): - if rule.action == 'manufacture': - Production = self.env['mrp.production'] - ProductionSudo = Production.sudo().with_context(force_company=values['company_id'].id) - bom = self._get_matching_bom(values, rule) - if not bom: - msg = _('No Bill of Material found for product %s.') % (values['product_id'].display_name,) - if doraise: - raise UserError(msg) - else: - self.log_next_activity(values['product_id'], msg) - return False + def _run_manufacture(self, product_id, product_qty, product_uom, location_id, name, origin, values): + Production = self.env['mrp.production'] + ProductionSudo = Production.sudo().with_context(force_company=values['company_id'].id) + bom = self._get_matching_bom(product_id, values) + if not bom: + msg = _('No Bill of Material found for product %s.') % (product_id.display_name,) + raise UserError(msg) - # create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example) - production = ProductionSudo.create(self._prepare_mo_vals(values, rule, bom)) - origin_production = values.get('move_dest_ids') and values ['move_dest_ids'][0].raw_material_production_id or False - orderpoint = values.get('orderpoint_id') - if orderpoint: - production.message_post_with_view('mail.message_origin_link', - values={'self': production, 'origin': orderpoint.id}, - subtype_id=self.env.ref('mail.mt_note').id) - if origin_production: - production.message_post_with_view('mail.message_origin_link', - values={'self': production, 'origin': origin_production}, - subtype_id=self.env.ref('mail.mt_note').id) - return True - return super(ProcurementGroup, self)._run(values, rule, doraise) + # create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example) + production = ProductionSudo.create(self._prepare_mo_vals(product_id, product_qty, product_uom, location_id, name, origin, values, bom)) + origin_production = values.get('move_dest_ids') and values['move_dest_ids'][0].raw_material_production_id or False + orderpoint = values.get('orderpoint_id') + if orderpoint: + production.message_post_with_view('mail.message_origin_link', + values={'self': production, 'origin': orderpoint.id}, + subtype_id=self.env.ref('mail.mt_note').id) + if origin_production: + production.message_post_with_view('mail.message_origin_link', + values={'self': production, 'origin': origin_production}, + subtype_id=self.env.ref('mail.mt_note').id) + return True @api.multi - def _get_matching_bom(self, values, rule): + def _get_matching_bom(self, product_id, values): if values.get('bom_id', False): return values['bom_id'] return self.env['mrp.bom'].with_context( company_id=values['company_id'].id, force_company=values['company_id'].id - )._bom_find(product=values['product_id'], picking_type=rule.picking_type_id) # TDE FIXME: context bullshit - - def _get_date_planned(self, values, rule): - format_date_planned = fields.Datetime.from_string(values['date_planned']) - date_planned = format_date_planned - relativedelta(days=values['product_id'].produce_delay or 0.0) - date_planned = date_planned - relativedelta(days=values['company_id'].manufacturing_lead) - return date_planned + )._bom_find(product=product_id, picking_type=self.picking_type_id) # TDE FIXME: context bullshit - def _prepare_mo_vals(self, values, rule, bom): + def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, values, bom): return { - 'origin': values['origin'], - 'product_id': values['product_id'].id, - 'product_qty': values['product_qty'], - 'product_uom_id': values['product_uom'].id, - 'location_src_id': rule.location_src_id.id or values['location_id'].id, - 'location_dest_id': values['location_id'].id, + 'origin': origin, + 'product_id': product_id.id, + 'product_qty': product_qty, + 'product_uom_id': product_uom.id, + 'location_src_id': self.location_src_id.id or location_id.id, + 'location_dest_id': location_id.id, 'bom_id': bom.id, - 'date_planned_start': fields.Datetime.to_string(self._get_date_planned(values, rule)), + 'date_planned_start': fields.Datetime.to_string(self._get_date_planned(product_id, values)), 'date_planned_finished': values['date_planned'], 'procurement_group_id': values.get('group_id').id if values.get('group_id', False) else False, - 'propagate': rule.propagate, - 'picking_type_id': rule.picking_type_id.id or values['warehouse_id'].manu_type_id.id, + 'propagate': self.propagate, + 'picking_type_id': self.picking_type_id.id or values['warehouse_id'].manu_type_id.id, 'company_id': values['company_id'].id, 'move_dest_ids': values.get('move_dest_ids') and [(4, x.id) for x in values['move_dest_ids']] or False, } + def _get_date_planned(self, product_id, values): + format_date_planned = fields.Datetime.from_string(values['date_planned']) + date_planned = format_date_planned - relativedelta(days=product_id.produce_delay or 0.0) + date_planned = date_planned - relativedelta(days=values['company_id'].manufacturing_lead) + return date_planned diff --git a/addons/procurement_jit/sale.py b/addons/procurement_jit/sale.py index a3718f866f8..cf921fd88d8 100644 --- a/addons/procurement_jit/sale.py +++ b/addons/procurement_jit/sale.py @@ -8,8 +8,8 @@ class SaleOrderLine(models.Model): _inherit = "sale.order.line" @api.multi - def _action_procurement_create(self): - res = super(SaleOrderLine, self)._action_procurement_create() + def _action_launch_procurement_rule(self): + res = super(SaleOrderLine, self)._action_launch_procurement_rule() orders = list(set(x.order_id for x in self)) for order in orders: reassign = order.picking_ids.filtered(lambda x: x.state=='confirmed' or ((x.state in ['partially_available', 'waiting']) and not x.printed)) diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 310715e05ed..e40713bac40 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -747,6 +747,12 @@ class PurchaseOrderLine(models.Model): else: return datetime.today() + relativedelta(days=seller.delay if seller else 0) + def _merge_in_existing_line(self, product_id, product_qty, product_uom, location_id, name, origin, values): + """ This function purpose is to be override with the purpose to forbide _run_buy method + to merge a new po line in an existing one. + """ + return True + @api.onchange('product_id') def onchange_product_id(self): result = {} @@ -842,62 +848,62 @@ class PurchaseOrderLine(models.Model): self.product_qty = 1.0 -class ProcurementRule(models.Model): - _inherit = 'procurement.rule' +class ProcurementGroup(models.Model): + _inherit = 'procurement.group' - action = fields.Selection(selection_add=[('buy', 'Buy')]) + @api.model + def _get_exceptions_domain(self): + return super(ProcurementGroup, self)._get_exceptions_domain() + [('created_purchase_line_id', '=', False)] -class ProcurementGroup(models.Model): - _inherit = 'procurement.group' +class ProcurementRule(models.Model): + _inherit = 'procurement.rule' + action = fields.Selection(selection_add=[('buy', 'Buy')]) @api.multi - def _run(self, values, rule, doraise=True): - if rule.action == 'buy': - cache = {} - suppliers = values['product_id'].seller_ids\ - .filtered(lambda r: (not r.company_id or r.company_id == values['company_id']) and (not r.product_id or r.product_id == values['product_id'])) - if not suppliers: - msg = _('No vendor associated to product %s. Unable to generate the purchase order.') % (values['product_id'].display_name,) - if doraise: - raise UserError(msg) - else: - self._log_next_activity(values['product_id'], msg) - return False - supplier = self._make_po_select_supplier(values, rule, suppliers) - partner = supplier.name + def _run_buy(self, product_id, product_qty, product_uom, location_id, name, origin, values): + cache = {} + suppliers = product_id.seller_ids\ + .filtered(lambda r: (not r.company_id or r.company_id == values['company_id']) and (not r.product_id or r.product_id == product_id)) + if not suppliers: + msg = _('No vendor associated to product %s. Unable to generate the purchase order.') % (product_id.display_name,) + raise UserError(msg) - domain = self._make_po_get_domain(values, rule, partner) + supplier = self._make_po_select_supplier(values, suppliers) + partner = supplier.name - if domain in cache: - po = cache[domain] - else: - po = self.env['purchase.order'].search([dom for dom in domain]) - po = po[0] if po else False - cache[domain] = po - if not po: - vals = self._prepare_purchase_order(values, rule, partner) - po = self.env['purchase.order'].create(vals) - cache[domain] = po - elif not po.origin or values['origin'] not in po.origin.split(', '): - if po.origin: - if values['origin']: - po.write({'origin': po.origin + ', ' + values['origin']}) - else: - po.write({'origin': po.origin}) + domain = self._make_po_get_domain(values, partner) + + if domain in cache: + po = cache[domain] + else: + po = self.env['purchase.order'].search([dom for dom in domain]) + po = po[0] if po else False + cache[domain] = po + if not po: + vals = self._prepare_purchase_order(product_id, product_qty, product_uom, origin, values, partner) + po = self.env['purchase.order'].create(vals) + cache[domain] = po + elif not po.origin or origin not in po.origin.split(', '): + if po.origin: + if origin: + po.write({'origin': po.origin + ', ' + origin}) else: - po.write({'origin': values['origin']}) - - # Create Line - po_line = False - for line in po.order_line: - if line.product_id == values['product_id'] and line.product_uom == values['product_id'].uom_po_id: - procurement_uom_po_qty = values['product_uom']._compute_quantity(values['product_qty'], values['product_id'].uom_po_id) - seller = values['product_id']._select_seller( + po.write({'origin': po.origin}) + else: + po.write({'origin': origin}) + + # Create Line + po_line = False + for line in po.order_line: + if line.product_id == product_id and line.product_uom == product_id.uom_po_id: + if line._merge_in_existing_line(product_id, product_qty, product_uom, location_id, name, origin, values): + procurement_uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id) + seller = product_id._select_seller( partner_id=partner, - quantity = line.product_qty + procurement_uom_po_qty, - date = po.date_order and po.date_order[:10], - uom_id=values['product_id'].uom_po_id) + quantity=line.product_qty + procurement_uom_po_qty, + date=po.date_order and po.date_order[:10], + uom_id=product_id.uom_po_id) price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, line.product_id.supplier_taxes_id, line.taxes_id) if seller else 0.0 if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id: @@ -909,50 +915,48 @@ class ProcurementGroup(models.Model): 'move_dest_ids': [(4, x.id) for x in values.get('move_dest_ids', [])] }) break - if not po_line: - vals = self._prepare_purchase_order_line(values, rule, po, supplier) - self.env['purchase.order.line'].create(vals) - return True - return super(ProcurementGroup, self)._run(values, rule, doraise=True) + if not po_line: + vals = self._prepare_purchase_order_line(product_id, product_qty, product_uom, values, po, supplier) + self.env['purchase.order.line'].create(vals) - def _get_purchase_schedule_date(self, values, rule): + def _get_purchase_schedule_date(self, values): """Return the datetime value to use as Schedule Date (``date_planned``) for the Purchase Order Lines created to satisfy the given procurement. """ - procurement_date_planned = datetime.strptime(values['date_planned'], DEFAULT_SERVER_DATETIME_FORMAT) + procurement_date_planned = fields.Datetime.from_string(values['date_planned']) schedule_date = (procurement_date_planned - relativedelta(days=values['company_id'].po_lead)) return schedule_date - def _get_purchase_order_date(self, values, rule, partner, schedule_date): + def _get_purchase_order_date(self, product_id, product_qty, product_uom, values, partner, schedule_date): """Return the datetime value to use as Order Date (``date_order``) for the Purchase Order created to satisfy the given procurement. """ - seller = values['product_id']._select_seller( + seller = product_id._select_seller( partner_id=partner, - quantity=values['product_qty'], + quantity=product_qty, date=fields.Date.to_string(schedule_date), - uom_id=values['product_uom']) + uom_id=product_uom) return schedule_date - relativedelta(days=int(seller.delay)) - @api.model - def _prepare_purchase_order_line(self, values, rule, po, supplier): - procurement_uom_po_qty = values['product_uom']._compute_quantity(values['product_qty'], values['product_id'].uom_po_id) - seller = values['product_id']._select_seller( + @api.multi + def _prepare_purchase_order_line(self, product_id, product_qty, product_uom, values, po, supplier): + procurement_uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id) + seller = product_id._select_seller( partner_id=supplier.name, quantity=procurement_uom_po_qty, date=po.date_order and po.date_order[:10], - uom_id=values['product_id'].uom_po_id) + uom_id=product_id.uom_po_id) - taxes = values['product_id'].supplier_taxes_id + taxes = product_id.supplier_taxes_id fpos = po.fiscal_position_id taxes_id = fpos.map_tax(taxes) if fpos else taxes if taxes_id: taxes_id = taxes_id.filtered(lambda x: x.company_id.id == values['company_id'].id) - price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, values['product_id'].supplier_taxes_id, taxes_id) if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, product_id.supplier_taxes_id, taxes_id) 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) - product_lang = values['product_id'].with_context({ + product_lang = product_id.with_context({ 'lang': supplier.name.lang, 'partner_id': supplier.name.id, }) @@ -965,8 +969,8 @@ class ProcurementGroup(models.Model): return { 'name': name, 'product_qty': procurement_uom_po_qty, - 'product_id': values['product_id'].id, - 'product_uom': values['product_id'].uom_po_id.id, + 'product_id': product_id.id, + 'product_uom': product_id.uom_po_id.id, 'price_unit': price_unit, 'date_planned': date_planned, 'orderpoint_id': values.get('orderpoint_id', False) and values.get('orderpoint_id').id, @@ -975,56 +979,50 @@ class ProcurementGroup(models.Model): 'move_dest_ids': [(4, x.id) for x in values.get('move_dest_ids', [])], } - def _prepare_purchase_order(self, values, rule, partner): - schedule_date = self._get_purchase_schedule_date(values, rule) - purchase_date = self._get_purchase_order_date(values, rule, partner, schedule_date) + def _prepare_purchase_order(self, product_id, product_qty, product_uom, origin, values, partner): + schedule_date = self._get_purchase_schedule_date(values) + purchase_date = self._get_purchase_order_date(product_id, product_qty, product_uom, values, partner, schedule_date) fpos = self.env['account.fiscal.position'].with_context(company_id=values['company_id'].id).get_fiscal_position(partner.id) - gpo = rule.group_propagation_option - group = (gpo == 'fixed' and rule.group_id.id) or \ + gpo = self.group_propagation_option + group = (gpo == 'fixed' and self.group_id.id) or \ (gpo == 'propagate' and values['group_id'].id) or False return { 'partner_id': partner.id, - 'picking_type_id': rule.picking_type_id.id, + 'picking_type_id': self.picking_type_id.id, 'company_id': values['company_id'].id, 'currency_id': partner.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id, 'dest_address_id': values.get('partner_dest_id', False) and values['partner_dest_id'].id, - 'origin': values['origin'], + 'origin': origin, 'payment_term_id': partner.property_supplier_payment_term_id.id, 'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'fiscal_position_id': fpos, 'group_id': group } - def _make_po_select_supplier(self, values, rule, suppliers): + def _make_po_select_supplier(self, values, suppliers): """ Method intended to be overridden by customized modules to implement any logic in the selection of supplier. """ return suppliers[0] - def _make_po_get_domain(self, values, rule, partner): - domain = super(ProcurementGroup, self)._make_po_get_domain(values, partner) - gpo = rule.group_propagation_option - group = (gpo == 'fixed' and rule.group_id) or \ + def _make_po_get_domain(self, values, partner): + domain = super(ProcurementRule, self)._make_po_get_domain(values, partner) + gpo = self.group_propagation_option + group = (gpo == 'fixed' and self.group_id) or \ (gpo == 'propagate' and values['group_id']) or False domain += ( ('partner_id', '=', partner.id), ('state', '=', 'draft'), - ('picking_type_id', '=', rule.picking_type_id.id), + ('picking_type_id', '=', self.picking_type_id.id), ('company_id', '=', values['company_id'].id), ) if group: domain += (('group_id', '=', group.id),) - if values.get('date_planned', False): - domain += (('date_planned', '=', values.get('date_planned')),) return domain - @api.model - def _get_exceptions_domain(self): - return super(ProcurementGroup, self)._get_exceptions_domain() + [('created_purchase_line_id', '=', False)] - class ProductTemplate(models.Model): _name = 'product.template' diff --git a/addons/purchase/tests/common.py b/addons/purchase/tests/common.py index fee4813f344..1e1b290377a 100644 --- a/addons/purchase/tests/common.py +++ b/addons/purchase/tests/common.py @@ -10,17 +10,11 @@ class TestPurchase(TestStockCommon): def _create_make_procurement(self, product, product_qty, date_planned=False): ProcurementGroup = self.env['procurement.group'] order_values = { - 'name': product.name, - 'product_id': product, - 'product_qty': product_qty, - 'product_uom': self.uom_unit, 'warehouse_id': self.warehouse_1, 'date_planned': date_planned or fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10)), # 10 days added to current date of procurement to get future schedule date and order date of purchase order. - 'location_id': self.warehouse_1.lot_stock_id, - 'origin': '/', 'group_id': self.env['procurement.group'], } - return ProcurementGroup.run(order_values) + return ProcurementGroup.run(product, product_qty, self.uom_unit, self.warehouse_1.lot_stock_id, product.name, '/', order_values) @classmethod def setUpClass(cls): diff --git a/addons/purchase/tests/test_purchase_lead_time.py b/addons/purchase/tests/test_purchase_lead_time.py index df5486513ea..834d40bf82e 100644 --- a/addons/purchase/tests/test_purchase_lead_time.py +++ b/addons/purchase/tests/test_purchase_lead_time.py @@ -104,22 +104,16 @@ class TestPurchaseLeadTime(TestPurchase): date_planned = fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10)) # Create procurement order of product_1 - self.env['procurement.group'].run({ - 'product_id': self.product_1, - 'product_qty': 5.000, - 'name': 'Test scheduler for RFQ', - 'product_uom': self.uom_unit, + self.env['procurement.group'].run(self.product_1, 5.000, self.uom_unit, self.warehouse_1.lot_stock_id, 'Test scheduler for RFQ', '/', { 'warehouse_id': self.warehouse_1, - 'location_id': self.warehouse_1.lot_stock_id, 'date_planned': date_planned, # 10 days added to current date of procurement to get future schedule date and order date of purchase order. 'rule_id': self.warehouse_1.buy_pull_id, - 'origin': '/', - 'group_id': False, #self.env['procurement.group'], + 'group_id': False, 'route_ids': [], }) # Confirm purchase order - + purchase = self.env['purchase.order.line'].search([('product_id', '=', self.product_1.id)], limit=1).order_id purchase.button_confirm() diff --git a/addons/purchase_requisition/models/purchase_requisition.py b/addons/purchase_requisition/models/purchase_requisition.py index c0c440954a8..b2c1df32fb3 100644 --- a/addons/purchase_requisition/models/purchase_requisition.py +++ b/addons/purchase_requisition/models/purchase_requisition.py @@ -105,6 +105,20 @@ class PurchaseRequisition(models.Model): raise UserError(_('You have to cancel or validate every RfQ before closing the purchase requisition.')) self.write({'state': 'done'}) + def _prepare_tender_values(self, product_id, product_qty, product_uom, location_id, name, origin, values): + return{ + 'origin': origin, + 'date_end': values['date_planned'], + 'warehouse_id': values.get('warehouse_id') and values['warehouse_id'].id or False, + 'company_id': values['company_id'].id, + 'line_ids': [(0, 0, { + '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, + })], + } + class PurchaseRequisitionLine(models.Model): _name = "purchase.requisition.line" @@ -296,24 +310,14 @@ class ProductTemplate(models.Model): string='Procurement', default='rfq') -class ProcurementGroup(models.Model): - _inherit = 'procurement.group' +class ProcurementRule(models.Model): + _inherit = 'procurement.rule' @api.multi - def _run_buy(self, product_id, product_qty, product_uom, location_id, name, origin, values, log_activites=False): + def _run_buy(self, product_id, product_qty, product_uom, location_id, name, origin, values): if product_id.purchase_requisition != 'tenders': - return super(ProcurementGroup, self)._run_buy(product_id, product_qty, product_uom, location_id, name, origin, values, log_activites) - self.env['purchase.requisition'].create({ - 'origin': origin, - 'date_end': values['date_planned'], - 'warehouse_id': values.get('warehouse_id') and values['warehouse_id'].id or False, - 'company_id': values['company_id'].id, - 'picking_type_id': self.picking_type_id.id, - 'line_ids': [(0, 0, { - '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, - })], - }) + return super(ProcurementRule, self)._run_buy(product_id, product_qty, product_uom, location_id, name, origin, values) + values = self.env['purchase.requisition']._prepare_tender_values(product_id, product_qty, product_uom, location_id, name, origin, values) + values['picking_type_id'] = self.picking_type_id.id + self.env['purchase.requisition'].create(values) return True diff --git a/addons/purchase_requisition/tests/test_purchase_requisition.py b/addons/purchase_requisition/tests/test_purchase_requisition.py index 9ba09ca390d..1ddbed67bf8 100644 --- a/addons/purchase_requisition/tests/test_purchase_requisition.py +++ b/addons/purchase_requisition/tests/test_purchase_requisition.py @@ -2,7 +2,6 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests import common -from odoo.tools import float_compare from odoo import fields @@ -48,16 +47,12 @@ class TestPurchaseRequisition(common.TransactionCase): 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.route_warehouse0_buy'))]}) - self.env['procurement.group'].run({'product_id': product, - 'product_qty': 14, - 'product_uom': self.env['product.uom'].browse(self.ref('product.product_uom_unit')), - 'warehouse_id': warehouse, - 'location_id': warehouse.lot_stock_id, - 'name': '/', - 'origin': '/', - 'date_planned': date_planned, - }) - + self.env['procurement.group'].run(product, 14, self.env['product.uom'].browse(self.ref('product.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 diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 13d2effa460..ce96e5fa8b4 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -917,6 +917,14 @@ class SaleOrderLine(models.Model): return invoice_lines @api.multi + def _prepare_procurement_values(self, group_id=False): + """ Prepare specific key for moves or other components that will be created from a procurement rule + comming from a sale order line. This method could be override in order to add other custom key that could + be used in move/po creation. + """ + return {} + + @api.multi def _get_display_price(self, product): # TO DO: move me in master/saas-16 on sale.order if self.order_id.pricelist_id.discount_policy == 'with_discount': diff --git a/addons/sale_order_dates/models/sale_order.py b/addons/sale_order_dates/models/sale_order.py index 7fdabc76590..0a01683040d 100644 --- a/addons/sale_order_dates/models/sale_order.py +++ b/addons/sale_order_dates/models/sale_order.py @@ -66,8 +66,8 @@ class SaleOrderLine(models.Model): _inherit = 'sale.order.line' @api.multi - def _prepare_order_line_procurement(self, group_id): - vals = super(SaleOrderLine, self)._prepare_order_line_procurement(group_id=group_id) + def _prepare_procurement_values(self, group_id): + vals = super(SaleOrderLine, self)._prepare_procurement_values(group_id=group_id) for line in self.filtered("order_id.requested_date"): date_planned = fields.Datetime.from_string(line.order_id.requested_date) - timedelta(days=line.order_id.company_id.security_lead) vals.update({ diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index 701ff26261f..38ad7ff77a1 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -37,7 +37,7 @@ class SaleOrder(models.Model): def action_confirm(self): result = super(SaleOrder, self).action_confirm() for order in self: - order.order_line._action_procurement_create() + order.order_line._action_launch_procurement_rule() return result @api.depends('picking_ids') @@ -89,7 +89,7 @@ class SaleOrderLine(models.Model): def create(self, values): line = super(SaleOrderLine, self).create(values) if line.state == 'sale': - line._action_procurement_create() + line._action_launch_procurement_rule() return line @api.multi @@ -101,7 +101,7 @@ class SaleOrderLine(models.Model): lambda r: r.state == 'sale' and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1) res = super(SaleOrderLine, self).write(values) if lines: - lines._action_procurement_create() + lines._action_launch_procurement_rule() return res @@ -186,31 +186,32 @@ class SaleOrderLine(models.Model): return {} @api.multi - def _prepare_order_line_procurement(self, group_id=False): + def _prepare_procurement_values(self, group_id=False): + """ Prepare specific key for moves or other components that will be created from a procurement rule + comming from a sale order line. This method could be override in order to add other custom key that could + be used in move/po creation. + """ + values = super(SaleOrderLine, self)._prepare_procurement_values(group_id) self.ensure_one() date_planned = datetime.strptime(self.order_id.confirmation_date, DEFAULT_SERVER_DATETIME_FORMAT)\ + timedelta(days=self.customer_lead or 0.0) - timedelta(days=self.order_id.company_id.security_lead) - return { - 'name': self.name, - 'origin': self.order_id.name, - 'product_id': self.product_id, - 'product_qty': self.product_uom_qty, - 'product_uom': self.product_uom, + values.update({ 'company_id': self.order_id.company_id, 'group_id': group_id, 'sale_line_id': self.id, 'date_planned': date_planned.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'location_id': self.order_id.partner_shipping_id.property_stock_customer, 'route_ids': self.route_id, 'warehouse_id': self.order_id.warehouse_id or False, 'partner_dest_id': self.order_id.partner_shipping_id - } + }) + return values @api.multi - def _action_procurement_create(self): + def _action_launch_procurement_rule(self): """ - Create procurements based on quantity ordered. If the quantity is increased, new - procurements are created. If the quantity is decreased, no automated action is taken. + Launch procurement group run method with required/custom fields genrated by a + sale order line. procurement group will launch '_run_move', '_run_buy' or '_run_manufacture' + depending on the sale order line product rule. """ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') for line in self: @@ -223,17 +224,16 @@ class SaleOrderLine(models.Model): continue if not line.order_id.procurement_group_id: - line.order_id.procurement_group_id = self.env["procurement.group"].create({ + line.order_id.procurement_group_id = self.env['procurement.group'].create({ 'name': line.order_id.name, 'move_type': line.order_id.picking_policy, 'sale_id': line.order_id.id }) - vals = line._prepare_order_line_procurement(group_id=line.order_id.procurement_group_id) - vals['product_qty'] = line.product_uom_qty - qty + values = line._prepare_procurement_values(group_id=line.order_id.procurement_group_id) + product_qty = line.product_uom_qty - qty - new_proc = self.env["procurement.group"].run(vals) + self.env['procurement.group'].run(line.product_id, product_qty, line.product_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, values) return True - @api.multi def _get_delivered_qty(self): self.ensure_one() diff --git a/addons/sale_stock/models/stock.py b/addons/sale_stock/models/stock.py index b2ee51e4d5a..4843858a364 100644 --- a/addons/sale_stock/models/stock.py +++ b/addons/sale_stock/models/stock.py @@ -20,17 +20,6 @@ class StockMove(models.Model): line.qty_delivered = line._get_delivered_qty() return result -class ProcurementGroup(models.Model): - _inherit = 'procurement.group' - - sale_id = fields.Many2one('sale.order', 'Sale Order') - - def _get_stock_move_values(self, values, rule, group_id): - result = super(ProcurementGroup, self)._get_stock_move_values(values, rule, group_id) - if values.get('sale_line_id', False): - result['sale_line_id'] = values['sale_line_id'] - return result - @api.multi def write(self, vals): res = super(StockMove, self).write(vals) @@ -43,10 +32,25 @@ class ProcurementGroup(models.Model): return res +class ProcurementGroup(models.Model): + _inherit = 'procurement.group' + + sale_id = fields.Many2one('sale.order', 'Sale Order') + + +class ProcurementRule(models.Model): + _inherit = 'procurement.rule' + + def _get_stock_move_values(self, product_id, product_qty, product_uom, location_id, name, origin, values, group_id): + result = super(ProcurementRule, self)._get_stock_move_values(product_id, product_qty, product_uom, location_id, name, origin, values, group_id) + if values.get('sale_line_id', False): + result['sale_line_id'] = values['sale_line_id'] + return result + + class StockPicking(models.Model): _inherit = 'stock.picking' - sale_id = fields.Many2one(related="group_id.sale_id", string="Sales Order", store=True) @api.multi @@ -59,4 +63,4 @@ class StockPicking(models.Model): 'mail.message_origin_link', values={'self': backorder, 'origin': backorder.sale_id}, subtype_id=self.env.ref('mail.mt_note').id) - return res
\ No newline at end of file + return res diff --git a/addons/stock/models/procurement.py b/addons/stock/models/procurement.py index 09b5b49d89e..57f969d8542 100644 --- a/addons/stock/models/procurement.py +++ b/addons/stock/models/procurement.py @@ -62,6 +62,82 @@ class ProcurementRule(models.Model): 'stock.warehouse', 'Warehouse to Propagate', help="The warehouse to propagate on the created move/procurement, which can be different of the warehouse this rule is for (e.g for resupplying rules from another warehouse)") + @api.multi + def _run_move(self, product_id, product_qty, product_uom, location_id, name, origin, values): + if not self.location_src_id: + msg = _('No source location defined on procurement rule: %s!') % (self.name, ) + raise UserError(msg) + + # create the move as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example) + # Search if picking with move for it exists already: + group_id = False + if self.group_propagation_option == 'propagate': + group_id = values.get('group_id', False) and values['group_id'].id + elif self.group_propagation_option == 'fixed': + group_id = self.group_id.id + + data = self._get_stock_move_values(product_id, product_qty, product_uom, location_id, name, origin, values, group_id) + # Since action_confirm launch following procurement_group we should activate it. + move = self.env['stock.move'].sudo().create(data) + move.assign_picking() + move.picking_id.action_confirm() + return True + + def _get_stock_move_values(self, product_id, product_qty, product_uom, location_id, name, origin, values, group_id): + ''' Returns a dictionary of values that will be used to create a stock move from a procurement. + This function assumes that the given procurement has a rule (action == 'move') set on it. + + :param procurement: browse record + :rtype: dictionary + ''' + date_expected = (datetime.strptime(values['date_planned'], DEFAULT_SERVER_DATETIME_FORMAT) - relativedelta(days=self.delay or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT) + # it is possible that we've already got some move done, so check for the done qty and create + # a new move with the correct qty + qty_left = product_qty + return { + 'name': name[:2000], + 'company_id': self.company_id.id or self.location_src_id.company_id.id or self.location_id.company_id.id or values['company_id'].id, + 'product_id': product_id.id, + 'product_uom': product_uom.id, + 'product_uom_qty': qty_left, + 'partner_id': self.partner_address_id.id or (values.get('group_id', False) and values['group_id'].partner_id.id) or False, + 'location_id': self.location_src_id.id, + 'location_dest_id': location_id.id, + 'move_dest_ids': values.get('move_dest_ids', False) and [(4, x.id) for x in values['move_dest_ids']] or [], + 'rule_id': self.id, + 'procure_method': self.procure_method, + 'origin': origin, + 'picking_type_id': self.picking_type_id.id, + 'group_id': group_id, + 'route_ids': [(4, route.id) for route in values.get('route_ids', [])], + 'warehouse_id': self.propagate_warehouse_id.id or self.warehouse_id.id, + 'date': date_expected, + 'date_expected': date_expected, + 'propagate': self.propagate, + 'priority': values.get('priority', "1"), + } + + def _log_next_activity(self, product_id, note): + existing_activity = self.env['mail.activity'].search([('res_id', '=', product_id.product_tmpl_id.id), ('res_model_id', '=', self.env.ref('product.model_product_template').id), + ('note', '=', note)]) + if not existing_activity: + # If the user deleted todo activity type. + try: + activity_type_id = self.env.ref('mail.mail_activity_data_todo').id + except: + activity_type_id = False + self.env['mail.activity'].create({ + 'activity_type_id': activity_type_id, + 'note': note, + 'user_id': product_id.responsible_id.id, + 'res_id': product_id.product_tmpl_id.id, + 'res_model_id': self.env.ref('product.model_product_template').id, + }) + + def _make_po_get_domain(self, values, partner): + return () + + class ProcurementGroup(models.Model): """ The procurement group class is used to group products together @@ -99,44 +175,21 @@ class ProcurementGroup(models.Model): ('one', 'All at once')], string='Delivery Type', default='direct', required=True) - def _make_po_get_domain(self, values, partner): - return () - - def _log_next_activity(self, product_id, note): - existing_activity = self.env['mail.activity'].search([('res_id', '=', product_id.product_tmpl_id.id), ('res_model_id', '=', self.env.ref('product.model_product_template').id), - ('note', '=', note)]) - if not existing_activity: - self.env['mail.activity'].create({ - 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, - 'note': note, - 'user_id': product_id.responsible_id.id, - 'res_id': product_id.product_tmpl_id.id, - 'res_model_id': self.env.ref('product.model_product_template').id, - }) - @api.model - def run(self, values, doraise=True): - for key in ('product_id', 'product_uom', 'product_qty', 'location_id', 'name', 'origin'): - assert key in values + def run(self, product_id, product_qty, product_uom, location_id, name, origin, values): values.setdefault('company_id', self.env['res.company']._company_default_get('procurement.group')) values.setdefault('priority', '1') values.setdefault('date_planned', fields.Datetime.now()) - rule = self._get_rule(values) + rule = self._get_rule(product_id, location_id, values) if not rule: - if doraise: - raise UserError(_('No procurement rule found.')) - else: - msg = _('No procurement rule found. Update the inventory tab of the product form.') - if values.get('orderpoint_id', False): - msg = _('No procurement rule found for orderpoint %s. Update the inventory tab of the product form.') % (values['orderpoint_id'].name,) - self._log_next_activity(values['product_id'], msg) - return False - self._run(values, rule, doraise) + raise UserError(_('No procurement rule found.')) + + getattr(rule, '_run_%s' % rule.action)(product_id, product_qty, product_uom, location_id, name, origin, values) return True @api.model - def _search_rule(self, values, domain): + def _search_rule(self, product_id, values, domain): """ First find a rule among the ones defined on the procurement group; then try on the routes defined for the product; finally fallback on the default behavior """ @@ -147,7 +200,7 @@ class ProcurementGroup(models.Model): if values.get('route_ids', False): res = Pull.search(expression.AND([[('route_id', 'in', values['route_ids'].ids)], domain]), order='route_sequence, sequence', limit=1) if not res: - product_routes = values['product_id'].route_ids | values['product_id'].categ_id.total_route_ids + product_routes = product_id.route_ids | product_id.categ_id.total_route_ids if product_routes: res = Pull.search(expression.AND([[('route_id', 'in', product_routes.ids)], domain]), order='route_sequence, sequence', limit=1) if not res: @@ -157,51 +210,17 @@ class ProcurementGroup(models.Model): return res @api.model - def _get_rule(self, values): + def _get_rule(self, product_id, location_id, values): result = False - location = values['location_id'] + location = location_id while (not result) and location: - result = self._search_rule(values, [('location_id', '=', location.id)]) + result = self._search_rule(product_id, values, [('location_id', '=', location.id)]) location = location.location_id return result - def _get_stock_move_values(self, values, rule, group_id): - ''' Returns a dictionary of values that will be used to create a stock move from a procurement. - This function assumes that the given procurement has a rule (action == 'move') set on it. - - :param procurement: browse record - :rtype: dictionary - ''' - date_expected = (datetime.strptime(values['date_planned'], DEFAULT_SERVER_DATETIME_FORMAT) - relativedelta(days=rule.delay or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT) - # it is possible that we've already got some move done, so check for the done qty and create - # a new move with the correct qty - qty_left = values['product_qty'] - return { - 'name': values['name'][:2000], - 'company_id': rule.company_id.id or rule.location_src_id.company_id.id or rule.location_id.company_id.id or values['company_id'].id, - 'product_id': values['product_id'].id, - 'product_uom': values['product_uom'].id, - 'product_uom_qty': qty_left, - 'partner_id': rule.partner_address_id.id or (values['group_id'] and values['group_id'].partner_id.id) or False, - 'location_id': rule.location_src_id.id, - 'location_dest_id': values['location_id'].id, - 'move_dest_ids': values.get('move_dest_ids', False) and [(4, x.id) for x in values['move_dest_ids']] or [], - 'rule_id': rule.id, - 'procure_method': rule.procure_method, - 'origin': values['origin'], - 'picking_type_id': rule.picking_type_id.id, - 'group_id': group_id, - 'route_ids': [(4, route.id) for route in values.get('route_ids', [])], - 'warehouse_id': rule.propagate_warehouse_id.id or rule.warehouse_id.id, - 'date': date_expected, - 'date_expected': date_expected, - 'propagate': rule.propagate, - 'priority': values.get('priority', "1"), - } - def _merge_domain(self, values, rule, group_id): return [ - ('group_id', '=', group_id), #extra logic? + ('group_id', '=', group_id), # extra logic? ('location_id', '=', rule.location_src_id.id), ('location_dest_id', '=', values['location_id'].id), ('picking_type_id', '=', rule.picking_type_id.id), @@ -210,33 +229,6 @@ class ProcurementGroup(models.Model): ('picking_id.backorder_id', '=', False), ('product_id', '=', values['product_id'].id)] - @api.multi - def _run(self, values, rule, doraise=True): - if rule.action == 'move': - if not rule.location_src_id: - msg = _('No source location defined on procurement rule: %s!') % (rule.name, ) - if doraise: - raise UserError(msg) - else: - self._log_next_activity(values['product_id'], msg) - return False - - # create the move as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example) - # Search if picking with move for it exists already: - group_id = False - if rule.group_propagation_option == 'propagate': - group_id = values.get('group_id', False) and values['group_id'].id - elif rule.group_propagation_option == 'fixed': - group_id = rule.group_id.id - - data = self._get_stock_move_values(values, rule, group_id) - # Since action_confirm launch following procurement_group we should activate it. - move = self.env['stock.move'].sudo().create(data) - move.assign_picking() - move.picking_id.action_confirm() - return True - return False - @api.model def _get_exceptions_domain(self): return [('procure_method', '=', 'make_to_order'), ('move_orig_ids', '=', False)] @@ -260,10 +252,17 @@ class ProcurementGroup(models.Model): self.env['stock.move'].browse(moves_chunk).action_assign() if use_new_cursor: self._cr.commit() - + exception_moves = self.env['stock.move'].search(self._get_exceptions_domain()) for move in exception_moves: - self.env['procurement.group'].run(move._prepare_procurement_from_move(), doraise=False) + values = move._prepare_procurement_values() + try: + with self._cr.savepoint(): + origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or move.origin or move.picking_id.name or "/") + self.run(move.product_id, move.product_uom_qty, move.product_uom, move.location_id, move.rule_id and move.rule_id.name or "/", origin, values) + except UserError as error: + self.env['procurement.rule']._log_next_activity(move.product_id, error.name) + if use_new_cursor: self._cr.commit() finally: @@ -355,8 +354,13 @@ class ProcurementGroup(models.Model): qty -= substract_quantity[orderpoint.id] qty_rounded = float_round(qty, precision_rounding=orderpoint.product_uom.rounding) if qty_rounded > 0: - self.env['procurement.group'].run( - orderpoint._prepare_procurement_values(qty_rounded, **group['procurement_values']), doraise=False) + values = orderpoint._prepare_procurement_values(qty_rounded, **group['procurement_values']) + try: + with self._cr.savepoint(): + self.env['procurement.group'].run(orderpoint.product_id, qty_rounded, orderpoint.product_uom, orderpoint.location_id, + orderpoint.name, orderpoint.name, values) + except UserError as error: + self.env['procurement.rule']._log_next_activity(orderpoint.product_id, error.name) self._procurement_from_orderpoint_post_process([orderpoint.id]) if use_new_cursor: cr.commit() @@ -384,4 +388,3 @@ class ProcurementGroup(models.Model): cr.close() return {} - diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 7e938d2e7bd..d631e421375 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -589,7 +589,10 @@ class StockMove(models.Model): # create procurements for make to order moves for move in move_create_proc: - self.env['procurement.group'].run(move._prepare_procurement_from_move()) + values = move._prepare_procurement_values() + origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or move.origin or move.picking_id.name or "/") + self.env['procurement.group'].run(move.product_id, move.product_uom_qty, move.product_uom, move.location_id, move.rule_id and move.rule_id.name or "/", origin, + values) move_to_confirm.write({'state': 'confirmed'}) (move_waiting | move_create_proc).write({'state': 'waiting'}) @@ -600,9 +603,12 @@ class StockMove(models.Model): self._push_apply() return self - def _prepare_procurement_from_move(self): + def _prepare_procurement_values(self): + """ Prepare specific key for moves or other componenets that will be created from a procurement rule + comming from a stock move. This method could be override in order to add other custom key that could + be used in move/po creation. + """ self.ensure_one() - origin = (self.group_id and (self.group_id.name + ":") or "") + (self.rule_id and self.rule_id.name or self.origin or self.picking_id.name or "/") group_id = self.group_id or False if self.rule_id: if self.rule_id.group_propagation_option == 'fixed' and self.rule_id.group_id: @@ -610,14 +616,8 @@ class StockMove(models.Model): elif self.rule_id.group_propagation_option == 'none': group_id = False return { - 'name': self.rule_id and self.rule_id.name or "/", - 'origin': origin, 'company_id': self.company_id, 'date_planned': self.date, - 'product_id': self.product_id, - 'product_qty': self.product_uom_qty, - 'product_uom': self.product_uom, - 'location_id': self.location_id, 'move_dest_ids': self, 'group_id': group_id, 'route_ids': self.route_ids, diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py index 740ac533921..b0e407f3e23 100644 --- a/addons/stock/models/stock_warehouse.py +++ b/addons/stock/models/stock_warehouse.py @@ -824,15 +824,12 @@ class Orderpoint(models.Model): @api.multi def _prepare_procurement_values(self, product_qty, date=False, group=False): + """ Prepare specific key for moves or other components that will be created from a procurement rule + comming from an orderpoint. This method could be override in order to add other custom key that could + be used in move/po creation. + """ return { - 'name': self.name, 'date_planned': date or self._get_date_planned(product_qty, datetime.today()), - 'product_id': self.product_id, - 'product_qty': product_qty, - 'company_id': self.company_id, - 'product_uom': self.product_uom, - 'location_id': self.location_id, - 'origin': self.name, 'warehouse_id': self.warehouse_id, 'orderpoint_id': self, 'group_id': group or self.group_id, diff --git a/addons/stock_dropshipping/models/purchase.py b/addons/stock_dropshipping/models/purchase.py index b1b54fa0209..2a3e32d1269 100644 --- a/addons/stock_dropshipping/models/purchase.py +++ b/addons/stock_dropshipping/models/purchase.py @@ -17,12 +17,12 @@ class PurchaseOrderLine(models.Model): return res -class ProcurementGroup(models.Model): - _inherit = 'procurement.group' +class ProcurementRule(models.Model): + _inherit = 'procurement.rule' @api.model - def _prepare_purchase_order_line(self, values, rule, po, supplier): - res = super(ProcurementGroup, self)._prepare_purchase_order_line(values, rule, po, supplier) + def _prepare_purchase_order_line(self, product_id, product_qty, product_uom, values, po, supplier): + res = super(ProcurementRule, self)._prepare_purchase_order_line(product_id, product_qty, product_uom, values, po, supplier) res['sale_line_id'] = values.get('sale_line_id', False) return res |
