Skip to content
Snippets Groups Projects
Commit 54a22b4f authored by Nicolas Martinelli's avatar Nicolas Martinelli
Browse files

[IMP] sale_mrp: adaptation due to the new Sale module

Major changes:
- No use of UoS anymore
- Manage delivery of kits

Reason: complete rewrite of the Sale module.

Responsible: fp, dbo, nimproduct_id_change_margin
parent b18e78ef
No related branches found
No related tags found
No related merge requests found
...@@ -21,9 +21,9 @@ from sales order. It adds sales name and sales Reference on production order. ...@@ -21,9 +21,9 @@ from sales order. It adds sales name and sales Reference on production order.
], ],
'demo': [], 'demo': [],
'test':[ 'test':[
'test/cancellation_propagated.yml', 'test/cancellation_propagated.yml',
'test/sale_mrp.yml', 'test/sale_mrp.yml',
], ],
'installable': True, 'installable': True,
'auto_install': True, 'auto_install': True,
} }
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv from openerp import api, fields, models
from openerp.tools import float_compare
class mrp_production(osv.osv):
_inherit = 'mrp.production'
def _ref_calc(self, cr, uid, ids, field_names=None, arg=False, context=None): class MrpProduction(models.Model):
""" Finds reference of sales order for production order. _inherit = 'mrp.production'
@param field_names: Names of fields.
@param arg: User defined arguments
@return: Dictionary of values.
"""
res = {}
if not field_names:
field_names = []
for id in ids:
res[id] = {}.fromkeys(field_names, False)
for f in field_names:
field_name = False
if f == 'sale_name':
field_name = 'name'
if f == 'sale_ref':
field_name = 'client_order_ref'
for key, value in self._get_sale_ref(cr, uid, ids, field_name).items():
res[key][f] = value
return res
def _get_sale_ref(self, cr, uid, ids, field_name=False): sale_name = fields.Char(compute='_compute_sale_name_sale_ref', string='Sale Name', help='Indicate the name of sales order.')
move_obj = self.pool.get('stock.move') sale_ref = fields.Char(compute='_compute_sale_name_sale_ref', string='Sale Reference', help='Indicate the Customer Reference from sales order.')
def get_parent_move(move_id): @api.multi
move = move_obj.browse(cr, uid, move_id) def _compute_sale_name_sale_ref(self):
def get_parent_move(move):
if move.move_dest_id: if move.move_dest_id:
return get_parent_move(move.move_dest_id.id) return get_parent_move(move.move_dest_id.id)
return move_id return move
for production in self:
res = {}
productions = self.browse(cr, uid, ids)
for production in productions:
res[production.id] = False
if production.move_prod_id: if production.move_prod_id:
parent_move_line = get_parent_move(production.move_prod_id.id) move = get_parent_move(production.move_prod_id)
if parent_move_line: production.sale_name = move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.name or False
move = move_obj.browse(cr, uid, parent_move_line) production.sale_ref = move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.client_order_ref or False
if field_name == 'name':
res[production.id] = move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.name or False
if field_name == 'client_order_ref':
res[production.id] = move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.client_order_ref or False
return res
_columns = {
'sale_name': fields.function(_ref_calc, multi='sale_name', type='char', string='Sale Name', help='Indicate the name of sales order.'),
'sale_ref': fields.function(_ref_calc, multi='sale_name', type='char', string='Sale Reference', help='Indicate the Customer Reference from sales order.'),
}
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
class sale_order(osv.Model): property_ids = fields.Many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]})
_inherit = 'sale.order'
def _prepare_order_line_procurement(self, cr, uid, order, line, group_id=False, context=None): @api.multi
result = super(sale_order, self)._prepare_order_line_procurement(cr, uid, order, line, group_id=group_id, context=context) def _get_delivered_qty(self):
result['property_ids'] = [(6, 0, [x.id for x in line.property_ids])] self.ensure_one()
return result precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
# In the case of a kit, we need to check if all components are shipped. We use a all or
# nothing policy. A product can have several BoMs, we don't know which one was used when the
# delivery was created.
bom_delivered = {}
for bom in self.product_id.product_tmpl_id.bom_ids:
if bom.type != 'phantom':
continue
bom_delivered[bom.id] = False
bom_exploded = self.env['mrp.bom']._bom_explode(bom, self.product_id.product_tmpl_id, self.product_uom_qty)[0]
for bom_line in bom_exploded:
qty = 0.0
for move in self.procurement_ids.mapped('move_ids'):
if move.state == 'done' and move.product_id.id == bom_line.get('product_id', False):
qty += self.env['product.uom']._compute_qty_obj(move.product_uom, move.product_uom_qty, self.product_uom)
if float_compare(qty, bom_line['product_qty'], precision_digits=precision) < 0:
bom_delivered[bom.id] = False
break
else:
bom_delivered[bom.id] = True
if bom_delivered and any(bom_delivered.values()):
return self.product_uom_qty
elif bom_delivered:
return 0.0
return super(SaleOrderLine, self)._get_delivered_qty()
class sale_order_line(osv.osv): @api.multi
def _prepare_order_line_procurement(self, group_id=False):
vals = super(SaleOrderLine, self)._prepare_order_line_procurement(group_id=group_id)
vals['property_ids'] = [(6, 0, self.property_ids.ids)]
return vals
_inherit = 'sale.order.line'
_columns = {
'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]}),
}
class stock_move(osv.osv): class StockMove(models.Model):
_inherit = 'stock.move' _inherit = 'stock.move'
def _prepare_procurement_from_move(self, cr, uid, move, context=None): @api.model
res = super(stock_move, self)._prepare_procurement_from_move(cr, uid, move, context=context) def _prepare_procurement_from_move(self, move):
res = super(StockMove, self)._prepare_procurement_from_move(move)
if res and move.procurement_id and move.procurement_id.property_ids: if res and move.procurement_id and move.procurement_id.property_ids:
res['property_ids'] = [(6, 0, [x.id for x in move.procurement_id.property_ids])] res['property_ids'] = [(6, 0, self.property_ids.ids)]
return res return res
def _action_explode(self, cr, uid, move, context=None): @api.model
def _action_explode(self, move):
""" Explodes pickings. """ Explodes pickings.
@param move: Stock moves @param move: Stock moves
@return: True @return: True
""" """
if context is None: property_ids = move.procurement_id.sale_line_id.property_ids.ids
context = {} return super(StockMove, self.with_context(property_ids=property_ids))._action_explode(move)
property_ids = map(int, move.procurement_id.sale_line_id.property_ids or [])
return super(stock_move, self)._action_explode(cr, uid, move, context=dict(context, property_ids=property_ids))
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<openerp> <openerp>
<data> <data>
<record id="mrp_production_form_view_inherit_sale_mrp" model="ir.ui.view">
<record id="view_mrp_production_form" model="ir.ui.view">
<field name="name">mrp.production.form</field> <field name="name">mrp.production.form</field>
<field name="model">mrp.production</field> <field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/> <field name="inherit_id" ref="mrp.mrp_production_form_view"/>
...@@ -16,18 +15,16 @@ ...@@ -16,18 +15,16 @@
</field> </field>
</record> </record>
<record id="view_order_form_inherit_mrp" model="ir.ui.view"> <record id="view_order_form_inherit_sale_mrp" model="ir.ui.view">
<field name="name">sale.order.form.sale.stock.mrp</field> <field name="name">sale.order.form.sale.stock.mrp</field>
<field name="model">sale.order</field> <field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/> <field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//page/field[@name='order_line']/form/group/group/field[@name='th_weight']" position="after"> <xpath expr="//page/field[@name='order_line']/form/group/group/field[@name='tax_id']" position="after">
<field name="property_ids" widget="many2many_tags" <field name="property_ids" widget="many2many_tags"
groups="sale.group_mrp_properties"/> groups="sale.group_mrp_properties"/>
</xpath> </xpath>
</field> </field>
</record> </record>
</data> </data>
</openerp> </openerp>
...@@ -43,14 +43,18 @@ ...@@ -43,14 +43,18 @@
partner_id: base.res_partner_3 partner_id: base.res_partner_3
note: Create Sales order note: Create Sales order
warehouse_id: wh_pps warehouse_id: wh_pps
pricelist_id: product.list0
order_line: order_line:
- product_id: product_manu - product_id: product_manu
name: "product_manu"
product_uom_qty: 5.00 product_uom_qty: 5.00
product_uom: product.product_uom_unit
- -
Confirm sales order Confirm sales order
- -
!workflow {model: sale.order, action: order_confirm, ref: sale_order_product_manu} !python {model: sale.order}: |
self.action_confirm(cr, uid, ref("sale_order_product_manu"), context=context)
- -
I run scheduler. I run scheduler.
- -
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
!record {model: product.template, id: product_template_slidermobile0}: !record {model: product.template, id: product_template_slidermobile0}:
categ_id: product_category_allproductssellable0 categ_id: product_category_allproductssellable0
list_price: 200.0 list_price: 200.0
mes_type: fixed
name: Slider Mobile name: Slider Mobile
standard_price: 189.0 standard_price: 189.0
type: product type: product
...@@ -28,7 +27,6 @@ ...@@ -28,7 +27,6 @@
!record {model: product.product, id: product_product_slidermobile0}: !record {model: product.product, id: product_product_slidermobile0}:
categ_id: product_category_allproductssellable0 categ_id: product_category_allproductssellable0
list_price: 200.0 list_price: 200.0
mes_type: fixed
name: Slider Mobile name: Slider Mobile
seller_delay: '1' seller_delay: '1'
seller_ids: seller_ids:
...@@ -72,10 +70,8 @@ ...@@ -72,10 +70,8 @@
product_uom: product.product_uom_unit product_uom: product.product_uom_unit
product_uom_qty: 500.0 product_uom_qty: 500.0
state: draft state: draft
delay: 7.0 customer_lead: 7.0
product_id: product_product_slidermobile0 product_id: product_product_slidermobile0
product_uos_qty: 500.0
order_policy: manual
partner_id: base.res_partner_4 partner_id: base.res_partner_4
partner_invoice_id: base.res_partner_address_7 partner_invoice_id: base.res_partner_address_7
partner_shipping_id: base.res_partner_address_7 partner_shipping_id: base.res_partner_address_7
...@@ -84,7 +80,8 @@ ...@@ -84,7 +80,8 @@
- -
I confirm the sale order I confirm the sale order
- -
!workflow {model: sale.order, action: order_confirm, ref: sale_order_so0} !python {model: sale.order}: |
self.action_confirm(cr, uid, ref("sale_order_so0"), context=context)
- -
I verify that a procurement has been generated for sale order I verify that a procurement has been generated for sale order
- -
...@@ -120,5 +117,5 @@ ...@@ -120,5 +117,5 @@
mnf_id = mnf_obj.search(cr, uid, [('origin','like',so.name)]) mnf_id = mnf_obj.search(cr, uid, [('origin','like',so.name)])
assert mnf_id, 'Manufacturing order has not been generated' assert mnf_id, 'Manufacturing order has not been generated'
mo = mnf_obj.browse(cr, uid, mnf_id)[0] mo = mnf_obj.browse(cr, uid, mnf_id)[0]
assert mo.sale_name == so.name, 'Wrong Name for the Manufacturing Order. Expected %s, Got %s' % (so.name, mo.name) assert mo.sale_name == so.name, 'Wrong Name for the Manufacturing Order. Expected %s, Got %s' % (so.name, mo.sale_name)
assert mo.sale_ref == so.client_order_ref, 'Wrong Sale Reference for the Manufacturing Order' assert mo.sale_ref == so.client_order_ref, 'Wrong Sale Reference for the Manufacturing Order'
...@@ -8,66 +8,71 @@ class TestMoveExplode(common.TransactionCase): ...@@ -8,66 +8,71 @@ class TestMoveExplode(common.TransactionCase):
def setUp(self): def setUp(self):
super(TestMoveExplode, self).setUp() super(TestMoveExplode, self).setUp()
cr, uid = self.cr, self.uid
# Usefull models # Usefull models
self.ir_model_data = self.registry('ir.model.data') self.SaleOrderLine = self.env['sale.order.line']
self.sale_order_line = self.registry('sale.order.line') self.SaleOrder = self.env['sale.order']
self.sale_order = self.registry('sale.order') self.MrpBom = self.env['mrp.bom']
self.mrp_bom = self.registry('mrp.bom') self.Product = self.env['product.product']
self.product = self.registry('product.product')
#product that has a phantom bom #product that has a phantom bom
self.product_bom_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_3')[1] self.product_bom = self.env.ref('product.product_product_3')
#bom with that product #bom with that product
self.bom_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_9')[1] self.bom = self.env.ref('mrp.mrp_bom_9')
#partner agrolait #partner agrolait
self.partner_id = self.ir_model_data.get_object_reference(cr, uid, 'base', 'res_partner_1')[1] self.partner = self.env.ref('base.res_partner_1')
#bom: PC Assemble (with property: DDR 512MB) #bom: PC Assemble (with property: DDR 512MB)
self.bom_prop_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_property_0')[1] self.bom_prop = self.env.ref('mrp.mrp_bom_property_0')
self.template_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_3_product_template')[1] self.template = self.env.ref('product.product_product_3_product_template')
#property: DDR 512MB #property: DDR 512MB
self.mrp_property_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_property_0')[1] self.mrp_property = self.env.ref('mrp.mrp_property_0')
#product: RAM SR2 #product: RAM SR2
self.product_bom_prop_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_14')[1] self.product_bom_prop = self.env.ref('product.product_product_14')
#phantom bom for RAM SR2 with three lines containing properties #phantom bom for RAM SR2 with three lines containing properties
self.bom_prop_line_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_property_line')[1] self.bom_prop_line = self.env.ref('mrp.mrp_bom_property_line')
#product: iPod included in the phantom bom #product: iPod included in the phantom bom
self.product_A_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_11')[1] self.product_A = self.env.ref('product.product_product_11')
#product: Mouse, Wireless included in the phantom bom #product: Mouse, Wireless included in the phantom bom
self.product_B_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_12')[1] self.product_B = self.env.ref('product.product_product_12')
#pricelist
self.pricelist = self.env.ref('product.list0')
def test_00_sale_move_explode(self): def test_00_sale_move_explode(self):
"""check that when creating a sale order with a product that has a phantom BoM, move explode into content of the """check that when creating a sale order with a product that has a phantom BoM, move explode into content of the
BoM""" BoM"""
cr, uid, context = self.cr, self.uid, {}
#create sale order with one sale order line containing product with a phantom bom #create sale order with one sale order line containing product with a phantom bom
so_id = self.sale_order.create(cr, uid, vals={'partner_id': self.partner_id}, context=context) so_vals = {
self.sale_order_line.create(cr, uid, values={'order_id': so_id, 'product_id': self.product_bom_id, 'product_uom_qty': 1}, context=context) 'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'pricelist_id': self.pricelist.id,
}
self.so = self.SaleOrder.create(vals=so_vals)
sol_vals = {
'order_id': self.so.id,
'name': self.product_bom.name,
'product_id': self.product_bom.id,
'product_uom': self.product_bom.uom_id.id,
'product_uom_qty': 1.0,
}
self.SaleOrderLine.create(values=sol_vals)
#confirm sale order #confirm sale order
self.sale_order.action_button_confirm(cr, uid, [so_id], context=context) self.so.action_confirm()
#get all move associated to that sale_order #get all move associated to that sale_order
browse_move_ids = self.sale_order.browse(cr, uid, so_id, context=context).picking_ids[0].move_lines move_ids = self.so.picking_ids.mapped('move_lines').ids
move_ids = [x.id for x in browse_move_ids]
#we should have same amount of move as the component in the phatom bom #we should have same amount of move as the component in the phatom bom
bom = self.mrp_bom.browse(cr, uid, self.bom_id, context=context) bom_component_length = self.MrpBom._bom_explode(self.bom, self.product_bom, 1.0, [])
bom_component_length = self.mrp_bom._bom_explode(cr, uid, bom, self.product_bom_id, 1, [])
self.assertEqual(len(move_ids), len(bom_component_length[0])) self.assertEqual(len(move_ids), len(bom_component_length[0]))
def test_00_bom_find(self): def test_00_bom_find(self):
"""Check that _bom_find searches the bom corresponding to the properties passed or takes the bom with the smallest """Check that _bom_find searches the bom corresponding to the properties passed or takes the bom with the smallest
sequence.""" sequence."""
cr, uid, context = self.cr, self.uid, {} res_id = self.MrpBom._bom_find(product_tmpl_id=self.template.id, product_id=None, properties=[self.mrp_property.id])
res_id = self.mrp_bom._bom_find(cr, uid, product_tmpl_id=self.template_id, product_id=None, properties=[self.mrp_property_id], context=context) self.assertEqual(res_id, self.bom_prop.id)
self.assertEqual(res_id, self.bom_prop_id)
def test_00_bom_explode(self): def test_00_bom_explode(self):
"""Check that _bom_explode only takes the lines with the right properties.""" """Check that _bom_explode only takes the lines with the right properties."""
cr, uid, context = self.cr, self.uid, {} res = self.MrpBom._bom_explode(self.bom_prop_line, self.product_bom_prop, 1, properties=[self.mrp_property.id])
bom = self.mrp_bom.browse(cr, uid, self.bom_prop_line_id)
product = self.product.browse(cr, uid, self.product_bom_prop_id)
res = self.mrp_bom._bom_explode(cr, uid, bom, product, 1, properties=[self.mrp_property_id], context=context)
res = set([p['product_id'] for p in res[0]]) res = set([p['product_id'] for p in res[0]])
self.assertEqual(res, set([self.product_A_id, self.product_B_id])) self.assertEqual(res, set([self.product_A.id, self.product_B.id]))
...@@ -8,7 +8,7 @@ class TestSaleMrpFlow(common.TransactionCase): ...@@ -8,7 +8,7 @@ class TestSaleMrpFlow(common.TransactionCase):
def setUp(self): def setUp(self):
super(TestSaleMrpFlow, self).setUp() super(TestSaleMrpFlow, self).setUp()
# Usefull models # Useful models
self.SaleOrderLine = self.env['sale.order.line'] self.SaleOrderLine = self.env['sale.order.line']
self.SaleOrder = self.env['sale.order'] self.SaleOrder = self.env['sale.order']
self.MrpBom = self.env['mrp.bom'] self.MrpBom = self.env['mrp.bom']
...@@ -116,16 +116,20 @@ class TestSaleMrpFlow(common.TransactionCase): ...@@ -116,16 +116,20 @@ class TestSaleMrpFlow(common.TransactionCase):
order = self.SaleOrder.create({ order = self.SaleOrder.create({
'partner_id': self.partner_agrolite.id, 'partner_id': self.partner_agrolite.id,
'partner_invoice_id': self.partner_agrolite.id,
'partner_shipping_id': self.partner_agrolite.id,
'date_order': datetime.today(), 'date_order': datetime.today(),
'pricelist_id': self.env.ref('product.list0').id,
}) })
self.SaleOrderLine.create({ self.SaleOrderLine.create({
'name': product_a.name,
'order_id': order.id, 'order_id': order.id,
'product_id': product_a.id, 'product_id': product_a.id,
'product_uom_qty': 10, 'product_uom_qty': 10,
'product_uom': self.uom_dozen.id 'product_uom': self.uom_dozen.id
}) })
self.assertTrue(order, "Sale order not created.") self.assertTrue(order, "Sale order not created.")
order.action_button_confirm() order.action_confirm()
# =============================================================================== # ===============================================================================
# Sale order of 10 Dozen product A should create production order # Sale order of 10 Dozen product A should create production order
...@@ -346,3 +350,48 @@ class TestSaleMrpFlow(common.TransactionCase): ...@@ -346,3 +350,48 @@ class TestSaleMrpFlow(common.TransactionCase):
self.assertEqual(mnf_product_a.state, 'done', 'Manufacturing order should be done.') self.assertEqual(mnf_product_a.state, 'done', 'Manufacturing order should be done.')
# Check product A avaialble quantity should be 120. # Check product A avaialble quantity should be 120.
self.assertEqual(product_a.qty_available, 120, 'Wrong quantity available of product A.') self.assertEqual(product_a.qty_available, 120, 'Wrong quantity available of product A.')
def test_01_sale_mrp_delivery_kit(self):
""" Test delivered quantity on SO based on delivered quantity in pickings."""
# intial so
self.partner = self.env.ref('base.res_partner_1')
self.product = self.env.ref('product.product_product_3')
so_vals = {
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': self.product.name, 'product_id': self.product.id, 'product_uom_qty': 5, 'product_uom': self.product.uom_id.id, 'price_unit': self.product.list_price})],
'pricelist_id': self.env.ref('product.list0').id,
}
self.so = self.SaleOrder.create(so_vals)
# confirm our standard so, check the picking
self.so.action_confirm()
self.assertTrue(self.so.picking_ids, 'Sale MRP: no picking created for "invoice on delivery" stockable products')
# invoice in on delivery, nothing should be invoiced
self.so.action_invoice_create()
self.assertEqual(self.so.invoice_status, 'no', 'Sale MRP: so invoice_status should be "nothing to invoice" after invoicing')
# deliver partially (1 of each instead of 5), check the so's invoice_status and delivered quantities
pick = self.so.picking_ids
pick.force_assign()
pick.pack_operation_product_ids.write({'qty_done': 1})
wiz_act = pick.do_new_transfer()
wiz = self.env[wiz_act['res_model']].browse(wiz_act['res_id'])
wiz.process()
self.assertEqual(self.so.invoice_status, 'no', 'Sale MRP: so invoice_status should be "no" after partial delivery of a kit')
del_qty = sum(sol.qty_delivered for sol in self.so.order_line)
self.assertEqual(del_qty, 0.0, 'Sale MRP: delivered quantity should be zero after partial delivery of a kit')
# deliver remaining products, check the so's invoice_status and delivered quantities
self.assertEqual(len(self.so.picking_ids), 2, 'Sale MRP: number of pickings should be 2')
pick_2 = self.so.picking_ids[0]
pick_2.force_assign()
pick_2.pack_operation_product_ids.write({'qty_done': 4})
pick_2.do_new_transfer()
del_qty = sum(sol.qty_delivered for sol in self.so.order_line)
self.assertEqual(del_qty, 5.0, 'Sale MRP: delivered quantity should be 5.0 after complete delivery of a kit')
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale MRP: so invoice_status should be "to invoice" after complete delivery of a kit')
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment