Files
suite/delivery_gls_nl/models/delivery_gls_nl.py
2022-05-31 22:02:13 +00:00

312 lines
13 KiB
Python

# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from .gls_nl_request import GLSNLRequest
from requests import HTTPError
from base64 import decodebytes, b64decode
from csv import reader as csv_reader
class PackageType(models.Model):
_inherit = 'stock.package.type'
package_carrier_type = fields.Selection(selection_add=[('gls_nl', 'GLS Netherlands')])
class ProviderGLSNL(models.Model):
_inherit = 'delivery.carrier'
GLS_NL_SOFTWARE_NAME = 'Odoo'
GLS_NL_SOFTWARE_VER = '15.0'
GLS_NL_COUNTRY_NOT_FOUND = 'GLS_NL_COUNTRY_NOT_FOUND'
delivery_type = fields.Selection(selection_add=[('gls_nl', 'GLS Netherlands')],
ondelete={'gls_nl': lambda recs: recs.write({'delivery_type': 'fixed', 'fixed_price': 0})})
gls_nl_username = fields.Char(string='GLS NL Username', groups='base.group_system')
gls_nl_password = fields.Char(string='GLS NL Password', groups='base.group_system')
gls_nl_labeltype = fields.Selection([
('zpl', 'ZPL'),
('pdf', 'PDF'),
], string='GLS NL Label Type')
gls_nl_express = fields.Selection([
('t9', 'Delivery before 09:00 on weekdays'),
('t12', 'Delivery before 12:00 on weekdays'),
('t17', 'Delivery before 17:00 on weekdays'),
('s9', 'Delivery before 09:00 on Saturday'),
('s12', 'Delivery before 12:00 on Saturday'),
('s17', 'Delivery before 17:00 on Saturday'),
], string='GLS NL Express', help='Express service tier (leave blank for regular)')
gls_nl_rate_id = fields.Many2one('ir.attachment', string='GLS NL Rates')
def button_gls_nl_test_rates(self):
self.ensure_one()
if not self.gls_nl_rate_id:
raise UserError(_('No GLS NL Rate file is attached.'))
rate_data = self._gls_nl_process_rates()
weight_col_count = len(rate_data['w'])
row_count = len(rate_data['r'])
country_col = rate_data['c']
country_model = self.env['res.country']
for row in rate_data['r']:
country = country_model.search([('code', '=', row[country_col])], limit=1)
if not country:
raise ValidationError(_('Could not locate country by code: "%s" for row: %s') % (row[country_col], row))
for w, i in rate_data['w'].items():
try:
cost = float(row[i])
except ValueError:
raise ValidationError(_('Could not process cost for row: %s') % (row, ))
raise ValidationError(_('Looks good! %s weights, %s countries located.') % (weight_col_count, row_count))
def _gls_nl_process_rates(self):
"""
'w' key will be weights to row index map
'c' key will be the country code index
'r' key will be rows from the original that can use indexes above
:return:
"""
self = self.sudo()
datab = decodebytes(self.gls_nl_rate_id.datas)
csv_data = datab.decode()
csv_data = csv_data.replace('\r', '')
csv_lines = csv_data.splitlines()
header = [csv_lines[0]]
body = csv_lines[1:]
data = {'w': {}, 'r': []}
for row in csv_reader(header):
for i, col in enumerate(row):
if col == 'Country':
data['c'] = i
else:
try:
weight = float(col)
data['w'][weight] = i
except ValueError:
pass
if 'c' not in data:
raise ValidationError(_('Could not locate "Country" column.'))
if not data['w']:
raise ValidationError(_('Could not locate any weight columns.'))
for row in csv_reader(body):
data['r'].append(row)
return data
def _gls_nl_rate(self, country_code, weight):
if weight < 0.0:
return 0.0
rate_data = self._gls_nl_process_rates()
country_col = rate_data['c']
rate = None
country_found = False
for row in rate_data['r']:
if row[country_col] == country_code:
country_found = True
for w, i in rate_data['w'].items():
if weight <= w:
try:
rate = float(row[i])
break
except ValueError:
pass
else:
# our w, i will be the last weight and rate.
try:
# Return Max rate + remaining weight rated
return float(row[i]) + self._gls_nl_rate(country_code, weight-w)
except ValueError:
pass
break
if rate is None and not country_found:
return self.GLS_NL_COUNTRY_NOT_FOUND
return rate
def gls_nl_rate_shipment(self, order):
recipient = self.get_recipient(order=order)
rate = None
dest_country = recipient.country_id.code
est_weight_value = sum([(line.product_id.weight * line.product_uom_qty) for line in order.order_line]) or 0.0
if dest_country:
rate = self._gls_nl_rate(dest_country, est_weight_value)
# Handle errors and rate conversions.
error_message = None
if not dest_country or rate == self.GLS_NL_COUNTRY_NOT_FOUND:
error_message = _('Destination country not found: "%s"') % (dest_country, )
if rate is None or error_message:
if not error_message:
error_message = _('Rate not found for weight: "%s"') % (est_weight_value, )
return {'success': False,
'price': 0.0,
'error_message': error_message,
'warning_message': False}
euro_currency = self.env['res.currency'].search([('name', '=', 'EUR')], limit=1)
if euro_currency and order.currency_id and euro_currency != order.currency_id:
rate = euro_currency._convert(rate,
order.currency_id,
order.company_id,
order.date_order or fields.Date.today())
return {'success': True,
'price': rate,
'error_message': False,
'warning_message': False}
def _get_gls_nl_service(self):
return GLSNLRequest(self.prod_environment)
def _gls_nl_make_address(self, partner):
# Addresses look like
# {
# 'name1': '',
# 'name2': '',
# 'name3': '',
# 'street': '',
# 'houseNo': '',
# 'houseNoExt': '',
# 'zipCode': '',
# 'city': '',
# 'countrycode': '',
# 'contact': '',
# 'phone': '',
# 'email': '',
# }
address = {}
pieces = partner.street.split(' ')
street = ' '.join(pieces[:-1]).strip(' ,')
house = pieces[-1]
address['name1'] = partner.name
address['street'] = street
address['houseNo'] = house
if partner.street2:
address['houseNoExt'] = partner.street2
address['zipCode'] = partner.zip
address['city'] = partner.city
address['countrycode'] = partner.country_id.code
if partner.phone:
address['phone'] = partner.phone
if partner.email:
address['email'] = partner.email
return address
def gls_nl_send_shipping(self, pickings):
res = []
sudoself = self.sudo()
service = sudoself._get_gls_nl_service()
for picking in pickings:
#company = self.get_shipper_company(picking=picking) # Requester not needed currently
from_ = self.get_shipper_warehouse(picking=picking)
to = self.get_recipient(picking=picking)
total_rate = 0.0
request_body = {
'labelType': sudoself.gls_nl_labeltype,
'username': sudoself.gls_nl_username,
'password': sudoself.gls_nl_password,
'shiptype': 'p', # note not shipType, 'f' for Freight
'trackingLinkType': 's',
# 'customerNo': '', # needed if there are more 'customer numbers' attached to a single MyGLS API Account
'reference': picking.name,
'addresses': {
'pickupAddress': self._gls_nl_make_address(from_),
'deliveryAddress': self._gls_nl_make_address(to),
#'requesterAddress': {}, # Not needed currently
},
'units': [],
'services': {},
'shippingDate': fields.Date.to_string(fields.Date.today()),
'shippingSystemName': self.GLS_NL_SOFTWARE_NAME,
'shippingSystemVersion': self.GLS_NL_SOFTWARE_VER,
}
if sudoself.gls_nl_express:
request_body['services']['expressService'] = sudoself.gls_nl_express
# Build out units
# Units look like:
# {
# 'unitId': 'A',
# 'unitType': '', # only for freight
# 'weight': 0.0,
# 'additionalInfo1': '',
# 'additionalInfo2': '',
# }
if picking.package_ids:
for package in picking.package_ids:
converted_weight = self._gls_convert_weight(package.shipping_weight)
rate = self._gls_nl_rate(to.country_id.code, converted_weight or 0.0)
if rate and rate != self.GLS_NL_COUNTRY_NOT_FOUND:
total_rate += rate
unit = {
'unitId': package.name,
'weight': converted_weight
}
request_body['units'].append(unit)
else:
converted_weight = self._gls_convert_weight(picking.shipping_weight)
rate = self._gls_nl_rate(to.country_id.code, converted_weight or 0.0)
if rate and rate != self.GLS_NL_COUNTRY_NOT_FOUND:
total_rate += rate
unit = {
'unitId': picking.name,
'weight': converted_weight,
}
request_body['units'].append(unit)
try:
# Create label
label = service.create_label(request_body)
trackings = []
uniq_nos = []
attachments = []
for i, unit in enumerate(label['units'], 1):
trackings.append(unit['unitNo'])
uniq_nos.append(unit['uniqueNo'])
label_data = unit.get('label')
if label_data and sudoself.gls_nl_labeltype == 'pdf':
label_data = b64decode(label_data)
attachments.append(('LabelGLSNL-%s-%s.%s' % (unit['unitNo'], i, sudoself.gls_nl_labeltype), label_data))
tracking = ', '.join(set(trackings))
logmessage = _("Shipment created into GLS NL<br/>"
"<b>Tracking Number:</b> %s<br/>"
"<b>UniqueNo:</b> %s") % (tracking, ', '.join(set(uniq_nos)))
picking.message_post(body=logmessage, attachments=attachments)
shipping_data = {'exact_price': total_rate, 'tracking_number': tracking}
res.append(shipping_data)
except HTTPError as e:
raise ValidationError(e)
return res
def gls_nl_get_tracking_link(self, pickings):
return 'https://gls-group.eu/EU/en/parcel-tracking?match=%s' % pickings.carrier_tracking_ref
def gls_nl_cancel_shipment(self, picking):
sudoself = self.sudo()
service = sudoself._get_gls_nl_service()
try:
request_body = {
'unitNo': picking.carrier_tracking_ref,
'username': sudoself.gls_nl_username,
'password': sudoself.gls_nl_password,
'shiptype': 'p',
'shippingSystemName': self.GLS_NL_SOFTWARE_NAME,
'shippingSystemVersion': self.GLS_NL_SOFTWARE_VER,
}
service.delete_label(request_body)
picking.message_post(body=_('Shipment N° %s has been cancelled' % picking.carrier_tracking_ref))
picking.write({'carrier_tracking_ref': '', 'carrier_price': 0.0})
except HTTPError as e:
raise ValidationError(e)
def _gls_convert_weight(self, weight):
get_param = self.env['ir.config_parameter'].sudo().get_param
product_weight_in_lbs_param = get_param('product.weight_in_lbs')
if product_weight_in_lbs_param == '1':
return weight / 2.20462
else:
return weight