Merge branch 'mig/16.0/delivery_gso' into '16.0-test'

mig/16.0/delivery_gso into 16.0-test

See merge request hibou-io/hibou-odoo/suite!1589
This commit is contained in:
Hibou Bot
2022-10-28 23:12:03 +00:00
7 changed files with 761 additions and 0 deletions

3
delivery_gso/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import models

View File

@@ -0,0 +1,26 @@
{
'name': 'Golden State Overnight (gso.com) Shipping',
'summary': 'Send your shippings through gso.com and track them online.',
'version': '16.0.1.0.1',
'author': "Hibou Corp.",
'category': 'Warehouse',
'license': 'OPL-1',
'images': [],
'website': "https://hibou.io",
'description': """
Golden State Overnight (gso.com) Shipping
=========================================
* Provides estimates on shipping costs through gso.com.
* Send your shippings through gso.com and allows tracking of packages.
""",
'depends': [
'delivery_hibou',
],
'demo': [],
'data': [
'views/delivery_gso_view.xml',
],
'auto_install': False,
'installable': True,
}

190
delivery_gso/i18n/es.po Normal file
View File

@@ -0,0 +1,190 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * delivery_gso
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-29 23:44+0000\n"
"PO-Revision-Date: 2021-10-29 23:44+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_service_type__sam
msgid "AM Select (8A-12P) Delivery Window"
msgstr "Rango de Entrega: En la Mañana (8A - 12P)"
#. module: delivery_gso
#: model:ir.model.fields,field_description:delivery_gso.field_stock_package_type__package_carrier_type
msgid "Carrier"
msgstr "Transportista"
#. module: delivery_gso
#: model:ir.model.fields,field_description:delivery_gso.field_delivery_carrier__gso_default_packaging_id
msgid "Default Package Type"
msgstr "Tipo de Paquete Predeterminado"
#. module: delivery_gso
#: code:addons/delivery_gso/models/delivery_gso.py:0
#, python-format
msgid "Delivery Method not found in result"
msgstr "Método de envío no se encontro en la busqueda"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_service_type__eps
msgid "Early Priority Overnight"
msgstr "Entrega Temprana Prioritaria: Al día siguiente "
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_service_type__ess
msgid "Early Saturday Delivery"
msgstr "Entrega el Sábado Temprano"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_service_type__sev
msgid "Evening Select (4P-8P) Delivery Window"
msgstr "Ventana de Entrega: En la Noche (4P - 8P)"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_service_type__cps
msgid "GSO Ground"
msgstr "GSO Terrestre"
#. module: delivery_gso
#: code:addons/delivery_gso/models/delivery_gso.py:0
#, python-format
msgid "GSO web service returned an error. "
msgstr "El servicio de web de GSO ha retornado un error"
#. module: delivery_gso
#: code:addons/delivery_gso/models/delivery_gso.py:0
#, python-format
msgid "GSO web service returned an error."
msgstr "El servicio de web de GSO ha retornado un error"
#. module: delivery_gso
#: model:ir.model.fields,field_description:delivery_gso.field_delivery_carrier__gso_image_type
msgid "Image Type"
msgstr "Tipo de Imagen"
#. module: delivery_gso
#: model:ir.model.fields,help:delivery_gso.field_delivery_carrier__gso_image_type
msgid "Image Type is the type of Label to use"
msgstr "El tipo de imagen es el tipo de etiqueta para utilizar"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_image_type__zpl_long_label
msgid "Long label"
msgstr "Etiqueta larga"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_image_type__no_label
msgid "No Label"
msgstr "Sin etiqueta"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_service_type__nps
msgid "Noon Priority Overnight"
msgstr "Entrega Prioritaria: Al Día Siguiente en la Tarde"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_service_type__spm
msgid "PM Select (12P-4P) Delivery Window"
msgstr "Ventana de Entrega: En la tarde (12P - 4P)"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_image_type__paper_label
msgid "Paper Label"
msgstr "Etiqueta de papel"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_service_type__pds
msgid "Priority Overnight"
msgstr "Entrega Prioritaria: Al Día Siguiente"
#. module: delivery_gso
#: model:ir.model.fields,field_description:delivery_gso.field_delivery_carrier__delivery_type
msgid "Provider"
msgstr "Proveedor"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_service_type__sds
msgid "Saturday Delivery"
msgstr "Entrega el Sábado"
#. module: delivery_gso
#: model:ir.model.fields,field_description:delivery_gso.field_delivery_carrier__gso_service_type
msgid "Service Type"
msgstr "Tipo de Servicio"
#. module: delivery_gso
#: model:ir.model.fields,help:delivery_gso.field_delivery_carrier__gso_service_type
msgid "Service Type determines speed of delivery"
msgstr "El tipo de servicio determina la velocidad de entrega"
#. module: delivery_gso
#: code:addons/delivery_gso/models/delivery_gso.py:0
#, python-format
msgid "Shipment N° %s has been cancelled"
msgstr "El número de envío %s ha sido cancelado"
#. module: delivery_gso
#: code:addons/delivery_gso/models/delivery_gso.py:0
#, python-format
msgid "Shipment created into GSO<br/><b>Tracking Numbers:</b> %s"
msgstr ""
"El envío ha sido creado en GSO<br/><b> Números de Seguimiento: </b> %s"
#. module: delivery_gso
#: model:ir.model,name:delivery_gso.model_delivery_carrier
msgid "Shipping Methods"
msgstr "Métodos de Envío"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__gso_image_type__zpl_short_label
msgid "Short Label"
msgstr "Etiqueta corta"
#. module: delivery_gso
#: model:ir.model,name:delivery_gso.model_stock_package_type
msgid "Stock package type"
msgstr "Tipo de Paquete de Stock"
#. module: delivery_gso
#: code:addons/delivery_gso/models/delivery_gso.py:0
#: code:addons/delivery_gso/models/delivery_gso.py:0
#, python-format
msgid "TotalCharge not found."
msgstr "El CostoTotal no se encontró"
#. module: delivery_gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__delivery_carrier__delivery_type__gso
#: model:ir.model.fields.selection,name:delivery_gso.selection__stock_package_type__package_carrier_type__gso
msgid "gso.com"
msgstr "gso.com"
#. module: delivery_gso
#: model:ir.model.fields,field_description:delivery_gso.field_delivery_carrier__gso_account_number
msgid "gso.com Account Number"
msgstr "gso.com Número de Cuenta"
#. module: delivery_gso
#: model_terms:ir.ui.view,arch_db:delivery_gso.view_delivery_carrier_form_with_provider_gso
msgid "gso.com Configuration"
msgstr "gso.com Configuración"
#. module: delivery_gso
#: model:ir.model.fields,field_description:delivery_gso.field_delivery_carrier__gso_password
msgid "gso.com Password"
msgstr "gso.com Clave"
#. module: delivery_gso
#: model:ir.model.fields,field_description:delivery_gso.field_delivery_carrier__gso_username
msgid "gso.com Username"
msgstr "gso.com Usuario"

