From 8b6c6a7ce26fc222b76e923a0ea717e789890f52 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Thu, 16 Aug 2018 14:24:32 -0700 Subject: [PATCH] IMP `rma` add the ability to relate RMA's to pickings. Additionally, improve tests to add lines by the wizard instead of manually (especially since manually is not an option in the UI). --- rma/__init__.py | 1 + rma/__manifest__.py | 1 + rma/data/rma_demo.xml | 12 +++ rma/models/rma.py | 163 ++++++++++++++++++++++++++++++++- rma/tests/test_rma.py | 71 +++++++++++++- rma/views/rma_views.xml | 13 ++- rma/wizard/__init__.py | 2 + rma/wizard/rma_lines.py | 59 ++++++++++++ rma/wizard/rma_lines_views.xml | 38 ++++++++ rma_sale/models/rma.py | 1 + rma_sale/tests/test_rma.py | 12 +-- rma_sale/views/rma_views.xml | 13 +++ 12 files changed, 371 insertions(+), 15 deletions(-) create mode 100644 rma/wizard/__init__.py create mode 100644 rma/wizard/rma_lines.py create mode 100644 rma/wizard/rma_lines_views.xml diff --git a/rma/__init__.py b/rma/__init__.py index 9e5827f9..cec04a5b 100644 --- a/rma/__init__.py +++ b/rma/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import controllers from . import models +from . import wizard diff --git a/rma/__manifest__.py b/rma/__manifest__.py index 99ea96da..cf3c037e 100644 --- a/rma/__manifest__.py +++ b/rma/__manifest__.py @@ -18,6 +18,7 @@ 'security/ir.model.access.csv', 'views/rma_views.xml', 'views/stock_picking_views.xml', + 'wizard/rma_lines_views.xml', ], 'demo': [ 'data/rma_demo.xml', diff --git a/rma/data/rma_demo.xml b/rma/data/rma_demo.xml index 5ec8bc2f..f114a484 100644 --- a/rma/data/rma_demo.xml +++ b/rma/data/rma_demo.xml @@ -9,4 +9,16 @@ make_to_stock + + + Picking Return + stock_picking + + + + + + make_to_stock + + \ No newline at end of file diff --git a/rma/models/rma.py b/rma/models/rma.py index 39370714..015df127 100644 --- a/rma/models/rma.py +++ b/rma/models/rma.py @@ -11,7 +11,9 @@ class RMATemplate(models.Model): _name = 'rma.template' name = fields.Char(string='Name') - usage = fields.Selection([], string='Applies To') + usage = fields.Selection([ + ('stock_picking', 'Stock Picking'), + ], string='Applies To') description = fields.Html(string='Internal Instructions') customer_description = fields.Html(string='Customer Instructions') valid_days = fields.Integer(string='Expire in Days') @@ -30,7 +32,6 @@ class RMATemplate(models.Model): ('make_to_stock', 'Take from Stock'), ('make_to_order', 'Apply Procurements') ], string="Inbound Procurement Method", default='make_to_stock') - in_to_refund_so = fields.Boolean(string='Inbound mark refund SO') out_location_id = fields.Many2one('stock.location', string='Outbound Source Location') out_location_dest_id = fields.Many2one('stock.location', string='Outbound Destination Location') @@ -103,6 +104,8 @@ class RMA(models.Model): ], string='State', default='draft', copy=False) company_id = fields.Many2one('res.company', 'Company') template_id = fields.Many2one('rma.template', string='Type', required=True) + stock_picking_id = fields.Many2one('stock.picking', string='Stock Picking') + stock_picking_rma_count = fields.Integer('Number of RMAs for this Picking', compute='_compute_stock_picking_rma_count') partner_id = fields.Many2one('res.partner', string='Partner') partner_shipping_id = fields.Many2one('res.partner', string='Shipping') lines = fields.One2many('rma.line', 'rma_id', string='Lines') @@ -130,9 +133,18 @@ class RMA(models.Model): @api.multi def _onchange_template_usage(self): now = datetime.now() - for rma in self.filtered(lambda r: r.template_id.valid_days): - rma.validity_date = now + timedelta(days=rma.template_id.valid_days) + for rma in self: + if rma.template_id.valid_days: + rma.validity_date = now + timedelta(days=rma.template_id.valid_days) + if rma.template_usage != 'stock_picking': + rma.stock_picking_id = False + @api.onchange('stock_picking_id') + @api.multi + def _onchange_stock_picking_id(self): + for rma in self.filtered(lambda rma: rma.stock_picking_id): + rma.partner_id = rma.stock_picking_id.partner_id + rma.partner_shipping_id = rma.stock_picking_id.partner_id @api.onchange('in_carrier_tracking_ref', 'validity_date') @api.multi @@ -162,6 +174,29 @@ class RMA(models.Model): str(attachment.id) + '&e=' + str(e_expires) + \ '&h=' + create_hmac(secret, attachment.id, e_expires) + @api.multi + @api.depends('stock_picking_id') + def _compute_stock_picking_rma_count(self): + for rma in self: + if rma.stock_picking_id: + rma_data = self.read_group([('stock_picking_id', '=', rma.stock_picking_id.id), ('state', '!=', 'cancel')], + ['stock_picking_id'], ['stock_picking_id']) + if rma_data: + rma.stock_picking_rma_count = rma_data[0]['stock_picking_id_count'] + else: + rma.stock_picking_rma_count = 0.0 + + + @api.multi + def open_stock_picking_rmas(self): + return { + 'type': 'ir.actions.act_window', + 'name': _('Picking RMAs'), + 'res_model': 'rma.rma', + 'view_mode': 'tree,form', + 'context': {'search_default_stock_picking_id': self[0].stock_picking_id.id} + } + @api.model def create(self, vals): if vals.get('name', _('New')) == _('New'): @@ -242,6 +277,17 @@ class RMA(models.Model): if rma.in_picking_id and rma.in_picking_carrier_id: rma.in_picking_id.send_to_shipper() + @api.multi + def action_add_picking_lines(self): + make_line_obj = self.env['rma.picking.make.lines'] + for rma in self: + lines = make_line_obj.create({ + 'rma_id': rma.id, + }) + action = self.env.ref('rma.action_rma_add_lines').read()[0] + action['res_id'] = lines.id + return action + @api.multi def unlink(self): for rma in self: @@ -249,6 +295,115 @@ class RMA(models.Model): raise UserError(_('You can not delete a non-draft RMA.')) return super(RMA, self).unlink() + def _create_in_picking_stock_picking(self): + if not self.stock_picking_id or self.stock_picking_id.state != 'done': + raise UserError(_('You must have a completed stock picking for this RMA.')) + if not self.template_id.in_require_return: + group_id = self.stock_picking_id.group_id.id if self.stock_picking_id.group_id else 0 + + values = self.template_id._values_for_in_picking(self) + values.update({'group_id': group_id}) + move_lines = [] + for l1, l2, vals in values['move_lines']: + vals.update({'group_id': group_id}) + move_lines.append((l1, l2, vals)) + values['move_lines'] = move_lines + return self.env['stock.picking'].sudo().create(values) + + lines = self.lines.filtered(lambda l: l.product_uom_qty >= 1) + if not lines: + raise UserError(_('You have no lines with positive quantity.')) + + old_picking = self.stock_picking_id + + new_picking = old_picking.copy({ + 'move_lines': [], + 'picking_type_id': self.template_id.in_type_id.id, + 'state': 'draft', + 'origin': old_picking.name + ' ' + self.name, + 'location_id': self.template_id.in_location_id.id, + 'location_dest_id': self.template_id.in_location_dest_id.id, + 'carrier_id': self.template_id.in_carrier_id.id if self.template_id.in_carrier_id else 0, + 'carrier_tracking_ref': False, + 'carrier_price': False + }) + new_picking.message_post_with_view('mail.message_origin_link', + values={'self': new_picking, 'origin': self}, + subtype_id=self.env.ref('mail.mt_note').id) + + for l in lines: + return_move = old_picking.move_lines.filtered(lambda ol: ol.state == 'done' and ol.product_id.id == l.product_id.id)[0] + return_move.copy({ + 'name': self.name + ' IN: ' + l.product_id.name_get()[0][1], + 'product_id': l.product_id.id, + 'product_uom_qty': l.product_uom_qty, + 'picking_id': new_picking.id, + 'state': 'draft', + 'location_id': return_move.location_dest_id.id, + 'location_dest_id': self.template_id.in_location_dest_id.id, + 'picking_type_id': new_picking.picking_type_id.id, + 'warehouse_id': new_picking.picking_type_id.warehouse_id.id, + 'origin_returned_move_id': return_move.id, + 'procure_method': self.template_id.in_procure_method, + 'move_dest_id': False, + }) + + return new_picking + + def _create_out_picking_stock_picking(self): + if not self.stock_picking_id or self.stock_picking_id.state != 'done': + raise UserError(_('You must have a completed stock picking for this RMA.')) + if not self.template_id.out_require_return: + group_id = self.stock_picking_id.group_id.id if self.stock_picking_id.group_id else 0 + + values = self.template_id._values_for_out_picking(self) + values.update({'group_id': group_id}) + move_lines = [] + for l1, l2, vals in values['move_lines']: + vals.update({'group_id': group_id}) + move_lines.append((l1, l2, vals)) + values['move_lines'] = move_lines + return self.env['stock.picking'].sudo().create(values) + + lines = self.lines.filtered(lambda l: l.product_uom_qty >= 1) + if not lines: + raise UserError(_('You have no lines with positive quantity.')) + + old_picking = self.stock_picking_id + new_picking = old_picking.copy({ + 'move_lines': [], + 'picking_type_id': self.template_id.out_type_id.id, + 'state': 'draft', + 'origin': old_picking.name + ' ' + self.name, + 'location_id': self.template_id.out_location_id.id, + 'location_dest_id': self.template_id.out_location_dest_id.id, + 'carrier_id': self.template_id.out_carrier_id.id if self.template_id.out_carrier_id else 0, + 'carrier_tracking_ref': False, + 'carrier_price': False + }) + new_picking.message_post_with_view('mail.message_origin_link', + values={'self': new_picking, 'origin': self}, + subtype_id=self.env.ref('mail.mt_note').id) + + for l in lines: + return_move = old_picking.move_lines.filtered(lambda ol: ol.state == 'done' and ol.product_id.id == l.product_id.id)[0] + return_move.copy({ + 'name': self.name + ' OUT: ' + l.product_id.name_get()[0][1], + 'product_id': l.product_id.id, + 'product_uom_qty': l.product_uom_qty, + 'picking_id': new_picking.id, + 'state': 'draft', + 'location_id': self.template_id.out_location_id.id, + 'location_dest_id': self.template_id.out_location_dest_id.id, + 'picking_type_id': new_picking.picking_type_id.id, + 'warehouse_id': new_picking.picking_type_id.warehouse_id.id, + 'origin_returned_move_id': False, + 'procure_method': self.template_id.out_procure_method, + 'move_dest_id': False, + }) + + return new_picking + class RMALine(models.Model): _name = 'rma.line' diff --git a/rma/tests/test_rma.py b/rma/tests/test_rma.py index a64fffca..2175ed8e 100644 --- a/rma/tests/test_rma.py +++ b/rma/tests/test_rma.py @@ -7,12 +7,14 @@ class TestRMA(common.TransactionCase): def setUp(self): super(TestRMA, self).setUp() self.product1 = self.env.ref('product.product_product_24') - self.template1 = self.env.ref('rma.template_missing_item') + self.template_missing = self.env.ref('rma.template_missing_item') + self.template_return = self.env.ref('rma.template_picking_return') self.partner1 = self.env.ref('base.res_partner_2') def test_00_basic_rma(self): + self.template_missing.usage = False rma = self.env['rma.rma'].create({ - 'template_id': self.template1.id, + 'template_id': self.template_missing.id, 'partner_id': self.partner1.id, 'partner_shipping_id': self.partner1.id, }) @@ -41,8 +43,9 @@ class TestRMA(common.TransactionCase): self.assertEqual(rma.state, 'done') def test_10_rma_cancel(self): + self.template_missing.usage = False rma = self.env['rma.rma'].create({ - 'template_id': self.template1.id, + 'template_id': self.template_missing.id, 'partner_id': self.partner1.id, 'partner_shipping_id': self.partner1.id, }) @@ -58,3 +61,65 @@ class TestRMA(common.TransactionCase): self.assertEqual(rma.out_picking_id.move_lines.state, 'assigned') rma.action_cancel() self.assertEqual(rma.out_picking_id.move_lines.state, 'cancel') + + def test_20_picking_rma(self): + type_out = self.env.ref('stock.picking_type_out') + location = self.env.ref('stock.stock_location_stock') + location_customer = self.env.ref('stock.stock_location_customers') + self.product1.tracking = 'serial' + picking_out = self.env['stock.picking'].create({ + 'partner_id': self.partner1.id, + 'name': 'testpicking', + 'picking_type_id': type_out.id, + 'location_id': location.id, + 'location_dest_id': location_customer.id, + }) + self.env['stock.move'].create({ + 'name': self.product1.name, + 'product_id': self.product1.id, + 'product_uom_qty': 1.0, + 'product_uom': self.product1.uom_id.id, + 'picking_id': picking_out.id, + 'location_id': location.id, + 'location_dest_id': location_customer.id, + }) + picking_out.action_confirm() + + # Try to RMA item not delivered yet + rma = self.env['rma.rma'].create({ + 'template_id': self.template_return.id, + 'partner_id': self.partner1.id, + 'partner_shipping_id': self.partner1.id, + 'stock_picking_id': picking_out.id, + }) + self.assertEqual(rma.state, 'draft') + wizard = self.env['rma.picking.make.lines'].create({ + 'rma_id': rma.id, + }) + wizard.line_ids.product_uom_qty = 1.0 + wizard.add_lines() + self.assertEqual(len(rma.lines), 1) + with self.assertRaises(UserError): + rma.action_confirm() + + picking_out.force_assign() + pack_opt = self.env['stock.pack.operation'].search([('picking_id', '=', picking_out.id)], limit=1) + lot = self.env['stock.production.lot'].create({'product_id': self.product1.id, 'name': 'X100'}) + self.env['stock.pack.operation.lot'].create({'operation_id': pack_opt.id, 'lot_id': lot.id, 'qty': 1.0}) + pack_opt.qty_done = 1.0 + picking_out.do_transfer() + + self.assertEqual(picking_out.state, 'done') + + rma.action_confirm() + self.assertEqual(rma.in_picking_id.state, 'assigned') + pack_opt = self.env['stock.pack.operation'].search([('picking_id', '=', rma.in_picking_id.id)], limit=1) + self.assertEqual(pack_opt.pack_lot_ids.lot_id, lot) + + with self.assertRaises(UserError): + rma.action_done() + + pack_opt.pack_lot_ids.qty = 1.0 + pack_opt.qty_done = 1.0 + rma.in_picking_id.do_transfer() + rma.action_done() diff --git a/rma/views/rma_views.xml b/rma/views/rma_views.xml index c20401e8..df85464b 100644 --- a/rma/views/rma_views.xml +++ b/rma/views/rma_views.xml @@ -13,7 +13,12 @@ -
+
+ +

@@ -23,6 +28,9 @@ + +
+