# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import Command from odoo.addons.helpdesk.tests import common from odoo.exceptions import UserError from odoo.tests import Form, tagged @tagged('post_install', '-at_install') class TestHelpdeskStock(common.HelpdeskCommon): """ Test used to check that the functionalities of After sale in Helpdesk (stock). """ def test_helpdesk_stock(self): # give the test team ability to create coupons self.test_team.use_product_returns = True product = self.env['product.product'].create({ 'name': 'product 1', 'is_storable': True, 'invoice_policy': 'order', }) so = self.env['sale.order'].create({ 'partner_id': self.partner.id, }) self.env['sale.order.line'].create({ 'product_id': product.id, 'price_unit': 10, 'product_uom_qty': 1, 'order_id': so.id, }) so.action_confirm() so._create_invoices() invoice = so.invoice_ids invoice.action_post() so.picking_ids[0].move_ids[0].quantity = 1 so.picking_ids[0].button_validate() ticket = self.env['helpdesk.ticket'].create({ 'name': 'test', 'partner_id': self.partner.id, 'team_id': self.test_team.id, 'sale_order_id': so.id, }) stock_picking_form = Form(self.env['stock.return.picking'].with_context({ 'active_model': 'helpdesk.ticket', 'default_ticket_id': ticket.id })) stock_picking_form.picking_id = so.picking_ids[0] with stock_picking_form.product_return_moves.edit(0) as line: line.quantity = 1 return_picking = stock_picking_form.save() self.assertEqual(len(return_picking.product_return_moves), 1, "A picking line should be present") self.assertEqual(return_picking.product_return_moves[0].product_id, product, "The product of the picking line does not match the product of the sale order") return_picking.action_create_returns() return_picking = self.env['stock.picking'].search([ ('partner_id', '=', self.partner.id), ('picking_type_code', '=', 'incoming'), ]) self.assertEqual(len(return_picking), 1, "No return created") self.assertEqual(return_picking.state, 'assigned', "Wrong status of the refund") self.assertEqual(ticket.pickings_count, 1, "The ticket should be linked to a return") self.assertEqual(return_picking.id, ticket.picking_ids[0].id, "The correct return should be referenced in the ticket") return_picking.move_ids[0].quantity = 1 return_picking.button_validate() # Trigger _compute_state return_picking.state last_message = str(ticket.message_ids[0].body) self.assertTrue(return_picking.display_name in last_message and 'Return' in last_message, 'Return validation should be logged on the ticket') def test_helpdesk_stock_return(self): """ You should be able to return a product that has an open backorder. """ product = self.env['product.product'].create({ 'name': 'test product', 'is_storable': True, 'invoice_policy': 'order', }) partner = self.env['res.partner'].create({ 'name': 'Customer' }) so = self.env['sale.order'].create({ 'partner_id': partner.id, }) self.env['sale.order.line'].create({ 'product_id': product.id, 'price_unit': 10, 'product_uom_qty': 5, 'order_id': so.id, }) so.action_confirm() # get delivery order delivery_order = so.picking_ids[0] # validated only 3 units delivery_order.move_ids[0].quantity = 3 # validate delivery order delivery_order.button_validate() # create backorder with form Form(self.env['stock.backorder.confirmation'].with_context({ 'button_validate_picking_ids': [delivery_order.id], 'default_pick_ids': [(4, delivery_order.id)], 'default_show_transfers': False, 'skip_sanity_check': True, })).save().process() ticket = self.env['helpdesk.ticket'].create({ 'name': 'test', 'partner_id': partner.id, 'team_id': self.test_team.id, 'sale_order_id': so.id }) try: # create a return picking on ticket Form(self.env['stock.return.picking'].with_context({ 'active_model': 'helpdesk.ticket', 'default_ticket_id': ticket.id })) except UserError: self.fail("We should be able to make a return of the already delivered quantities " "even if the so has an open backorder that isn't done") def test_helpdesk_ticket_partner_has_picking(self): """ This test case verifies that a helpdesk ticket correctly identifies if its associated partner or the partner's commercial entity has related delivery orders after confirming a sale order. """ product = self.env['product.product'].create({ 'name': 'Product 2', 'is_storable': True, 'invoice_policy': 'order', }) commercial_partner = self.env['res.partner'].create({ 'name': 'Commercial Customer', 'is_company': True, }) # Create a child partner partner = self.env['res.partner'].create({ 'name': 'Customer 2', 'parent_id': commercial_partner.id, }) sale_order = self.env['sale.order'].create({ 'partner_id': partner.id, 'order_line': [Command.create({ 'product_id': product.id, 'price_unit': 10, 'product_uom_qty': 5, })], }) sale_order.action_confirm() delivery_order = sale_order.picking_ids[0] delivery_order.move_ids[0].quantity = 5 delivery_order.button_validate() ticket = self.env['helpdesk.ticket'].create({ 'name': 'Test Ticket', 'partner_id': partner.id, 'team_id': self.test_team.id, 'sale_order_id': sale_order.id, }) self.assertTrue(ticket.has_partner_picking, "The ticket should have a related delivery order for the partner.") # assign commercial_partner to partner_id in ticket ticket.partner_id = commercial_partner self.assertTrue(ticket.has_partner_picking, "The ticket should have a related delivery order for the commercial partner") def test_helpdesk_ticket_product_from_parent_company(self): company_product, employee_1_product, employee_2_product = self.env['product.product'].create([ {'name': 'Company Product'}, {'name': 'Employee 1 Product'}, {'name': 'Employee 2 Product'}, ]) partners = self.env['res.partner'].create([ {'name': 'Company', 'company_type': 'company'}, {'name': 'Employee 1', 'company_type': 'person'}, {'name': 'Employee 1', 'company_type': 'person'}, ]) company, employee_1, employee_2 = partners (employee_1 | employee_2).commercial_partner_id = company sale_orders = self.env['sale.order'].create([ {'partner_id': company.id, 'order_line': [Command.create({'product_id': company_product.id})]}, {'partner_id': employee_1.id, 'order_line': [Command.create({'product_id': employee_1_product.id})]}, {'partner_id': employee_2.id, 'order_line': [Command.create({'product_id': employee_2_product.id})]}, ]) sale_orders.action_confirm() products = (company_product | employee_1_product | employee_2_product) ticket = self.env['helpdesk.ticket'].create({'name': 'Ticket'}) for partner in partners: ticket.partner_id = partner self.assertEqual(ticket.suitable_product_ids, products, 'Products from all children of the parent company should be visible') def test_ensure_has_partner_picking(self): """ It is possible to return some products from a ticket. To do so, the user clicks on the Return button. That button will only be displayed in a condition strongly based on the field `has_partner_picking` (tldr: the client must have an outgoing done picking OR a confirmed sale order line). This test ensures the field value is correct in several cases. """ product, service = self.env['product.product'].create([ {'name': 'Amazing Product', 'type': 'consu'}, {'name': 'Amazing Service', 'type': 'service'}, ]) partners = self.env['res.partner'].create([{ 'name': name, } for name in [ 'No SO', 'SO with service', 'Draft SO', 'Confirmed SO', 'Cancelled SO', 'Done SO', ]]) service_sale_order = self.env['sale.order'].create([{ 'partner_id': partners[1].id, 'order_line': [(0, 0, {'product_id': service.id})] }]) _draft_so, confirmed_so, canceled_so, done_so = self.env['sale.order'].create([{ 'partner_id': partner.id, 'order_line': [(0, 0, {'product_id': product.id})] } for partner in partners[2:]]) (confirmed_so | canceled_so | done_so | service_sale_order).action_confirm() canceled_so.with_context(disable_cancel_warning=True).action_cancel() self.assertEqual(canceled_so.state, 'cancel') done_so.picking_ids.move_ids.quantity = 1 done_so.picking_ids.button_validate() self.assertEqual(done_so.picking_ids.state, 'done') tickets = self.env['helpdesk.ticket'].create([{ 'name': 'Amazing ticket', 'partner_id': partner.id, } for partner in partners]) ticket_by_partner = {t.partner_id.id: t for t in tickets} self.assertFalse(ticket_by_partner[partners[0].id].has_partner_picking, "Partner with 'No SO' should not have return picking.") self.assertTrue(ticket_by_partner[partners[1].id].has_partner_picking, "Partner with 'SO with service' should have return picking.") self.assertFalse(ticket_by_partner[partners[2].id].has_partner_picking, "Partner with 'Draft SO' should not have return picking.") self.assertTrue(ticket_by_partner[partners[3].id].has_partner_picking, "Partner with 'Confirmed SO' should have return picking.") self.assertFalse(ticket_by_partner[partners[4].id].has_partner_picking, "Partner with 'Cancelled SO' should not have return picking.") self.assertTrue(ticket_by_partner[partners[5].id].has_partner_picking, "Partner with 'Done SO' should have return picking.") def test_set_picking_to_false_in_wizard(self): """ This test ensure that when the picking field of the wizard is set to False, no traceback is triggered during the product_return_moves compute.""" product = self.env['product.product'].create({ 'name': 'test product', 'is_storable': True, 'invoice_policy': 'order', }) partner = self.env['res.partner'].create({ 'name': 'Customer' }) so = self.env['sale.order'].create({ 'partner_id': partner.id, }) self.env['sale.order.line'].create({ 'product_id': product.id, 'price_unit': 10, 'product_uom_qty': 5, 'order_id': so.id, }) so.action_confirm() delivery_order = so.picking_ids[0] delivery_order.move_ids[0].quantity = 5 delivery_order.button_validate() ticket = self.env['helpdesk.ticket'].create({ 'name': 'test', 'partner_id': partner.id, 'team_id': self.test_team.id, 'sale_order_id': so.id }) wizard_form = Form(self.env['stock.return.picking'].with_context({ 'active_model': 'helpdesk.ticket', 'default_ticket_id': ticket.id })) wizard_form.picking_id = self.env['stock.picking'] def test_return_product_no_sale_order(self): """ This test ensure that when the picking field of the wizard is not set, we can return the product without any issue.""" product = self.env['product.product'].create({ 'name': 'test product', 'is_storable': True, 'invoice_policy': 'order', }) partner = self.env['res.partner'].create({ 'name': 'Customer' }) ticket = self.env['helpdesk.ticket'].create({ 'name': 'test', 'partner_id': partner.id, 'team_id': self.test_team.id, }) wizard = self.env['stock.return.picking'].create({ 'ticket_id': ticket.id, 'partner_id': partner.id }) line = self.env['stock.return.picking.line'].create({ 'product_id': product.id, 'move_quantity': 5, 'quantity': 5, 'uom_id': product.uom_id.id, 'wizard_id': wizard.id, }) res = wizard.action_create_returns() self.assertTrue(res) self.assertEqual(line.ids, wizard.product_return_moves.ids, 'the line ids should match the wizard product return moves') res_picking = self.env['stock.picking'].search([('id', '=', res.get('res_id'))]) self.assertEqual(res_picking.product_id.id, product.id, 'the product id should match the product selected in the wizard') self.assertEqual(res_picking.picking_type_code, 'incoming', 'the picking type code should be incoming in the return') self.assertEqual(res_picking.return_id.id, wizard.picking_id.id, 'the return id should match the wizard picking id') def test_return_picking_default_multistep_delivery(self): """ Tests that when returning a product from a helpdesk ticket, the correct delivery order is selected by default in multi-step delivery scenarios. The default should always be the final customer-facing operation (Ship/Out). """ warehouse = self.env['stock.warehouse'].create({ 'name': 'Multi-Step Test WH', 'code': 'MSWH', }) product_multi_step = self.env['product.product'].create({ 'name': 'Multi-Step Test Product', 'is_storable': True, }) routes = ['pick_ship', 'pick_pack_ship'] for steps in routes: with self.subTest(delivery_steps=steps): warehouse.write({'delivery_steps': steps}) so = self.env['sale.order'].create({ 'partner_id': self.partner.id, 'warehouse_id': warehouse.id, 'order_line': [Command.create({'product_id': product_multi_step.id, 'product_uom_qty': 1})], }) so.action_confirm() final_picking = self.env['stock.picking'] current_picking = so.picking_ids while current_picking: current_picking.move_ids.quantity = 1 current_picking.button_validate() final_picking = current_picking current_picking = current_picking.move_ids.move_dest_ids.picking_id self.assertEqual(final_picking.state, 'done') ticket = self.env['helpdesk.ticket'].create({ 'name': f'Return for {steps}', 'partner_id': self.partner.id, 'team_id': self.test_team.id, 'sale_order_id': so.id, }) return_wizard_form = Form(self.env['stock.return.picking'].with_context({ 'active_model': 'helpdesk.ticket', 'default_ticket_id': ticket.id, })) self.assertEqual( return_wizard_form.picking_id, final_picking, f"For {steps} delivery, the final OUT operation should be defaulted." ) def test_copy_ticket_without_stock_user(self): service_product = self.env['product.product'].create({ 'name': "Service product", 'type': 'service', }) ticket_1 = self.env['helpdesk.ticket'].create({ 'name': 'Ticket 1', 'team_id': self.test_team.id, 'partner_id': self.partner.id, 'product_id': service_product.id }) # should not raise an access error copied_ticket = ticket_1.with_user(self.helpdesk_user).copy() self.assertTrue(copied_ticket)