View File

@@ -0,0 +1,3 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import delivery_gso

View File

@@ -0,0 +1,462 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
import pytz
from math import ceil
from base64 import b64decode
from requests import HTTPError
from hashlib import sha1
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError, UserError
from .requests_gso import GSORequest
import logging
_logger = logging.getLogger(__name__)
GSO_TZ = 'PST8PDT'
def inline_b64decode(data):
try:
return b64decode(data)
except:
return ''
class StockPackageType(models.Model):
_inherit = 'stock.package.type'
package_carrier_type = fields.Selection(selection_add=[('gso', 'gso.com')], ondelete={'gso': 'set default'})
class ProviderGSO(models.Model):
_inherit = 'delivery.carrier'
delivery_type = fields.Selection(selection_add=[('gso', 'gso.com')], ondelete={'gso': 'cascade'})
gso_username = fields.Char(string='gso.com Username', groups='base.group_system')
gso_password = fields.Char(string='gso.com Password', groups='base.group_system')
gso_account_number = fields.Char(string='gso.com Account Number', groups='base.group_system')
gso_default_packaging_id = fields.Many2one('stock.package.type', string='Default Package Type')
# For service type, SAM, SPM, and SEV require authorized accounts.
gso_service_type = fields.Selection([('PDS', 'Priority Overnight'),
('EPS', 'Early Priority Overnight'),
('NPS', 'Noon Priority Overnight'),
('SDS', 'Saturday Delivery'),
('ESS', 'Early Saturday Delivery'),
('CPS', 'GSO Ground'),
('SAM', 'AM Select (8A-12P) Delivery Window'),
('SPM', 'PM Select (12P-4P) Delivery Window'),
('SEV', 'Evening Select (4P-8P) Delivery Window'),
],
string="Service Type", default="CPS", help="Service Type determines speed of delivery")
gso_image_type = fields.Selection([('NO_LABEL', 'No Label'),
('PAPER_LABEL', 'Paper Label'),
('ZPL_SHORT_LABEL', 'Short Label'),
('ZPL_LONG_LABEL', 'Long label'),
],
string="Image Type", default="ZPL_SHORT_LABEL", help="Image Type is the type of Label to use")
def _get_gso_service(self):
return GSORequest(self.prod_environment,
self.gso_username,
self.gso_password,
self.gso_account_number)
def _gso_make_ship_address(self, partner):
# Addresses look like
# {
# 'ShipToCompany': '',
# 'ShipToAttention': '',
# 'ShipToPhone': '',
# 'ShipToEmail': '',
# 'DeliveryAddress1': '',
# 'DeliveryAddress2': '',
# 'DeliveryCity': '',
# 'DeliveryState': '',
# 'DeliveryZip': '',
# }
address = {}
# ShipToCompany is required. ShipToAttention which is a person is not.
if partner.name and not partner.parent_id:
address['ShipToCompany'] = partner.name
if partner.name and partner.parent_id:
address['ShipToCompany'] = partner.parent_id.name # or partner.parent_id.id.name ??
address['ShipToAttention'] = partner.name
if partner.phone:
address['ShipToPhone'] = partner.phone
if partner.email:
address['ShipToEmail'] = partner.email
if partner.street:
address['DeliveryAddress1'] = partner.street
if partner.street2:
address['DeliveryAddress2'] = partner.street2
if partner.city:
address['DeliveryCity'] = partner.city
if partner.state_id:
address['DeliveryState'] = partner.state_id.code
if partner.zip:
address['DeliveryZip'] = partner.zip
return address
def _gso_make_shipper_address(self, warehouse, company):
# Addresses look like
# {
# 'ShipperCompany': '',
# 'ShipperContact': '',
# 'ShipperPhone': '',
# 'ShipperEmail': '',
# 'PickupAddress1': '',
# 'PickupAddress2': '',
# 'PickupCity': '',
# 'PickupState': '',
# 'PickupZip': '',
# }
address = {}
if company.name and not company.parent_id:
address['ShipperCompany'] = company.name
if company.name and company.parent_id:
address['ShipperCompany'] = company.parent_id.name
address['ShipperContact'] = company.name
if warehouse.phone:
address['ShipperPhone'] = warehouse.phone
if warehouse.email:
address['ShipperEmail'] = warehouse.email
if warehouse.street:
address['PickupAddress1'] = warehouse.street
if warehouse.street2:
address['PickupAddress2'] = warehouse.street2
if warehouse.city:
address['PickupCity'] = warehouse.city
if warehouse.state_id:
address['PickupState'] = warehouse.state_id.code
if warehouse.zip:
address['PickupZip'] = warehouse.zip
return address
def _gso_create_tracking_number(self, identifier):
# Override for a more 'customized' tracking number
# Expects a self.sudo()
if not identifier:
identifier = fields.Datetime.now() # string in Odoo 11
salt = self.env['ir.config_parameter'].sudo().get_param('database.secret')
sha = sha1((identifier + salt).encode()).hexdigest()
return sha[:20]
def _gso_get_package_dimensions(self, package=None):
if not package:
package_type = self.gso_default_packaging_id
else:
package_type = package.package_type_id
length_uom = self.env['product.template']._get_length_uom_id_from_ir_config_parameter()
if length_uom.name == 'ft':
return {'Length': round(package_type.packaging_length / 12.0), 'Width': round(package_type.width / 12.0), 'Height': round(package_type.height / 12.0)}
elif length_uom.name == 'mm':
return {'Length': round(package_type.packaging_length * 0.0393701), 'Width': round(package_type.width * 0.0393701), 'Height': round(package_type.height * 0.0393701)}
return {'Length': package_type.packaging_length, 'Width': package_type.width, 'Height': package_type.height}
def _gso_convert_weight(self, weight_in_db):
weight_uom = self.env['product.template']._get_weight_uom_id_from_ir_config_parameter()
if weight_uom.name == 'kg':
weight_in_lb = weight_in_db / 0.45359237
else:
# assume lbs
weight_in_lb = weight_in_db
# If less than 8 oz...
if weight_in_lb < 0.5:
return 0
else:
# Round up to nearest lb
return int(ceil(weight_in_lb))
def gso_send_shipping(self, pickings):
res = []
sudoself = self.sudo()
service = sudoself._get_gso_service()
for picking in pickings:
company = self.get_shipper_company(picking=picking)
from_ = self.get_shipper_warehouse(picking=picking)
to = self.get_recipient(picking=picking)
address_type = 'B' if "company" in (to.company_type, to.parent_id.company_type) else 'R'
request_body = {
'AccountNumber': sudoself.gso_account_number,
'Shipment': {
'ServiceCode': sudoself.gso_service_type,
'ShipmentLabelType': sudoself.gso_image_type,
'SignatureCode': 'SIG_NOT_REQD',
'DeliveryAddressType': address_type,
# 'ShipDate': fields.Date.today(), # safer not to send in case you want to ship on a weekend
},
}
request_body['Shipment'].update(self._gso_make_shipper_address(from_, company))
request_body['Shipment'].update(self._gso_make_ship_address(to))
cost = 0.0
labels = {
'thermal': [],
'paper': [],
}
picking_packages = picking.package_ids
package_carriers = picking_packages.mapped('carrier_id')
if package_carriers:
# only ship ours
picking_packages = picking_packages.filtered(lambda p: p.carrier_id == self and not p.carrier_tracking_ref)
if picking_packages:
# Every package will be a transaction
for package in picking_packages:
# Use Sale Order Number or fall back to Picking
shipment_ref = (picking.sale_id.name if picking.sale_id else picking.name) + '-' + package.name
insurance_value = sudoself.get_insurance_value(picking=picking, package=package)
if insurance_value > 100.0:
# Documentation says to set DeclaredValue ONLY if over $100.00
request_body['Shipment']['DeclaredValue'] = insurance_value
elif 'DeclaredValue' in request_body['Shipment']:
del request_body['Shipment']['DeclaredValue']
if sudoself.get_signature_required(picking=picking, package=package):
request_body['Shipment']['SignatureCode'] = 'SIG_REQD'
else:
request_body['Shipment']['SignatureCode'] = 'SIG_NOT_REQD'
request_body['Shipment']['Weight'] = self._gso_convert_weight(package.shipping_weight)
request_body['Shipment'].update(self._gso_get_package_dimensions(package))
request_body['Shipment']['ShipmentReference'] = package.name
request_body['Shipment']['TrackingNumber'] = self._gso_create_tracking_number(package.name)
try:
response = service.post_shipment(request_body)
if response.get('ThermalLabel'):
labels['thermal'].append((response['TrackingNumber'], response['ThermalLabel']))
elif response.get('PaperLabel'):
labels['paper'].append((response['TrackingNumber'], response['PaperLabel']))
if response.get('ShipmentCharges', {}).get('TotalCharge'):
cost += response['ShipmentCharges']['TotalCharge']
except HTTPError as e:
raise ValidationError(e)
elif not package_carriers:
# ship the whole picking
shipment_ref = picking.sale_id.name if picking.sale_id else picking.name
request_body['Shipment']['Weight'] = self._gso_convert_weight(picking.shipping_weight)
request_body['Shipment'].update(self._gso_get_package_dimensions())
request_body['Shipment']['ShipmentReference'] = shipment_ref
request_body['Shipment']['TrackingNumber'] = self._gso_create_tracking_number(picking.name)
try:
response = service.post_shipment(request_body)
if response.get('ThermalLabel'):
labels['thermal'].append((response['TrackingNumber'], response['ThermalLabel']))
elif response.get('PaperLabel'):
labels['paper'].append((response['TrackingNumber'], response['PaperLabel']))
if response.get('ShipmentCharges', {}).get('TotalCharge'):
cost += response['ShipmentCharges']['TotalCharge']
except HTTPError as e:
raise ValidationError(e)
else:
continue
# Handle results
trackings = [l[0] for l in labels['thermal']] + [l[0] for l in labels['paper']]
carrier_tracking_ref = ','.join(trackings)
logmessage = _("Shipment created into GSO<br/>"
"<b>Tracking Numbers:</b> %s") % (carrier_tracking_ref, )
attachments = []
if labels['thermal']:
attachments += [('LabelGSO-%s.zpl' % (l[0], ), l[1]) for l in labels['thermal']]
if labels['paper']:
# paper labels re-encoded base64
attachments += [('LabelGSO-%s.png' % (l[0], ), inline_b64decode(l[1])) for l in labels['paper']]
picking.message_post(body=logmessage, attachments=attachments)
shipping_data = {'exact_price': cost,
'tracking_number': carrier_tracking_ref}
res.append(shipping_data)
return res
def gso_cancel_shipment(self, picking):
sudoself = self.sudo()
service = sudoself._get_gso_service()
try:
request_body = {
'AccountNumber': sudoself.gso_account_number,
}
for tracking in picking.carrier_tracking_ref.split(','):
request_body['TrackingNumber'] = tracking
__ = service.delete_shipment(request_body)
except HTTPError as e:
raise ValidationError(e)
picking.message_post(body=_('Shipment N° %s has been cancelled') % (picking.carrier_tracking_ref, ))
picking.write({'carrier_tracking_ref': '', 'carrier_price': 0.0})
def gso_rate_shipment(self, order):
sudoself = self.sudo()
service = sudoself._get_gso_service()
from_ = sudoself.get_shipper_warehouse(order=order)
to = sudoself.get_recipient(order=order)
address_type = 'B' if "company" in (to.company_type, to.parent_id.company_type) else 'R'
est_weight_value = self._gso_convert_weight(
sum([(line.product_id.weight * line.product_uom_qty) for line in order.order_line]) or 0.0)
date_planned = None
if self.env.context.get('date_planned'):
date_planned = self.env.context.get('date_planned')
ship_date_utc = fields.Datetime.from_string(date_planned if date_planned else fields.Datetime.now())
ship_date_utc = ship_date_utc.replace(tzinfo=pytz.utc)
ship_date_gso = ship_date_utc.astimezone(pytz.timezone(GSO_TZ))
ship_date_gso = fields.Datetime.to_string(ship_date_gso)
request_body = {
'AccountNumber': sudoself.gso_account_number,
'OriginZip': from_.zip,
'DestinationZip': to.zip,
'ShipDate': ship_date_gso,
'PackageDimension': self._gso_get_package_dimensions(),
'PackageWeight': est_weight_value,
'DeliveryAddressType': address_type,
}
result = service.get_rates_and_transit_time(request_body)
delivery = list(filter(lambda d: d['ServiceCode'] == sudoself.gso_service_type, result['DeliveryServiceTypes']))
if delivery:
delivery = delivery[0]
delivery_date_gso = delivery['DeliveryDate'].replace('T', ' ')
delivery_date_gso = fields.Datetime.from_string(delivery_date_gso)
delivery_date_gso = delivery_date_gso.replace(tzinfo=pytz.timezone(GSO_TZ))
delivery_date_utc = delivery_date_gso.astimezone(pytz.utc)
delivery_date_utc = fields.Datetime.to_string(delivery_date_utc)
price = delivery.get('ShipmentCharges', {}).get('TotalCharge', 0.0)
return {
'success': True,
'price': price,
'error_message': False,
'date_delivered': delivery_date_utc,
'warning_message': _('TotalCharge not found.') if price == 0.0 else False,
}
raise Exception()
return {
'success': False,
'price': 0.0,
'error_message': _('Delivery Method not found in result'),
'warning_message': False,
}
def gso_get_tracking_link(self, pickings):
# No way to get a link specifically as their site only allows POST into tracking form.
res = []
for _ in pickings:
res.append('https://www.gso.com/Tracking')
return res
def gso_rate_shipment_multi(self, order=None, picking=None, packages=None):
if not packages:
return self._gso_rate_shipment_multi_package(order=order, picking=picking)
else:
rates = []
for package in packages:
rates += self._gso_rate_shipment_multi_package(order=order, picking=picking, package=package)
return rates
def _gso_rate_shipment_multi_package(self, order=None, picking=None, package=None):
sudoself = self.sudo()
try:
service = sudoself._get_gso_service()
except HTTPError as e:
_logger.error(e)
return [{
'success': False,
'price': 0.0,
'error_message': _('GSO web service returned an error. ' + str(e)),
'warning_message': False,
}]
from_ = sudoself.get_shipper_warehouse(order=order, picking=picking)
to = sudoself.get_recipient(order=order, picking=picking)
address_type = 'B' if bool(to.is_company or to.parent_id.is_company) else 'R'
package_dimensions = self._gso_get_package_dimensions(package=package)
date_planned = fields.Datetime.now()
if self.env.context.get('date_planned'):
date_planned = self.env.context.get('date_planned')
ship_date_utc = fields.Datetime.from_string(date_planned if date_planned else fields.Datetime.now())
ship_date_utc = ship_date_utc.replace(tzinfo=pytz.utc)
ship_date_gso = ship_date_utc.astimezone(pytz.timezone(GSO_TZ))
ship_date_gso = fields.Datetime.to_string(ship_date_gso)
if order:
est_weight_value = self._gso_convert_weight(
sum([(line.product_id.weight * line.product_uom_qty) for line in order.order_line]) or 0.0)
elif not package:
est_weight_value = self._gso_convert_weight(picking.shipping_weight)
else:
est_weight_value = self._gso_convert_weight(package.shipping_weight or package.weight)
request_body = {
'AccountNumber': sudoself.gso_account_number,
'OriginZip': from_.zip,
'DestinationZip': to.zip,
'ShipDate': ship_date_gso,
'PackageDimension': package_dimensions,
'PackageWeight': est_weight_value,
'DeliveryAddressType': address_type,
}
try:
result = service.get_rates_and_transit_time(request_body)
# _logger.warning('GSO result:\n%s' % result)
except HTTPError as e:
# _logger.error(e)
return [{
'success': False,
'price': 0.0,
'error_message': _('GSO web service returned an error.'),
'warning_message': False,
}]
# delivery = list(filter(lambda d: d['ServiceCode'] == sudoself.gso_service_type, result['DeliveryServiceTypes']))
# if delivery:
rates = []
for delivery in result['DeliveryServiceTypes']:
delivery_date_gso = delivery['DeliveryDate'].replace('T', ' ')
delivery_date_gso = fields.Datetime.from_string(delivery_date_gso)
delivery_date_gso = delivery_date_gso.replace(tzinfo=pytz.timezone(GSO_TZ))
delivery_date_utc = delivery_date_gso.astimezone(pytz.utc)
delivery_date_utc = fields.Datetime.to_string(delivery_date_utc)
price = delivery.get('ShipmentCharges', {}).get('TotalCharge', 0.0)
carrier = self.gso_find_delivery_carrier_for_service(delivery['ServiceCode'])
if carrier:
rates.append({
'carrier': carrier,
'package': package or self.env['stock.quant.package'].browse(),
'success': True,
'price': price,
'error_message': False,
'warning_message': _('TotalCharge not found.') if price == 0.0 else False,
'date_planned': date_planned,
'date_delivered': delivery_date_utc,
'transit_days': False,
'service_code': delivery['ServiceCode'],
})
return rates
def gso_find_delivery_carrier_for_service(self, service_code):
if self.gso_service_type == service_code:
return self
# arbitrary decision, lets find the same account number
carrier = self.search([('gso_account_number', '=', self.gso_account_number),
('gso_service_type', '=', service_code)
], limit=1)
return carrier

