mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
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:
3
delivery_gso/__init__.py
Normal file
3
delivery_gso/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
26
delivery_gso/__manifest__.py
Normal file
26
delivery_gso/__manifest__.py
Normal 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
190
delivery_gso/i18n/es.po
Normal 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"
|
||||
3
delivery_gso/models/__init__.py
Normal file
3
delivery_gso/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||
|
||||
from . import delivery_gso
|
||||
462
delivery_gso/models/delivery_gso.py
Normal file
462
delivery_gso/models/delivery_gso.py
Normal 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
|
||||
49
delivery_gso/models/requests_gso.py
Normal file
49
delivery_gso/models/requests_gso.py
Normal 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)
|
||||
28
delivery_gso/views/delivery_gso_view.xml
Normal file
28
delivery_gso/views/delivery_gso_view.xml
Normal 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>
|
||||
Reference in New Issue
Block a user