[IMP] stock, sale_stock, mrp, purchase_stock: transfer the custom variant information
Allow to propagate custom information to upper document. It could be usefull in many buisness that do custom stuff for people (t-shirt with user defined text, poster with own pictures...) The customes values are pass throught procurements'values and are display on MO or purchase order line. Task: 1970476
This commit is contained in:
committed by
Simon Lejeune
parent
ffc6fe1704
commit
6f247d799a
@@ -205,6 +205,7 @@ class MrpProduction(models.Model):
|
||||
procurement_group_id = fields.Many2one(
|
||||
'procurement.group', 'Procurement Group',
|
||||
copy=False)
|
||||
product_description_variants = fields.Char('Custom Description')
|
||||
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint')
|
||||
propagate_cancel = fields.Boolean(
|
||||
'Propagate cancel and split',
|
||||
|
||||
@@ -84,6 +84,7 @@ class StockRule(models.Model):
|
||||
return {
|
||||
'origin': origin,
|
||||
'product_id': product_id.id,
|
||||
'product_description_variants': values.get('product_description_variants'),
|
||||
'product_qty': product_qty,
|
||||
'product_uom_id': product_uom.id,
|
||||
'location_src_id': self.location_src_id.id or self.picking_type_id.default_location_src_id.id or location_id.id,
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
<strong>Finished Product:</strong><br/>
|
||||
<span t-field="o.product_id"/>
|
||||
</div>
|
||||
<div class="col-3" t-if="o.product_description_variants">
|
||||
<strong>Description:</strong><br/>
|
||||
<span t-field="o.product_description_variants"/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<strong>Quantity to Produce:</strong><br/>
|
||||
<span t-field="o.product_qty"/>
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
<field name="id" invisible="1"/>
|
||||
<field name="product_id" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="product_tmpl_id" invisible="1"/>
|
||||
<field name="product_description_variants" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<label for="product_qty"/>
|
||||
<div class="o_row no-gutters d-flex">
|
||||
<div class="col">
|
||||
|
||||
@@ -239,6 +239,7 @@ class PurchaseOrderLine(models.Model):
|
||||
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint')
|
||||
move_dest_ids = fields.One2many('stock.move', 'created_purchase_line_id', 'Downstream Moves')
|
||||
delay_alert = fields.Boolean(string='Delay alert')
|
||||
product_description_variants = fields.Char('Custom Description')
|
||||
propagate_date = fields.Boolean(string="Propagate Rescheduling", help='The rescheduling is propagated to the next move.')
|
||||
propagate_date_minimum_delta = fields.Integer(string='Reschedule if Higher Than', help='The change must be higher than this value to be propagated')
|
||||
propagate_cancel = fields.Boolean('Propagate cancellation', default=True)
|
||||
@@ -381,12 +382,14 @@ class PurchaseOrderLine(models.Model):
|
||||
|
||||
def _prepare_stock_move_vals(self, picking, price_unit, product_uom_qty, product_uom):
|
||||
self.ensure_one()
|
||||
description_picking = self.product_id._get_description(self.order_id.picking_type_id)
|
||||
if self.product_description_variants:
|
||||
description_picking += self.product_description_variants
|
||||
return {
|
||||
# truncate to 2000 to avoid triggering index limit error
|
||||
# TODO: remove index in master?
|
||||
'name': (self.name or '')[:2000],
|
||||
'product_id': self.product_id.id,
|
||||
'product_uom': self.product_uom.id,
|
||||
'date': self.order_id.date_order,
|
||||
'date_expected': self.date_planned,
|
||||
'location_id': self.order_id.partner_id.property_stock_supplier.id,
|
||||
@@ -403,7 +406,7 @@ class PurchaseOrderLine(models.Model):
|
||||
'origin': self.order_id.name,
|
||||
'propagate_date': self.propagate_date,
|
||||
'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
|
||||
'description_picking': self.product_id._get_description(self.order_id.picking_type_id),
|
||||
'description_picking': description_picking,
|
||||
'propagate_cancel': self.propagate_cancel,
|
||||
'delay_alert': self.delay_alert,
|
||||
'route_ids': self.order_id.picking_type_id.warehouse_id and [(6, 0, [x.id for x in self.order_id.picking_type_id.warehouse_id.route_ids])] or [],
|
||||
@@ -414,14 +417,19 @@ class PurchaseOrderLine(models.Model):
|
||||
|
||||
@api.model
|
||||
def _prepare_purchase_order_line_from_procurement(self, product_id, product_qty, product_uom, company_id, values, po):
|
||||
line_description = product_id._get_description(po.picking_type_id)
|
||||
if values.get('product_description_variants'):
|
||||
line_description += values['product_description_variants']
|
||||
supplier = values.get('supplier')
|
||||
res = self._prepare_purchase_order_line(product_id, product_qty, product_uom, company_id, supplier, po)
|
||||
res['name'] = line_description
|
||||
res['move_dest_ids'] = [(4, x.id) for x in values.get('move_dest_ids', [])]
|
||||
res['orderpoint_id'] = values.get('orderpoint_id', False) and values.get('orderpoint_id').id
|
||||
res['propagate_cancel'] = values.get('propagate_cancel')
|
||||
res['delay_alert'] = values.get('delay_alert')
|
||||
res['propagate_date'] = values.get('propagate_date')
|
||||
res['propagate_date_minimum_delta'] = values.get('propagate_date_minimum_delta')
|
||||
res['product_description_variants'] = values.get('product_description_variants')
|
||||
return res
|
||||
|
||||
def _create_stock_moves(self, picking):
|
||||
@@ -442,9 +450,13 @@ class PurchaseOrderLine(models.Model):
|
||||
args can be merged. If it returns an empty record then a new line will
|
||||
be created.
|
||||
"""
|
||||
description_picking = product_id._get_description(self.order_id.picking_type_id) or ''
|
||||
if values.get('product_description_variants'):
|
||||
description_picking += values['product_description_variants']
|
||||
lines = self.filtered(
|
||||
lambda l: l.propagate_date == values['propagate_date'] and
|
||||
l.propagate_date_minimum_delta == values['propagate_date_minimum_delta'] and
|
||||
l.propagate_cancel == values['propagate_cancel'] and
|
||||
((values['orderpoint_id'] and not values['move_dest_ids']) and l.orderpoint_id == values['orderpoint_id'] or True))
|
||||
((values['orderpoint_id'] and not values['move_dest_ids']) and l.orderpoint_id == values['orderpoint_id'] or True) and
|
||||
(values.get('product_description_variants') and l.name == description_picking or True))
|
||||
return lines and lines[0] or self.env['purchase.order.line']
|
||||
|
||||
@@ -159,12 +159,14 @@ class StockRule(models.Model):
|
||||
# necessary anymore since those values are taken from destination moves.
|
||||
return procurement.product_id, procurement.product_uom, procurement.values['propagate_date'],\
|
||||
procurement.values['propagate_date_minimum_delta'], procurement.values['propagate_cancel'],\
|
||||
procurement.values.get('product_description_variants'),\
|
||||
(procurement.values.get('orderpoint_id') and not procurement.values.get('move_dest_ids')) and procurement.values['orderpoint_id']
|
||||
|
||||
@api.model
|
||||
def _get_procurements_to_merge_sorted(self, procurement):
|
||||
return procurement.product_id.id, procurement.product_uom.id, procurement.values['propagate_date'],\
|
||||
procurement.values['propagate_date_minimum_delta'], procurement.values['propagate_cancel'],\
|
||||
procurement.values.get('product_description_variants'),\
|
||||
(procurement.values.get('orderpoint_id') and not procurement.values.get('move_dest_ids')) and procurement.values['orderpoint_id']
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -35,6 +35,12 @@ class PurchaseTestCommon(TestStockCommon):
|
||||
'route_ids': [(6, 0, [cls.route_buy, cls.route_mto])],
|
||||
'seller_ids': [(0, 0, {'name': cls.partner_1.id, 'delay': 5})]})
|
||||
|
||||
cls.t_shirt = cls.env['product.product'].create({
|
||||
'name': 'T-shirt',
|
||||
'route_ids': [(6, 0, [cls.route_buy, cls.route_mto])],
|
||||
'seller_ids': [(0, 0, {'name': cls.partner_1.id, 'delay': 5})]
|
||||
})
|
||||
|
||||
# Update product_2 with type, route and Delivery Lead Time
|
||||
cls.product_2.write({
|
||||
'type': 'product',
|
||||
|
||||
@@ -242,6 +242,57 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
|
||||
self.assertEqual(po_line[0].product_qty, 10, 'the purchase order line has a wrong quantity')
|
||||
self.assertEqual(po_line[1].product_qty, 5, 'the purchase order line has a wrong quantity')
|
||||
|
||||
def test_merge_po_line_3(self):
|
||||
"""Chage merging po line if same procurement is done depending on
|
||||
propagate_date, propagate_date_minimum_delta and custom values.
|
||||
"""
|
||||
# Create procurement order of product_1
|
||||
ProcurementGroup = self.env['procurement.group']
|
||||
procurement_values = {
|
||||
'warehouse_id': self.warehouse_1,
|
||||
'rule_id': self.warehouse_1.buy_pull_id,
|
||||
'date_planned': fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10)),
|
||||
'group_id': False,
|
||||
'route_ids': [],
|
||||
}
|
||||
|
||||
procurement_values['product_description_variants'] = '[{"color": "Red"}]'
|
||||
procurement_values['description'] = "T-shirt (Red)"
|
||||
order_1_values = procurement_values
|
||||
ProcurementGroup.run([self.env['procurement.group'].Procurement(
|
||||
self.t_shirt, 5, self.uom_unit, self.warehouse_1.lot_stock_id,
|
||||
self.t_shirt.name, '/', self.env.company, order_1_values)
|
||||
])
|
||||
purchase_order = self.env['purchase.order.line'].search([('product_id', '=', self.t_shirt.id)], limit=1).order_id
|
||||
self.assertEqual(len(purchase_order.order_line), 1, 'wrong number of order line is created')
|
||||
|
||||
procurement_values['product_description_variants'] = '[{"color": "Red"}]'
|
||||
order_2_values = procurement_values
|
||||
ProcurementGroup.run([self.env['procurement.group'].Procurement(
|
||||
self.t_shirt, 10, self.uom_unit, self.warehouse_1.lot_stock_id,
|
||||
self.t_shirt.name, '/', self.env.company, order_2_values)
|
||||
])
|
||||
self.env['procurement.group'].run_scheduler()
|
||||
self.assertEqual(len(purchase_order.order_line), 1, 'line with same custom value should be merged')
|
||||
self.assertEqual(purchase_order.order_line[0].product_qty, 15, 'line with same custom value should be merged and qty should be update')
|
||||
|
||||
procurement_values['product_description_variants'] = '[{"color": "Green"}]'
|
||||
procurement_values['description'] = "T-shirt (Green)"
|
||||
|
||||
order_3_values = procurement_values
|
||||
ProcurementGroup.run([self.env['procurement.group'].Procurement(
|
||||
self.t_shirt, 10, self.uom_unit, self.warehouse_1.lot_stock_id,
|
||||
self.t_shirt.name, '/', self.env.company, order_3_values)
|
||||
])
|
||||
self.assertEqual(len(purchase_order.order_line), 2, 'line with different custom value should not be merged')
|
||||
self.assertEqual(purchase_order.order_line.filtered(lambda x: x.product_qty == 15).name, "T-shirt (Red)", 'wrong description in po lines')
|
||||
self.assertEqual(purchase_order.order_line.filtered(lambda x: x.product_qty == 10).name, "T-shirt (Green)", 'wrong description in po lines')
|
||||
|
||||
purchase_order.button_confirm()
|
||||
|
||||
self.assertEqual(purchase_order.picking_ids[0].move_ids_without_package.filtered(lambda x: x.product_uom_qty == 15).description_picking, "T-shirt (Red)", 'wrong description in picking')
|
||||
self.assertEqual(purchase_order.picking_ids[0].move_ids_without_package.filtered(lambda x: x.product_uom_qty == 10).description_picking, "T-shirt (Green)", 'wrong description in picking')
|
||||
|
||||
def test_reordering_days_to_purchase(self):
|
||||
self.patcher = patch('odoo.addons.stock.models.stock_orderpoint.fields.Date', wraps=fields.Date)
|
||||
self.mock_date = self.patcher.start()
|
||||
|
||||
@@ -445,6 +445,7 @@ class SaleOrderLine(models.Model):
|
||||
'route_ids': self.route_id,
|
||||
'warehouse_id': self.order_id.warehouse_id or False,
|
||||
'partner_id': self.order_id.partner_shipping_id.id,
|
||||
'product_description_variants': self._get_sale_order_line_multiline_description_variants(),
|
||||
'company_id': self.order_id.company_id,
|
||||
})
|
||||
for line in self.filtered("order_id.commitment_date"):
|
||||
|
||||
@@ -691,7 +691,7 @@ class StockMove(models.Model):
|
||||
'product_id', 'price_unit', 'procure_method', 'location_id', 'location_dest_id',
|
||||
'product_uom', 'restrict_partner_id', 'scrapped', 'origin_returned_move_id',
|
||||
'package_level_id', 'propagate_cancel', 'propagate_date', 'propagate_date_minimum_delta',
|
||||
'delay_alert',
|
||||
'delay_alert', 'description_picking'
|
||||
]
|
||||
|
||||
@api.model
|
||||
@@ -701,7 +701,7 @@ class StockMove(models.Model):
|
||||
move.product_id.id, move.price_unit, move.procure_method, move.location_id, move.location_dest_id,
|
||||
move.product_uom.id, move.restrict_partner_id.id, move.scrapped, move.origin_returned_move_id.id,
|
||||
move.package_level_id.id, move.propagate_cancel, move.propagate_date, move.propagate_date_minimum_delta,
|
||||
move.delay_alert,
|
||||
move.delay_alert, move.description_picking
|
||||
]
|
||||
|
||||
def _clean_merged(self):
|
||||
@@ -1040,6 +1040,7 @@ class StockMove(models.Model):
|
||||
elif self.rule_id.group_propagation_option == 'none':
|
||||
group_id = False
|
||||
return {
|
||||
'product_description_variants': self.description_picking and self.description_picking.replace(self.product_id._get_description(self.picking_type_id), ''),
|
||||
'date_planned': self.date_expected,
|
||||
'move_dest_ids': self,
|
||||
'group_id': group_id,
|
||||
|
||||
@@ -285,6 +285,9 @@ class StockRule(models.Model):
|
||||
date_expected = fields.Datetime.to_string(
|
||||
fields.Datetime.from_string(values['date_planned']) - relativedelta(days=self.delay or 0)
|
||||
)
|
||||
picking_description = product_id._get_description(self.picking_type_id)
|
||||
if values.get('product_description_variants'):
|
||||
picking_description += values['product_description_variants']
|
||||
# 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
|
||||
@@ -315,7 +318,7 @@ class StockRule(models.Model):
|
||||
'propagate_cancel': self.propagate_cancel,
|
||||
'propagate_date': self.propagate_date,
|
||||
'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
|
||||
'description_picking': product_id._get_description(self.picking_type_id),
|
||||
'description_picking': picking_description,
|
||||
'priority': values.get('priority', "1"),
|
||||
'delay_alert': self.delay_alert,
|
||||
'orderpoint_id': values.get('orderpoint_id') and values['orderpoint_id'].id,
|
||||
|
||||
Reference in New Issue
Block a user