# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from contextlib import contextmanager import unittest from unittest.mock import Mock, patch from odoo.tests.common import TransactionCase, tagged, Form from odoo.exceptions import UserError # These errors are due to failures of Fedex test server and are not implementation errors ERROR_200 = u"200: Rating is temporarily unavailable, please try again later." ERROR_200_BIS = u"200: An unexpected exception occurred" ERROR_200_TER = u"200: An unexpected exception occurred, not found" ERROR_1000 = u"1000: General Failure" SKIPPABLE_ERRORS = [ERROR_200, ERROR_200_BIS, ERROR_200_TER, ERROR_1000] SKIP_MSG = u"Test skipped due to FedEx server unavailability" @tagged('-standard', 'external') class TestDeliveryFedex(TransactionCase): def setUp(self): super(TestDeliveryFedex, self).setUp() self.iPadMini = self.env['product.product'].create({ 'name': 'Ipad Mini', 'weight': 0.01, }) self.large_desk = self.env['product.product'].create({ 'name': 'Large Desk', 'weight': 0.01, }) self.uom_unit = self.env.ref('uom.product_uom_unit') self.your_company = self.env.ref('base.main_partner') self.your_company.write({'country_id': self.env.ref('base.us').id, 'state_id': self.env.ref('base.state_us_5').id, 'city': 'San Francisco', 'street': '51 Federal Street', 'zip': '94107', 'phone': 9874582356}) self.agrolait = self.env['res.partner'].create({ 'name': 'Agrolait', 'phone': '(603)-996-3829', 'street': "rue des Bourlottes, 9", 'street2': "", 'city': "Ramillies", 'zip': 1367, 'state_id': False, 'country_id': self.env.ref('base.be').id, }) self.delta_pc = self.env['res.partner'].create({ 'name': 'Delta PC', 'phone': '(803)-873-6126', 'street': "1515 Main Street", 'street2': "", 'city': "Columbia", 'zip': 29201, 'state_id': self.env.ref('base.state_us_41').id, 'country_id': self.env.ref('base.us').id, }) self.stock_location = self.env.ref('stock.stock_location_stock') self.customer_location = self.env.ref('stock.stock_location_customers') def wiz_put_in_pack(self, picking): """ Helper to use the 'choose.delivery.package' wizard in order to call the 'action_put_in_pack' method. """ wiz_action = picking.action_put_in_pack() self.assertEqual(wiz_action['res_model'], 'choose.delivery.package', 'Wrong wizard returned') wiz = Form(self.env[wiz_action['res_model']].with_context(wiz_action['context']).create({ 'delivery_package_type_id': picking.carrier_id.fedex_default_package_type_id.id })) choose_delivery_carrier = wiz.save() choose_delivery_carrier.action_put_in_pack() def test_01_fedex_basic_us_domestic_flow(self): try: SaleOrder = self.env['sale.order'] sol_vals = {'product_id': self.iPadMini.id, 'name': "[A1232] iPad Mini", 'product_uom': self.uom_unit.id, 'product_uom_qty': 1.0, 'price_unit': self.iPadMini.lst_price} so_vals = {'partner_id': self.delta_pc.id, 'order_line': [(0, None, sol_vals)]} sale_order = SaleOrder.create(so_vals) # I add delivery cost in Sales order delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ 'default_order_id': sale_order.id, 'default_carrier_id': self.env.ref('delivery_fedex.delivery_carrier_fedex_us').id })) choose_delivery_carrier = delivery_wizard.save() choose_delivery_carrier.update_price() self.assertGreater(choose_delivery_carrier.delivery_price, 0.0, "FedEx delivery cost for this SO has not been correctly estimated.") choose_delivery_carrier.button_confirm() sale_order.action_confirm() self.assertEqual(len(sale_order.picking_ids), 1, "The Sales Order did not generate a picking.") picking = sale_order.picking_ids[0] self.assertEqual(picking.carrier_id.id, sale_order.carrier_id.id, "Carrier is not the same on Picking and on SO.") picking.move_ids[0].quantity = 1.0 picking.move_ids[0].picked = True self.assertGreater(picking.shipping_weight, 0.0, "Picking weight should be positive.") picking._action_done() self.assertIsNot(picking.carrier_tracking_ref, False, "FedEx did not return any tracking number") self.assertGreater(picking.carrier_price, 0.0, "FedEx carrying price is probably incorrect") picking.cancel_shipment() self.assertFalse(picking.carrier_tracking_ref, "Carrier Tracking code has not been properly deleted") self.assertEqual(picking.carrier_price, 0.0, "Carrier price has not been properly deleted") except UserError as e: if e.args[0].strip() in SKIPPABLE_ERRORS: raise unittest.SkipTest(SKIP_MSG) else: raise e def test_02_fedex_basic_international_flow(self): try: SaleOrder = self.env['sale.order'] sol_vals = {'product_id': self.iPadMini.id, 'name': "[A1232] Large Cabinet", 'product_uom': self.uom_unit.id, 'product_uom_qty': 1.0, 'price_unit': self.iPadMini.lst_price} so_vals = {'partner_id': self.agrolait.id, 'order_line': [(0, None, sol_vals)]} sale_order = SaleOrder.create(so_vals) # I add delivery cost in Sales order delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ 'default_order_id': sale_order.id, 'default_carrier_id': self.env.ref('delivery_fedex.delivery_carrier_fedex_inter').id, })) choose_delivery_carrier = delivery_wizard.save() choose_delivery_carrier.update_price() self.assertGreater(choose_delivery_carrier.delivery_price, 0.0, "FedEx delivery cost for this SO has not been correctly estimated.") choose_delivery_carrier.button_confirm() sale_order.action_confirm() self.assertEqual(len(sale_order.picking_ids), 1, "The Sales Order did not generate a picking.") picking = sale_order.picking_ids[0] self.assertEqual(picking.carrier_id.id, sale_order.carrier_id.id, "Carrier is not the same on Picking and on SO.") picking.move_ids[0].quantity = 1.0 picking.move_ids[0].picked = True self.assertGreater(picking.shipping_weight, 0.0, "Picking weight should be positive.") picking._action_done() self.assertIsNot(picking.carrier_tracking_ref, False, "FedEx did not return any tracking number") self.assertGreater(picking.carrier_price, 0.0, "FedEx carrying price is probably incorrect") picking.cancel_shipment() self.assertFalse(picking.carrier_tracking_ref, "Carrier Tracking code has not been properly deleted") self.assertEqual(picking.carrier_price, 0.0, "Carrier price has not been properly deleted") except UserError as e: if e.args[0].strip() in SKIPPABLE_ERRORS: raise unittest.SkipTest(SKIP_MSG) else: raise e def test_03_fedex_multipackage_international_flow(self): try: SaleOrder = self.env['sale.order'] sol_1_vals = {'product_id': self.iPadMini.id, 'name': "[A1232] iPad Mini", 'product_uom': self.uom_unit.id, 'product_uom_qty': 1.0, 'price_unit': self.iPadMini.lst_price} sol_2_vals = {'product_id': self.large_desk.id, 'name': "[A1090] Large Desk", 'product_uom': self.uom_unit.id, 'product_uom_qty': 1.0, 'price_unit': self.large_desk.lst_price} so_vals = {'partner_id': self.agrolait.id, 'order_line': [(0, None, sol_1_vals), (0, None, sol_2_vals)]} sale_order = SaleOrder.create(so_vals) # I add delivery cost in Sales order delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ 'default_order_id': sale_order.id, 'default_carrier_id': self.env.ref('delivery_fedex.delivery_carrier_fedex_inter').id })) choose_delivery_carrier = delivery_wizard.save() choose_delivery_carrier.update_price() self.assertGreater(choose_delivery_carrier.delivery_price, 0.0, "FedEx delivery cost for this SO has not been correctly estimated.") choose_delivery_carrier.button_confirm() sale_order.action_confirm() self.assertEqual(len(sale_order.picking_ids), 1, "The Sales Order did not generate a picking.") picking = sale_order.picking_ids[0] self.assertEqual(picking.carrier_id.id, sale_order.carrier_id.id, "Carrier is not the same on Picking and on SO.") move0 = picking.move_ids[0] move0.quantity = 1.0 move0.picked = True self.wiz_put_in_pack(picking) move1 = picking.move_ids[1] move1.quantity = 1.0 move1.picked = True self.wiz_put_in_pack(picking) self.assertEqual(len(picking.move_line_ids.mapped('result_package_id')), 2, "2 Packages should have been created at this point") self.assertGreater(picking.shipping_weight, 0.0, "Picking weight should be positive.") picking._action_done() self.assertIsNot(picking.carrier_tracking_ref, False, "FedEx did not return any tracking number") self.assertGreater(picking.carrier_price, 0.0, "FedEx carrying price is probably incorrect") picking.cancel_shipment() self.assertFalse(picking.carrier_tracking_ref, "Carrier Tracking code has not been properly deleted") self.assertEqual(picking.carrier_price, 0.0, "Carrier price has not been properly deleted") except UserError as e: if e.args[0].strip() in SKIPPABLE_ERRORS: raise unittest.SkipTest(SKIP_MSG) else: raise e def test_04_fedex_international_delivery_from_delivery_order(self): StockPicking = self.env['stock.picking'] order1_vals = { 'product_id': self.iPadMini.id, 'name': "[A1232] iPad Mini", 'product_uom': self.uom_unit.id, 'product_uom_qty': 1.0, 'location_id': self.stock_location.id, 'location_dest_id': self.customer_location.id} do_vals = { 'partner_id': self.agrolait.id, 'carrier_id': self.env.ref('delivery_fedex.delivery_carrier_fedex_inter').id, 'location_id': self.stock_location.id, 'location_dest_id': self.customer_location.id, 'picking_type_id': self.env.ref('stock.picking_type_out').id, 'state': 'draft', 'move_ids_without_package': [(0, None, order1_vals)]} delivery_order = StockPicking.create(do_vals) self.assertEqual(delivery_order.state, 'draft', 'Shipment state should be draft.') delivery_order.action_confirm() self.assertEqual(delivery_order.state, 'assigned', 'Shipment state should be ready(assigned).') delivery_order.move_ids_without_package.quantity = 1.0 delivery_order.button_validate() self.assertEqual(delivery_order.state, 'done', 'Shipment state should be done.') @tagged('standard', '-external') class TestMockDeliveryFedex(TestDeliveryFedex): @contextmanager def patch_fedex_requests(self): """ Mock context for requests to the fedex API. """ class MockedSession: def __init__(self, *args, **kwargs): self.headers = dict() def mount(self, *args, **kwargs): return None def close(self, *args, **kwargs): return None def post(self, *args, **kwargs): response = Mock() response.headers = {} response.status_code = 200 if b'SUCCESSSUCCESSship0000SuccessSuccess234ship2800d7c541d1044e0079c63225709trueFDXEFEDEX0201794668707764PRIORITY_OVERNIGHT01longutf-8FedEx Priority Overnight\xc3\x82\xc2\xaelongasciiFedEx Priority Overnightmediumutf-8FedEx Priority Overnight\xc3\x82\xc2\xaemediumasciiFedEx Priority Overnightshortutf-8P-1shortasciiP-1abbrvasciiPOPriority OvernightP1FEDEX_BOX03longutf-8FedEx\xc3\x82\xc2\xae BoxlongasciiFedEx Boxmediumutf-8FedEx\xc3\x82\xc2\xae BoxmediumasciiFedEx Boxsmallutf-8BoxsmallasciiBoxshortutf-8BoxshortasciiBoxabbrvasciiBFedEx BoxFDX BOXEP1000000060DELIVER_WEEKDAY02longutf-8Deliver WeekdaylongasciiDeliver Weekdaymediumutf-8Deliver WeekdaymediumasciiDeliver Weekdayshortutf-8WDYshortasciiWDYXGUSCA JCCA 0A1USCA 0A1SC2022-09-26MON2022-09-26MONfalseMON - 26 SEP 10:30APRIORITY OVERNIGHT29201SCUSCAE0103PAYOR_ACCOUNT_PACKAGEPAYOR_ACCOUNT_PACKAGE161808PACKAGEPACKAGING_MINIMUM019.5LB2.0USD101.28USD0.0USD101.28USD19.75USD121.03USD0.0USD121.03USD0.0USD0.0USD0.0USD0.0USD121.03FUELFuelUSD19.751FEDEX02017946687077640PAYOR_ACCOUNT_PACKAGEPAYOR_ACCOUNT_PACKAGEPACKAGING_MINIMUMLB2.0USD101.28USD0.0USD101.28USD19.75USD121.03USD0.0USD121.03USD0.0FUELFuelUSD19.752TRK#302015XG USCA 710018975434800029201007946687077648581J1/EC8C/FE2D107946 6870 776412MON - 26 SEP 10:30A13PRIORITY OVERNIGHT152920116SC-US17CAECOMMON_2DWyk+HjAxHTAyMjkyMDEdODQwHTAxHTc5NDY2ODcwNzc2NDAyMDEdRkRFHTYwMTM1NjgwNR0yNjYdHTEvMR0wLjcyTEIdTh0xNTE1IE1haW4gU3RyZWV0HUNvbHVtYmlhHVNDHVJlYWR5IE1hdB4wNh0xMFpFRDAwOB0xMlo4MDM4NzM2MTI2HTE1WjExODY3OTY4NR0yMFocHTMxWjEwMDE4OTc1NDM0ODAwMDI5MjAxMDA3OTQ2Njg3MDc3NjQdMzJaMDIdMzRaMDMdMzlaSkNDQR1LUzAwNDAyHR4wOR1GRFgdeh04HQ4sOBw2MH9AHgQ=FEDEX_1D1001897543480002920100794668707764SERVICE_DEFAULT' elif b'SUCCESSSUCCESSship0000SuccessSuccess234ship2800' elif b'WARNINGWARNINGcrs396The returned rate types are in the requested preferred currency; preferred rates not returned.The returned rate types are in the requested preferred currency; preferred rates not returned.S00402crs3100PRIORITY_OVERNIGHTPRIORITY_OVERNIGHT01longutf-8FedEx Priority Overnight\xc3\x82\xc2\xaelongasciiFedEx Priority Overnightmediumutf-8FedEx Priority Overnight\xc3\x82\xc2\xaemediumasciiFedEx Priority Overnightshortutf-8P-1shortasciiP-1abbrvasciiPOPriority OvernightP1FEDEX_BOXCAEfalseA1A1SERVICE_DEFAULTPAYOR_ACCOUNT_PACKAGEPAYOR_ACCOUNT_PACKAGE161808PACKAGEPACKAGING_MINIMUM019.5LB2.0USD101.28USD0.0USD101.28USD19.75USD121.03USD0.0USD121.03USD0.0USD0.0USD0.0USD0.0USD121.03FUELFuelUSD19.750PAYOR_ACCOUNT_PACKAGEPACKAGING_MINIMUMLB2.0USD101.28USD0.0USD101.28USD19.75USD121.03USD0.0USD121.03USD0.0FUELFuelUSD19.75' return response # zeep.Client.transport is using post from requests.Session with patch('zeep.transports.requests.Session') as mocked_session: mocked_session.side_effect = MockedSession yield mocked_session def test_01_fedex_basic_us_domestic_flow(self): with self.patch_fedex_requests(): super().test_01_fedex_basic_us_domestic_flow() def test_02_fedex_basic_international_flow(self): with self.patch_fedex_requests(): super().test_02_fedex_basic_international_flow() def test_03_fedex_multipackage_international_flow(self): with self.patch_fedex_requests(): super().test_03_fedex_multipackage_international_flow() def test_04_fedex_international_delivery_from_delivery_order(self): with self.patch_fedex_requests(): super().test_04_fedex_international_delivery_from_delivery_order() def test_05_fedex_multistep_delivery_tracking(self): with self.patch_fedex_requests(): # Set Warehouse as multi steps delivery self.env.ref("stock.warehouse0").delivery_steps = "pick_pack_ship" sale_order = self.env['sale.order'].create({ 'partner_id': self.agrolait.id, 'order_line': [(0, None, { 'product_id': self.iPadMini.id, 'name': "[A1232] iPad Mini", 'product_uom': self.uom_unit.id, 'product_uom_qty': 1.0, 'price_unit': self.iPadMini.lst_price, })], }) # I add delivery cost in Sales order delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ 'default_order_id': sale_order.id, 'default_carrier_id': self.env.ref('delivery_fedex.delivery_carrier_fedex_inter').id })) choose_delivery_carrier = delivery_wizard.save() choose_delivery_carrier.update_price() choose_delivery_carrier.button_confirm() # Confirm the picking and send to shipper sale_order.action_confirm() picking = sale_order.picking_ids[0] picking.move_ids.quantity = 1.0 picking.move_ids.picked = True picking._action_done() picking.send_to_shipper() for p in sale_order.picking_ids: self.assertTrue(any("Tracking Numbers:" in m for m in p.message_ids.mapped('preview')))