diff --git a/rma_sale/__manifest__.py b/rma_sale/__manifest__.py
index 2cbf748b..e9fa3f5b 100644
--- a/rma_sale/__manifest__.py
+++ b/rma_sale/__manifest__.py
@@ -2,7 +2,7 @@
{
'name': 'Hibou RMAs for Sale Orders',
- 'version': '13.0.1.1.0',
+ 'version': '13.0.1.2.0',
'category': 'Sale',
'author': 'Hibou Corp.',
'license': 'OPL-1',
diff --git a/rma_sale/models/product.py b/rma_sale/models/product.py
index 2e338a39..06308b76 100644
--- a/rma_sale/models/product.py
+++ b/rma_sale/models/product.py
@@ -14,3 +14,13 @@ class ProductTemplate(models.Model):
'A positive number will allow the product to be '
'returned up to that number of days. A negative '
'number prevents the return of the product.')
+
+ rma_sale_warranty_validity = fields.Integer(string='RMA Eligible Days (Sale Warranty)',
+ help='Determines the number of days from the time '
+ 'of the sale that the product is eligible to '
+ 'be returned for warranty claims. '
+ '0 (default) will allow the product to be '
+ 'returned for an indefinite period of time. '
+ 'A positive number will allow the product to be '
+ 'returned up to that number of days. A negative '
+ 'number prevents the return of the product.')
diff --git a/rma_sale/models/rma.py b/rma_sale/models/rma.py
index 03344398..fb800915 100644
--- a/rma_sale/models/rma.py
+++ b/rma_sale/models/rma.py
@@ -20,6 +20,9 @@ class RMATemplate(models.Model):
_inherit = 'rma.template'
usage = fields.Selection(selection_add=[('sale_order', 'Sale Order')])
+ sale_order_warranty = fields.Boolean(string='Sale Order Warranty',
+ help='Determines if the regular return validity or '
+ 'Warranty validity is used.')
so_decrement_order_qty = fields.Boolean(string='SO Decrement Ordered Qty.',
help='When completing the RMA, the Ordered Quantity will be decremented by '
'the RMA qty.')
@@ -87,7 +90,10 @@ class RMATemplate(models.Model):
return super(RMATemplate, self)._portal_values(request_user, res_id=res_id)
def _rma_sale_line_validity(self, so_line):
- validity_days = so_line.product_id.rma_sale_validity
+ if self.sale_order_warranty:
+ validity_days = so_line.product_id.rma_sale_warranty_validity
+ else:
+ validity_days = so_line.product_id.rma_sale_validity
if validity_days < 0:
return ''
elif validity_days > 0:
@@ -195,6 +201,10 @@ class RMA(models.Model):
pass
return sale_orders.mapped('invoice_ids') - original_invoices
+ def _invoice_values_sale_order(self):
+ # the RMA invoice API will not be used as invoicing will happen at the SO level
+ return False
+
def action_add_so_lines(self):
make_line_obj = self.env['rma.sale.make.lines']
for rma in self:
diff --git a/rma_sale/tests/test_rma.py b/rma_sale/tests/test_rma.py
index 446260d6..7cd4d329 100644
--- a/rma_sale/tests/test_rma.py
+++ b/rma_sale/tests/test_rma.py
@@ -167,7 +167,140 @@ class TestRMASale(TestRMA):
# Existing lot cannot be re-used.
with self.assertRaises(ValidationError):
rma2.in_picking_id.action_done()
-
+
# RMA cannot be completed because the inbound picking state is confirmed
with self.assertRaises(UserError):
rma2.action_done()
+
+ def test_30_product_sale_return_warranty(self):
+ self.template_sale_return.write({
+ 'usage': 'sale_order',
+ 'invoice_done': True,
+ 'sale_order_warranty': True,
+ 'in_to_refund': True,
+ 'so_decrement_order_qty': False, # invoice on decremented delivered not decremented order
+ 'next_rma_template_id': self.template_rtv.id,
+ })
+
+ validity = 100 # eligible for 100 days
+ warranty_validity = validity + 100 # eligible for 200 days
+
+ self.product1.write({
+ 'rma_sale_validity': validity,
+ 'rma_sale_warranty_validity': warranty_validity,
+ 'type': 'product',
+ 'invoice_policy': 'delivery',
+ 'tracking': 'serial',
+ 'standard_price': 1.5,
+ })
+
+ order = self.env['sale.order'].create({
+ 'partner_id': self.partner1.id,
+ 'partner_invoice_id': self.partner1.id,
+ 'partner_shipping_id': self.partner1.id,
+ 'user_id': self.user1.id,
+ 'order_line': [(0, 0, {
+ 'product_id': self.product1.id,
+ 'product_uom_qty': 1.0,
+ 'product_uom': self.product1.uom_id.id,
+ 'price_unit': 10.0,
+ })]
+ })
+ order.action_confirm()
+ self.assertTrue(order.state in ('sale', 'done'))
+ self.assertEqual(len(order.picking_ids), 1, 'Tests only run with single stage delivery.')
+
+ # Try to RMA item not delivered yet
+ rma = self.env['rma.rma'].create({
+ 'template_id': self.template_sale_return.id,
+ 'partner_id': self.partner1.id,
+ 'partner_shipping_id': self.partner1.id,
+ 'sale_order_id': order.id,
+ })
+ self.assertEqual(rma.state, 'draft')
+ # Do not allow warranty return.
+ self.product1.rma_sale_warranty_validity = -1
+ wizard = self.env['rma.sale.make.lines'].with_user(self.user1).create({'rma_id': rma.id})
+ self.assertEqual(wizard.line_ids.qty_delivered, 0.0)
+ wizard.line_ids.product_uom_qty = 1.0
+
+ with self.assertRaises(UserError):
+ wizard.add_lines()
+
+ # Allows returns, but not forever
+ self.product1.rma_sale_warranty_validity = warranty_validity
+ original_date_order = order.date_order
+ order.write({'date_order': original_date_order - timedelta(days=warranty_validity+1)})
+ wizard = self.env['rma.sale.make.lines'].with_user(self.user1).create({'rma_id': rma.id})
+ self.assertEqual(wizard.line_ids.qty_delivered, 0.0)
+ wizard.line_ids.product_uom_qty = 1.0
+ with self.assertRaises(UserError):
+ wizard.add_lines()
+
+ # Allows returns due to date, due to warranty option
+ order.write({'date_order': original_date_order - timedelta(days=validity+1)})
+ wizard = self.env['rma.sale.make.lines'].with_user(self.user1).create({'rma_id': rma.id})
+ self.assertEqual(wizard.line_ids.qty_delivered, 0.0)
+ wizard.line_ids.product_uom_qty = 1.0
+ wizard.add_lines()
+
+ # finish outbound so that we can invoice.
+ order.picking_ids.action_assign()
+ pack_opt = order.picking_ids.move_line_ids[0]
+ lot = self.env['stock.production.lot'].create({
+ 'product_id': self.product1.id,
+ 'name': 'X100',
+ 'product_uom_id': self.product1.uom_id.id,
+ 'company_id': self.env.user.company_id.id,
+ })
+ pack_opt.qty_done = 1.0
+ pack_opt.lot_id = lot
+ order.picking_ids.button_validate()
+ self.assertEqual(order.picking_ids.state, 'done')
+ self.assertEqual(order.order_line.qty_delivered, 1.0)
+
+ # Invoice the order so that only the core product is invoiced at the end...
+ self.assertFalse(order.invoice_ids)
+ wiz = self.env['sale.advance.payment.inv'].with_context(active_ids=order.ids).create({})
+ wiz.create_invoices()
+ order.flush()
+ self.assertTrue(order.invoice_ids)
+ order_invoice = order.invoice_ids
+
+ self.assertEqual(rma.lines.product_id, self.product1)
+ rma.action_confirm()
+ self.assertTrue(rma.in_picking_id)
+ self.assertEqual(rma.in_picking_id.state, 'assigned')
+ pack_opt = rma.in_picking_id.move_line_ids[0]
+ pack_opt.lot_id = lot.id
+ pack_opt.qty_done = 1.0
+ rma.in_picking_id.button_validate()
+ self.assertEqual(rma.in_picking_id.state, 'done')
+ order.flush()
+ # self.assertEqual(order.order_line.qty_delivered, 0.0)
+ rma.action_done()
+ self.assertEqual(rma.state, 'done')
+ order.flush()
+
+ rma_invoice = rma.invoice_ids
+ self.assertTrue(rma_invoice)
+ sale_line = rma_invoice.invoice_line_ids.filtered(lambda l: l.sale_line_ids)
+ so_line = sale_line.sale_line_ids
+ self.assertTrue(sale_line)
+ self.assertEqual(sale_line.price_unit, so_line.price_unit)
+
+ # Invoices do not have their anglo-saxon cost lines until they post
+ order_invoice.post()
+ rma_invoice.post()
+
+ # Find the return to vendor RMA
+ rtv_rma = self.env['rma.rma'].search([('parent_id', '=', rma.id)])
+ self.assertTrue(rtv_rma)
+ self.assertFalse(rtv_rma.out_picking_id)
+
+ wiz = self.env['rma.make.rtv'].with_context(active_model='rma.rma', active_ids=rtv_rma.ids).create({})
+ self.assertTrue(wiz.rma_line_ids)
+ wiz.partner_id = self.partner2
+ wiz.create_batch()
+ self.assertTrue(rtv_rma.out_picking_id)
+ self.assertEqual(rtv_rma.out_picking_id.partner_id, self.partner2)
diff --git a/rma_sale/views/portal_templates.xml b/rma_sale/views/portal_templates.xml
index 6340352d..bb97b9b7 100644
--- a/rma_sale/views/portal_templates.xml
+++ b/rma_sale/views/portal_templates.xml
@@ -57,8 +57,8 @@