View File

@@ -0,0 +1,49 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
import requests
from json import dumps
class GSORequest:
BASE_URL = 'https://api.gso.com/Rest/v1'
def __init__(self, production, username, password, account_number):
self.username = username
self.password = password
self.account_number = account_number
self.headers = self.make_headers()
self._get_token()
def make_headers(self):
return {
'Content-Type': 'application/json',
'Accept-Encoding': 'gzip',
'UserName': self.username,
'PassWord': self.password,
'AccountNumber': self.account_number,
}
# Token Lasts 12 hours and should be refreshed accordingly.
# Might need to change to prevent too many calls to the API
def _get_token(self):
endpoint_url = self.BASE_URL + '/token'
response = requests.get(endpoint_url, headers=self.headers)
response.raise_for_status()
self.headers.update({'Token': response.headers['Token']})
def call(self, http_method, endpoint_url, payload):
url = self.BASE_URL + endpoint_url
result = requests.request(http_method, url, data=dumps(payload), headers=self.headers)
if result.status_code != 200:
raise requests.exceptions.HTTPError(result.text)
return result.json()
def post_shipment(self, request_body):
return self.call('POST', '/Shipment', request_body)
def delete_shipment(self, request_body):
return self.call('DELETE', '/Shipment', request_body)
def get_rates_and_transit_time(self, request_body):
return self.call('POST', '/RatesAndTransitTimes', request_body)

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_delivery_carrier_form_with_provider_gso" model="ir.ui.view">
<field name="name">delivery.carrier.form.provider.gso</field>
<field name="model">delivery.carrier</field>
<field name="inherit_id" ref="delivery.view_delivery_carrier_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='destination']" position='before'>
<page string="gso.com Configuration" attrs="{'invisible': [('delivery_type', '!=', 'gso')]}">
<group>
<group>
<field name="gso_username" attrs="{'required': [('delivery_type', '=', 'gso')]}" />
<field name="gso_password" attrs="{'required': [('delivery_type', '=', 'gso')]}" password="True"/>
<field name="gso_account_number" attrs="{'required': [('delivery_type', '=', 'gso')]}" />
</group>
<group>
<field name="gso_service_type" attrs="{'required': [('delivery_type', '==', 'gso')]}"/>
<field name="gso_default_packaging_id" attrs="{'required': [('delivery_type', '==', 'gso')]}"/>
<field name="gso_image_type" attrs="{'required': [('delivery_type', '==', 'gso')]}"/>
</group>
</group>
</page>
</xpath>
</field>
</record>
</odoo>