390 lines
17 KiB
Python
390 lines
17 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import json
|
|
from contextlib import contextmanager
|
|
from unittest.mock import patch
|
|
|
|
import requests
|
|
|
|
from odoo import Command
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.fields import Domain
|
|
from odoo.tests import TransactionCase, tagged
|
|
from odoo.addons.delivery_envia.models.envia_request import STATE_CODE_MAP_ENVIA, Envia
|
|
|
|
|
|
@contextmanager
|
|
def _mock_envia_call(specific_envia_check=None):
|
|
def _mock_request(*args, **kwargs):
|
|
method = kwargs.get('method') or args[1]
|
|
url = kwargs.get('url') or args[2]
|
|
responses = {
|
|
'GET': {
|
|
'available-service': {
|
|
'data': [
|
|
{'carrier_id': 103, 'carrier_name': 'ups', 'id': 240, 'name': 'saver', 'description': 'UPS Express Saver', 'international': False},
|
|
{'carrier_id': 109, 'carrier_name': 'shippify', 'id': 255, 'name': 'express', 'description': 'Shippify Express', 'international': False},
|
|
{'carrier_id': 109, 'carrier_name': 'shippify', 'id': 256, 'name': 'slots', 'description': 'Shippify Slots', 'international': True},
|
|
{'carrier_id': 113, 'carrier_name': 'Jadlog', 'id': 265, 'name': 'expresso', 'description': 'Expresso', 'international': False},
|
|
]
|
|
},
|
|
'additional-services': {
|
|
'data': [
|
|
{"name": "insurance", "description": "Insurance", "childs": [{"id": 14, "name": "insurance", "description": "Description", "json_structure": ""}]},
|
|
{"name": "liftgate_delivery", "description": "Liftgate Delivery", "childs": [{"id": 60, "name": "liftgate_delivery", "description": "Description", "json_structure": ""}]},
|
|
{"name": "lifgate_pickup", "description": "Lifgate Pickup", "childs": [{"id": 63, "name": "liftgate_pickup", "description": "Description", "json_structure": ""}]},
|
|
{"name": "pickup_residential", "description": "Pickup Residential", "childs": [{"id": 62, "name": "pickup_residential_zone", "description": "Pickup Residential Zone", "json_structure": ""}]},
|
|
{"name": "delivery_residential", "description": "Delivery Residential", "childs": [{"id": 61, "name": "delivery_residential_zone", "description": "Delivery Residential Zone", "json_structure": ""}]}
|
|
]
|
|
},
|
|
'generic-form': [
|
|
{'fieldId': 'address1', 'fieldName': 'street', 'rules': {'required': True, 'validationType': 'street'}},
|
|
{'fieldId': 'address2', 'fieldName': 'number', 'rules': {'required': False, 'validationType': 'value'}},
|
|
{'fieldId': 'postalCode', 'fieldName': 'postal_code', 'rules': {'required': True, 'max': '20', 'validationType': 'value'}},
|
|
{'fieldId': 'city', 'fieldName': 'city', 'rules': {'required': True, 'max': '50', 'validationType': 'value'}},
|
|
{'fieldId': 'city_select', 'fieldName': 'city_select', 'rules': {'required': False, 'max': '50'}},
|
|
{'fieldId': 'state', 'fieldName': 'state', 'rules': {'required': True, 'min': '2', 'max': '3', 'validationType': 'select'}},
|
|
{'fieldId': 'reference', 'fieldName': 'reference', 'rules': {'required': False, 'max': '50'}}
|
|
],
|
|
'zipcode/CO/730001': [{
|
|
'zip_code': '730001',
|
|
'country': {'name': 'Colombia', 'code': 'CO'},
|
|
'state': {'name': 'Tolima', 'code': {'1digit': None, '2digit': 'TO', '3digit': 'TOL'}},
|
|
'locality': 'San Antonio',
|
|
'suburbs': ['73675000'],
|
|
'info': {'stat': '73001', 'stat_8digit': '73001000'},
|
|
'regions': {'region_1': 'Tolima', 'region_2': 'Ibagué', 'region_3': 'Ibagué'},
|
|
}],
|
|
'uploads/ups': ['WyJtb2NrTGFiZWw9PT09Il0=']
|
|
},
|
|
'POST': {
|
|
'ship/rate': {
|
|
'meta': 'rate',
|
|
'data': [
|
|
{'carrier': 'ups', 'carrierDescription': 'UPS', 'carrierId': 103, 'serviceId': 240, 'quantity': 1, 'basePrice': 4.60, 'totalPrice': 4.60}
|
|
]
|
|
},
|
|
'ship/generate': {
|
|
'meta': 'generate',
|
|
'data': [
|
|
{
|
|
'carrier': 'ups',
|
|
'service': 'saver',
|
|
'shipmentId': 1890000,
|
|
'trackingNumber': '1Z48746Q48746',
|
|
'trackUrl': 'https://test.envia.com/rastreo?label=1Z48746Q48746&cntry_code=us',
|
|
'label': 'https://s3.us-east-2.amazonaws.com/envia-staging/uploads/ups/1Z48746Q487462219663ea5a6a7da1.png',
|
|
'additionalFiles': [],
|
|
'totalPrice': 5.20,
|
|
'currency': 'USD'
|
|
}
|
|
]
|
|
},
|
|
}
|
|
}
|
|
|
|
for endpoint, content in responses[method].items():
|
|
if endpoint in url:
|
|
if specific_envia_check:
|
|
specific_envia_check(endpoint, json.loads(kwargs.get('data') or '{}'))
|
|
response = requests.Response()
|
|
response._content = json.dumps(content).encode()
|
|
response.status_code = 200
|
|
return response
|
|
|
|
raise Exception('unhandled request url %s' % url)
|
|
|
|
with patch.object(requests.Session, 'request', _mock_request):
|
|
yield
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestDeliveryEnvia(TransactionCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.your_company = cls.env.ref("base.main_partner")
|
|
cls.your_company.write({
|
|
'name': 'Odoo BR',
|
|
'country_id': cls.env.ref('base.br').id,
|
|
'street': 'Praça Mauá 1',
|
|
'street2': 'Curitaba',
|
|
'state_id': cls.env.ref('base.state_br_rj').id,
|
|
'city': 'Rio de Janeiro',
|
|
'zip': '20081-240',
|
|
'phone': '+55 11 96123-4567',
|
|
})
|
|
cls.br_partner = cls.env['res.partner'].create({
|
|
'name': 'Odoo BR Partner',
|
|
'country_id': cls.env.ref('base.br').id,
|
|
'street': 'Av. Presidente Vargas 592',
|
|
'street2': 'Curitaba',
|
|
'state_id': cls.env.ref('base.state_br_rj').id,
|
|
'city': 'Rio de Janeiro',
|
|
'zip': '30071-001',
|
|
'phone': '+55 11 96123-4567',
|
|
})
|
|
# partner in us (azure)
|
|
cls.us_partner = cls.env['res.partner'].create({
|
|
'name': 'Azure Interior',
|
|
'is_company': True,
|
|
'street': '4557 De Silva St',
|
|
'city': 'Fremont',
|
|
'country_id': cls.env.ref('base.us').id,
|
|
'zip': '94538',
|
|
'state_id': cls.env.ref('base.state_us_5').id,
|
|
'email': '[email protected]',
|
|
'phone': '(870)-931-0505',
|
|
})
|
|
cls.co_partner = cls.env['res.partner'].create({
|
|
'name': 'Colombia Partner',
|
|
'street': 'Crr 14 no. 149 - 75 int 10 Apto 402',
|
|
'street2': 'Conjunto Monterrey, el salado',
|
|
'city': 'Ibagué',
|
|
'zip': '730001',
|
|
'country_id': cls.env.ref('base.co').id,
|
|
'state_id': cls.env.ref('base.state_co_14').id,
|
|
'email': '[email protected]',
|
|
'phone': '+57 310 3460237',
|
|
})
|
|
cls.cl_partner = cls.env['res.partner'].create({
|
|
'name': 'Chile Partner',
|
|
'street': 'Avenida Providencia 1432, Depto 402',
|
|
'street2': '',
|
|
'city': 'Santiago',
|
|
'zip': '8320000',
|
|
'country_id': cls.env.ref('base.cl').id,
|
|
'state_id': cls.env.ref('base.state_cl_13').id,
|
|
'email': '[email protected]',
|
|
'phone': '+56 9 4012 3456',
|
|
})
|
|
|
|
cls.product_to_ship1 = cls.env["product.product"].create({
|
|
'name': 'Door with Legs',
|
|
'type': 'consu',
|
|
'weight': 10.0
|
|
})
|
|
|
|
cls.product_to_ship2 = cls.env["product.product"].create({
|
|
'name': 'Door with Arms',
|
|
'type': 'consu',
|
|
'weight': 15.0
|
|
})
|
|
|
|
cls.envia = cls.env.ref('delivery_envia.delivery_carrier_envia')
|
|
|
|
cls.envia.write({
|
|
'envia_production_api_key': 'mock_key',
|
|
'envia_sandbox_api_key': 'mock_key',
|
|
'envia_service_code': 'saver',
|
|
'envia_carrier_code': 'ups'
|
|
})
|
|
|
|
def test_rate_order(self):
|
|
""" Set up a sale order for an BR client and ensure that the rate is computed properly. """
|
|
sale_order = self.env['sale.order'].create({
|
|
'partner_id': self.br_partner.id,
|
|
'order_line': [
|
|
Command.create({
|
|
'product_id': self.product_to_ship1.id
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product_to_ship2.id
|
|
})
|
|
]
|
|
})
|
|
wiz_action = sale_order.action_open_delivery_wizard()
|
|
choose_delivery_carrier = self.env[wiz_action['res_model']].with_context(wiz_action['context']).create({
|
|
'carrier_id': self.envia.id,
|
|
'order_id': sale_order.id
|
|
})
|
|
|
|
with _mock_envia_call():
|
|
choose_delivery_carrier.update_price()
|
|
self.assertEqual(choose_delivery_carrier.delivery_price, 4.60)
|
|
|
|
def test_rate_order_without_address(self):
|
|
""" Set up a sale order without address and ensure that the rate computation fails. """
|
|
partner = self.env['res.partner'].create({
|
|
'name': 'Test Partner',
|
|
'email': '[email protected]',
|
|
'phone': '(870)-931-0505',
|
|
})
|
|
|
|
sale_order = self.env['sale.order'].create({
|
|
'partner_id': partner.id,
|
|
'order_line': [
|
|
Command.create({
|
|
'product_id': self.product_to_ship1.id
|
|
})
|
|
]
|
|
})
|
|
wiz_action = sale_order.action_open_delivery_wizard()
|
|
choose_delivery_carrier = self.env[wiz_action['res_model']].with_context(wiz_action['context']).create({
|
|
'carrier_id': self.envia.id,
|
|
'order_id': sale_order.id
|
|
})
|
|
|
|
with _mock_envia_call():
|
|
with self.assertRaises(ValidationError):
|
|
choose_delivery_carrier.update_price()
|
|
|
|
def test_envia_code_map_data_integrity(self):
|
|
"""
|
|
Check the integrity of data used in the STATE_CODE_MAP_ENVIA used to convert
|
|
Odoo's nomenclature to envia's one.
|
|
"""
|
|
country_codes, state_codes = map(set, zip(*STATE_CODE_MAP_ENVIA))
|
|
state_domain = Domain([('code', 'in', state_codes), ('country_id.code', 'in', country_codes)])
|
|
state_match = self.env['res.country.state'].search(state_domain).grouped('code')
|
|
|
|
def is_valid(country_code, state_code):
|
|
return any(state.country_id.code == country_code for state in state_match[state_code])
|
|
|
|
self.assertTrue(
|
|
all(is_valid(country_code, state_code) for country_code, state_code in STATE_CODE_MAP_ENVIA),
|
|
'Each country/state code combination must be represented by an existing state in Odoo'
|
|
)
|
|
|
|
def test_prepare_address_values_uses_envia_codes(self):
|
|
envia_request = Envia(self.envia, prod_environment=True, debug_logger=lambda *args, **kwargs: None)
|
|
|
|
# Colombia
|
|
with _mock_envia_call():
|
|
address = envia_request._prepare_address_values(self.co_partner, is_cust=True)
|
|
|
|
self.assertEqual(address['city'], '73001000')
|
|
self.assertEqual(address['city_select'], 'Ibagué')
|
|
self.assertEqual(address['postalCode'], '73001000')
|
|
self.assertEqual(address['state'], self.co_partner.state_id.code)
|
|
|
|
# Chile
|
|
with _mock_envia_call():
|
|
address = envia_request._prepare_address_values(self.cl_partner, is_cust=True)
|
|
|
|
self.assertEqual(address['city'], 'Santiago')
|
|
self.assertEqual(address['postalCode'], '8320000')
|
|
# Check that the envia state code is used
|
|
country_code, state_code = self.cl_partner.country_id.code, self.cl_partner.state_id.code
|
|
self.assertEqual(address['state'], STATE_CODE_MAP_ENVIA.get((country_code, state_code)))
|
|
|
|
def test_shipping_order(self):
|
|
""" Ensure that the shipping of an order works properly. """
|
|
sale_order = self.env['sale.order'].create({
|
|
'partner_id': self.br_partner.id,
|
|
'order_line': [
|
|
Command.create({
|
|
'product_id': self.product_to_ship1.id
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product_to_ship2.id
|
|
})
|
|
]
|
|
})
|
|
wiz_action = sale_order.action_open_delivery_wizard()
|
|
choose_delivery_carrier = self.env[wiz_action['res_model']].with_context(wiz_action['context']).create({
|
|
'carrier_id': self.envia.id,
|
|
'order_id': sale_order.id
|
|
})
|
|
with _mock_envia_call():
|
|
choose_delivery_carrier.update_price()
|
|
choose_delivery_carrier.button_confirm()
|
|
sale_order.action_confirm()
|
|
self.assertGreater(len(sale_order.picking_ids), 0, "The Sales Order did not generate pickings for shipment.")
|
|
|
|
picking = sale_order.picking_ids[0]
|
|
self.assertEqual(picking.carrier_id.id, sale_order.carrier_id.id, "The carrier is not the same on Picking and on SO.")
|
|
picking.action_assign()
|
|
picking.move_ids.picked = True
|
|
self.assertGreater(picking.weight, 0.0, "The picking weight should be positive.")
|
|
|
|
picking._action_done()
|
|
self.assertEqual(picking.carrier_tracking_ref, "1Z48746Q48746", "The Envia Parcel Reference is not correct.")
|
|
|
|
# Check that we the PDF is there with the correct title.
|
|
pdf = picking.message_ids.attachment_ids.filtered(lambda m: m.description == 'LabelShipping-envia-1Z48746Q48746.PDF')
|
|
self.assertNotEqual(pdf, self.env['ir.attachment'], "The label should be present as a pdf attachment.")
|
|
|
|
def test_shipping_with_insurance(self):
|
|
"""
|
|
Ensure insurance information is included when getting a rate and booking the shipment.
|
|
"""
|
|
def insurance_format_check(endpoint, payload):
|
|
if endpoint in ['ship/rate', 'ship/generate']:
|
|
insurance_service = [
|
|
s for s in payload['packages'][0]['additionalServices']
|
|
if s['service'] == 'envia_insurance' and s.get('data', {}).get('amount', '0') == '40.0'
|
|
]
|
|
self.assertTrue(insurance_service)
|
|
|
|
self.envia.shipping_insurance = 50
|
|
sale_order = self.env['sale.order'].create({
|
|
'partner_id': self.br_partner.id,
|
|
'order_line': [
|
|
Command.create({
|
|
'product_id': self.product_to_ship1.id,
|
|
'price_unit': 42,
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product_to_ship2.id,
|
|
'price_unit': 38,
|
|
})
|
|
]
|
|
})
|
|
wiz_action = sale_order.action_open_delivery_wizard()
|
|
choose_delivery_carrier = self.env[wiz_action['res_model']].with_context(wiz_action['context']).create({
|
|
'carrier_id': self.envia.id,
|
|
'order_id': sale_order.id
|
|
})
|
|
with _mock_envia_call(insurance_format_check):
|
|
choose_delivery_carrier.update_price()
|
|
choose_delivery_carrier.button_confirm()
|
|
sale_order.action_confirm()
|
|
self.assertGreater(len(sale_order.picking_ids), 0)
|
|
|
|
picking = sale_order.picking_ids[0]
|
|
picking.action_assign()
|
|
picking.move_ids.picked = True
|
|
self.assertGreater(picking.weight, 0.0)
|
|
|
|
picking._action_done()
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestDeliveryEnviaWithLocalisations(TestDeliveryEnvia):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
required_locas = [
|
|
'l10n_co_edi',
|
|
]
|
|
|
|
if not all(loca.state == 'installed' for loca in cls.env['ir.module.module'].search([('name', 'in', required_locas)])):
|
|
cls.skipTest(cls, "This class requires for localisations to be installed")
|
|
|
|
cls.co_partner_2 = cls.env['res.partner'].create({
|
|
'name': 'Colombia Partner in Medellin',
|
|
'street': 'Cra. 25a #1 A Sur 45 local 1009',
|
|
'street2': '',
|
|
'city': '',
|
|
'zip': '050011',
|
|
'city_id': cls.env.ref('l10n_co_edi.city_co_01').id,
|
|
'country_id': cls.env.ref('base.co').id,
|
|
'state_id': cls.env.ref('base.state_co_01').id,
|
|
'email': '[email protected]',
|
|
'phone': '+57 310 3460239',
|
|
})
|
|
|
|
def test_prepare_address_values(self):
|
|
envia_request = Envia(self.envia, prod_environment=True, debug_logger=lambda *args, **kwargs: None)
|
|
|
|
with _mock_envia_call():
|
|
address = envia_request._prepare_address_values(self.co_partner_2, is_cust=True)
|
|
|
|
self.assertEqual(address['city'], '05001000')
|
|
self.assertEqual(address['city_select'], 'MEDELLÍN')
|
|
self.assertEqual(address['postalCode'], '05001000')
|
|
self.assertEqual(address['state'], self.co_partner_2.state_id.code)
|