[REF] purchase_requisition: blanket order generate sellerinfo
The blanket order is a contract between you and a supplier on a different price (usually lower than normal) for a big quantity of some specific products. The goal is to generate a supplier info for each product of the blanket order and remove them when the BO is either closed or cancel.
This commit is contained in:
committed by
Pierre Masereel
parent
7fea2292ce
commit
87af05ae76
@@ -50,9 +50,9 @@ class PurchaseRequisition(models.Model):
|
||||
order_count = fields.Integer(compute='_compute_orders_number', string='Number of Orders')
|
||||
vendor_id = fields.Many2one('res.partner', string="Vendor")
|
||||
type_id = fields.Many2one('purchase.requisition.type', string="Agreement Type", required=True, default=_get_type_id)
|
||||
ordering_date = fields.Date(string="Ordering Date")
|
||||
date_end = fields.Datetime(string='Agreement Deadline')
|
||||
schedule_date = fields.Date(string='Delivery Date', index=True, help="The expected and scheduled delivery date where all the products are received")
|
||||
ordering_date = fields.Date(string="Ordering Date", track_visibility='onchange')
|
||||
date_end = fields.Datetime(string='Agreement Deadline', track_visibility='onchange')
|
||||
schedule_date = fields.Date(string='Delivery Date', index=True, help="The expected and scheduled delivery date where all the products are received", track_visibility='onchange')
|
||||
user_id = fields.Many2one('res.users', string='Purchase Representative', default= lambda self: self.env.user)
|
||||
description = fields.Text()
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env['res.company']._company_default_get('purchase.requisition'))
|
||||
@@ -104,6 +104,8 @@ class PurchaseRequisition(models.Model):
|
||||
def action_cancel(self):
|
||||
# try to set all associated quotations to cancel state
|
||||
for requisition in self:
|
||||
for requisition_line in requisition.line_ids:
|
||||
requisition_line.supplier_info_ids.unlink()
|
||||
requisition.purchase_ids.button_cancel()
|
||||
for po in requisition.purchase_ids:
|
||||
po.message_post(body=_('Cancelled by the agreement associated to this quotation.'))
|
||||
@@ -112,7 +114,15 @@ class PurchaseRequisition(models.Model):
|
||||
@api.multi
|
||||
def action_in_progress(self):
|
||||
if not all(obj.line_ids for obj in self):
|
||||
raise UserError(_('You cannot confirm call because there is no product line.'))
|
||||
raise UserError(_('You cannot confirm agreement because there is no product line.'))
|
||||
for requisition in self:
|
||||
if requisition.type_id.quantity_copy == 'none' and requisition.vendor_id:
|
||||
for requisition_line in requisition.line_ids:
|
||||
if requisition_line.price_unit <= 0.0:
|
||||
raise UserError(_('You cannot confirm the blanket order without price.'))
|
||||
if requisition_line.product_qty <= 0.0:
|
||||
raise UserError(_('You cannot confirm the blanket order without quantity.'))
|
||||
requisition_line.create_supplier_info()
|
||||
self.write({'state': 'in_progress'})
|
||||
|
||||
@api.multi
|
||||
@@ -130,6 +140,9 @@ class PurchaseRequisition(models.Model):
|
||||
"""
|
||||
if any(purchase_order.state in ['draft', 'sent', 'to approve'] for purchase_order in self.mapped('purchase_ids')):
|
||||
raise UserError(_('You have to cancel or validate every RfQ before closing the purchase requisition.'))
|
||||
for requisition in self:
|
||||
for requisition_line in requisition.line_ids:
|
||||
requisition_line.supplier_info_ids.unlink()
|
||||
self.write({'state': 'done'})
|
||||
|
||||
def _prepare_tender_values(self, product_id, product_qty, product_uom, location_id, name, origin, values):
|
||||
@@ -146,6 +159,18 @@ class PurchaseRequisition(models.Model):
|
||||
})],
|
||||
}
|
||||
|
||||
def unlink(self):
|
||||
if any(requisition.state not in ('draft', 'cancel') for requisition in self):
|
||||
raise UserError(_('You can only delete draft requisitions.'))
|
||||
# Draft requisitions could have some requisition lines.
|
||||
self.mapped('requisition_line_ids').unlink()
|
||||
return super(PurchaseRequisition, self).unlink()
|
||||
|
||||
class SupplierInfo(models.Model):
|
||||
_inherit = "product.supplierinfo"
|
||||
purchase_requisition_id = fields.Many2one('purchase.requisition', related='purchase_requisition_line_id.requisition_id', string='Blanket order')
|
||||
purchase_requisition_line_id = fields.Many2one('purchase.requisition.line')
|
||||
|
||||
|
||||
class PurchaseRequisitionLine(models.Model):
|
||||
_name = "purchase.requisition.line"
|
||||
@@ -157,11 +182,50 @@ class PurchaseRequisitionLine(models.Model):
|
||||
product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'))
|
||||
price_unit = fields.Float(string='Unit Price', digits=dp.get_precision('Product Price'))
|
||||
qty_ordered = fields.Float(compute='_compute_ordered_qty', string='Ordered Quantities')
|
||||
requisition_id = fields.Many2one('purchase.requisition', string='Purchase Agreement', ondelete='cascade')
|
||||
requisition_id = fields.Many2one('purchase.requisition', required=True, string='Purchase Agreement', ondelete='cascade')
|
||||
company_id = fields.Many2one('res.company', related='requisition_id.company_id', string='Company', store=True, readonly=True, default= lambda self: self.env['res.company']._company_default_get('purchase.requisition.line'))
|
||||
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
|
||||
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.multi
|
||||
def create(self,vals):
|
||||
res = super(PurchaseRequisitionLine, self).create(vals)
|
||||
if res.requisition_id.state not in ['draft', 'cancel', 'done'] and res.requisition_id.is_quantity_copy == 'none':
|
||||
supplier_infos = self.env['product.supplierinfo'].search([
|
||||
('product_id', '=', vals.get('product_id')),
|
||||
('name', '=', res.requisition_id.vendor_id.id),
|
||||
])
|
||||
if not [s.requisition_id for s in supplier_infos]:
|
||||
res.create_supplier_info()
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
res = super(PurchaseRequisitionLine, self).write(vals)
|
||||
if 'price_unit' in vals:
|
||||
# If the price is updated, we have to update the related SupplierInfo
|
||||
self.supplier_info_ids.write({'price': vals['price_unit']})
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
if self.requisition_id.state not in ['draft', 'cancel', 'done']:
|
||||
self.supplier_info_ids.unlink()
|
||||
return super(PurchaseRequisitionLine, self).unlink()
|
||||
|
||||
def create_supplier_info(self):
|
||||
purchase_requisition = self.requisition_id
|
||||
if purchase_requisition.type_id.quantity_copy == 'none' and purchase_requisition.vendor_id:
|
||||
# create a supplier_info only in case of blanket order
|
||||
self.env['product.supplierinfo'].create({
|
||||
'name': purchase_requisition.vendor_id.id,
|
||||
'product_id': self.product_id.id,
|
||||
'product_tmpl_id': self.product_id.product_tmpl_id.id,
|
||||
'price': self.price_unit,
|
||||
'purchase_requisition_id': purchase_requisition.id,
|
||||
'purchase_requisition_line_id': self.id,
|
||||
})
|
||||
|
||||
@api.multi
|
||||
@api.depends('requisition_id.purchase_ids.state')
|
||||
|
||||
@@ -72,3 +72,50 @@ class TestPurchaseRequisition(common.TransactionCase):
|
||||
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):
|
||||
price_product09 = 34
|
||||
price_product13 = 62
|
||||
quantity = 26
|
||||
# Create a pruchase requisition with type blanket order and two product
|
||||
line1 = (0, 0, {'product_id': self.product_09_id, 'product_qty': quantity, 'product_uom_id': self.product_09_uom_id, 'price_unit': price_product09})
|
||||
|
||||
self.product_13_uom_id = self.ref('product.product_uom_unit')
|
||||
line2 = (0, 0, {'product_id': self.product_13_id, 'product_qty': quantity, 'product_uom_id': self.product_13_uom_id, 'price_unit': price_product13})
|
||||
|
||||
requisition_type = self.env['purchase.requisition.type'].create({
|
||||
'name': 'Blanket test',
|
||||
'quantity_copy': 'none'
|
||||
})
|
||||
requisition_blanket = self.env['purchase.requisition'].create({
|
||||
'line_ids': [line1, line2],
|
||||
'type_id': requisition_type.id,
|
||||
'vendor_id': self.res_partner_1_id
|
||||
})
|
||||
|
||||
# confirm the requisition
|
||||
requisition_blanket.action_in_progress()
|
||||
|
||||
# Check for both product that the new supplier info(purchase.requisition.vendor_id) is added to the puchase tab
|
||||
# and check the quantity
|
||||
seller_partner1 = self.env['res.partner'].browse(self.res_partner_1_id)
|
||||
supplierinfo09 = self.env['product.supplierinfo'].search([
|
||||
('name', '=', seller_partner1.id),
|
||||
('product_id', '=', self.product_09_id)
|
||||
])
|
||||
self.assertEqual(supplierinfo09.name, seller_partner1, 'The supplierinfo is not the good one')
|
||||
self.assertEqual(supplierinfo09.price, price_product09, 'The supplierinfo is not the good one')
|
||||
|
||||
supplierinfo13 = self.env['product.supplierinfo'].search([
|
||||
('name', '=', seller_partner1.id),
|
||||
('product_id', '=', self.product_13_id)
|
||||
])
|
||||
self.assertEqual(supplierinfo13.name, seller_partner1, 'The supplierinfo is not the good one')
|
||||
self.assertEqual(supplierinfo13.price, price_product13, 'The supplierinfo is not the good one')
|
||||
|
||||
# Put the requisition in done Status
|
||||
requisition_blanket.action_in_progress()
|
||||
requisition_blanket.action_done()
|
||||
|
||||
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')
|
||||
|
||||
@@ -85,6 +85,19 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="supplier_info_form_inherit">
|
||||
<field name="name">product.supplierinfo.requisition.view</field>
|
||||
<field name="model">product.supplierinfo</field>
|
||||
<field name="inherit_id" ref="product.product_supplierinfo_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="product_code" position="after">
|
||||
<field name="purchase_requisition_id"
|
||||
attrs="{'invisible': [('purchase_requisition_id', '=', False)]}"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="purchase_order_search_inherit">
|
||||
<field name="name">purchase.order.list.select.inherit</field>
|
||||
<field name="model">purchase.order</field>
|
||||
@@ -324,6 +337,17 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_supplierinfo_tree_view_inherit">
|
||||
<field name="name">product.template.product.form.inherit</field>
|
||||
<field name="model">product.supplierinfo</field>
|
||||
<field name="inherit_id" ref="product.product_supplierinfo_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='min_qty']" position="before">
|
||||
<field name="purchase_requisition_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window
|
||||
domain="[('requisition_id', '=', active_id)]"
|
||||
context="{'default_requisition_id': active_id}"
|
||||
|
||||
Reference in New Issue
Block a user