317 lines
12 KiB
Python
317 lines
12 KiB
Python
# This file is part of the carrier_send_shipments module for Tryton.
|
|
# The COPYRIGHT file at the top level of this repository contains
|
|
# the full copyright notices and license terms.
|
|
import logging
|
|
import tempfile
|
|
import base64
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.model import fields
|
|
from trytond.transaction import Transaction
|
|
from trytond.i18n import gettext
|
|
from trytond.exceptions import UserError
|
|
from trytond.modules.carrier_send_shipments.tools import unaccent, unspaces
|
|
from .utils import nacex_call
|
|
|
|
__all__ = ['ShipmentOut']
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ShipmentOut(metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.out'
|
|
nacex_envase = fields.Selection('get_nacex_envase', 'Nacex Envase')
|
|
nacex_set_pickup_address = fields.Boolean('Nacex Set Pickup Address',
|
|
help="NACEX: Set to true, if the warehouse address is differnet from "
|
|
"the one setted in Nacex register as pickup.")
|
|
nacex_tip_ea = fields.Selection([
|
|
(None, ''),
|
|
('N', 'Without ealerta'),
|
|
('S', 'Ealerta informed by SMS'),
|
|
('E', 'Ealerta informed by EMAIL'),
|
|
], 'Nacex Type eAlerta', sort=False)
|
|
nacex_ealerta = fields.Char('Nacex eAlerta')
|
|
nacex_frec_codigo = fields.Selection([
|
|
(None, ''),
|
|
('1', 'Interdia or Puente Urbano: morning'),
|
|
('2', 'Interdia or Puente Urbano: late'),
|
|
('8', 'Puente urbano night'),
|
|
('9', 'Interdia aerial'),
|
|
], 'Nacex Freq Codigo', sort=False)
|
|
nacex_ret = fields.Selection([
|
|
('N', 'N'),
|
|
('S', 'S'),
|
|
], 'Nacex Ret', sort=False)
|
|
nacex_ref_cli = fields.Char('Nacex Ref Cli')
|
|
|
|
@staticmethod
|
|
def get_nacex_envase():
|
|
Api = Pool().get('carrier.api')
|
|
selection = Api.fields_get(
|
|
['nacex_envase'])['nacex_envase']['selection']
|
|
return [(None, '')] + selection
|
|
|
|
@staticmethod
|
|
def default_nacex_envase():
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_nacex_set_pickup_address():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_nacex_tip_ea():
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_nacex_frec_codigo():
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_nacex_ret():
|
|
return 'N'
|
|
|
|
@fields.depends('nacex_tip_ea', 'customer')
|
|
def on_change_nacex_tip_ea(self):
|
|
if self.nacex_tip_ea == 'S':
|
|
self.nacex_ealerta = self.customer.mobile
|
|
if self.nacex_tip_ea == 'E':
|
|
self.nacex_ealerta = self.customer.email
|
|
|
|
@classmethod
|
|
def send_nacex(cls, api, shipments):
|
|
'''
|
|
Send shipments out to nacex
|
|
:param api: obj
|
|
:param shipments: list
|
|
Return references, labels, errors
|
|
'''
|
|
pool = Pool()
|
|
CarrierApi = pool.get('carrier.api')
|
|
ShipmentOut = pool.get('stock.shipment.out')
|
|
Uom = pool.get('product.uom')
|
|
Date = pool.get('ir.date')
|
|
|
|
references = []
|
|
labels = []
|
|
errors = []
|
|
|
|
default_service = CarrierApi.get_default_carrier_service(api)
|
|
|
|
for shipment in shipments:
|
|
service = (shipment.carrier_service or shipment.carrier.service or
|
|
default_service)
|
|
if not service:
|
|
message = gettext(
|
|
'carrier_send_shipments_nacex.msg_nacex_add_services')
|
|
errors.append(message)
|
|
continue
|
|
|
|
if api.reference_origin and hasattr(shipment, 'origin'):
|
|
code = (shipment.origin and shipment.origin.rec_name or
|
|
shipment.number)
|
|
else:
|
|
code = shipment.number
|
|
|
|
packages = shipment.number_packages
|
|
if not packages or packages == 0:
|
|
packages = 1
|
|
|
|
weight = 1
|
|
if api.weight and hasattr(shipment, 'manual_weight'):
|
|
weight = shipment.manual_weight or shipment.weight
|
|
if not weight:
|
|
weight = 1
|
|
|
|
if api.weight_api_unit:
|
|
if shipment.weight_uom:
|
|
weight = Uom.compute_qty(
|
|
shipment.weight_uom, weight, api.weight_api_unit)
|
|
elif api.weight_unit:
|
|
weight = Uom.compute_qty(
|
|
api.weight_unit, weight, api.weight_api_unit)
|
|
|
|
# weight must integer value, not float
|
|
weight = int(round(weight))
|
|
weight = 1 if weight == 0 else weight
|
|
|
|
if shipment.warehouse.address:
|
|
waddress = shipment.warehouse.address
|
|
else:
|
|
waddresses = api.company.party.addresses
|
|
if not waddresses:
|
|
raise UserError(gettext(
|
|
'carrier_send_shipments_nacex.'
|
|
'msg_missing_warehouse_address'))
|
|
waddress = waddresses[0]
|
|
|
|
data = {}
|
|
data['del_cli'] = api.nacex_delegacion[:4]
|
|
data['num_cli'] = api.nacex_abonado[:5]
|
|
data['fec'] = Date.today().strftime("%d/%m/%Y")
|
|
data['tip_ser'] = service.code
|
|
# TODO: Tipo de cobro
|
|
# 'O', Origen: Factura la agencia origen del envío
|
|
# 'D', Destino: Factura la agencia de entrega del envío
|
|
# 'T': Tercera: Factura una tercera agencia
|
|
data['tip_cob'] = 'O'
|
|
data['ref_cli'] = shipment.nacex_ref_cli and shipment.nacex_ref_cli[:20] or code[:20]
|
|
data['tip_env'] = shipment.nacex_envase or api.nacex_envase or '2'
|
|
data['bul'] = packages
|
|
data['kil'] = str(weight)
|
|
data['nacex_ret'] = shipment.nacex_ret or 'N'
|
|
|
|
# 1 Interdia or Puente Urbano: frequency 1 (morning)
|
|
# 2 Interdia or Puente Urbano: frequency 2 (late)
|
|
# 8 Puente urbano night.
|
|
# 9 Interdía aerial.
|
|
if shipment.nacex_frec_codigo:
|
|
data['frec_codigo'] = shipment.nacex_frec_codigo
|
|
|
|
# N Without ealerta.
|
|
# S Ealerta informed by SMS.
|
|
# E Ealerta informed by EMAIL.
|
|
data['tip_ea'] = shipment.nacex_tip_ea or 'N'
|
|
if shipment.nacex_tip_ea == 'S' and shipment.nacex_ealerta:
|
|
data['ealerta'] = shipment.nacex_ealerta
|
|
elif shipment.nacex_tip_ea == 'E' and shipment.nacex_ealerta:
|
|
data['ealerta'] = shipment.nacex_ealerta
|
|
|
|
if shipment.nacex_set_pickup_address:
|
|
data['dir_rec'] = unaccent(waddress.street.replace('\n', ' - ')
|
|
)[:60].rstrip()
|
|
data['cp_rec'] = unaccent(waddress.postal_code)[:8]
|
|
data['pob_rec'] = unaccent(waddress.city)[:30]
|
|
data['pais_rec'] = unaccent(waddress.country and
|
|
waddress.country.code or '')
|
|
data['tel_rec'] = unspaces(api.phone or
|
|
shipment.company.party.phone or '')[:35]
|
|
data['nom_ent'] = unaccent(shipment.customer.name)[:35]
|
|
data['per_ent'] = unaccent((shipment.delivery_address.party_name
|
|
or shipment.customer.name))[:35]
|
|
data['dir_ent'] = unaccent(
|
|
shipment.delivery_address.street.replace('\n', ' - ')
|
|
)[:60].rstrip()
|
|
data['cp_ent'] = unaccent(
|
|
shipment.delivery_address.postal_code)[:60]
|
|
data['pob_ent'] = unaccent(shipment.delivery_address.city)[:30]
|
|
data['pais_ent'] = unaccent(shipment.delivery_address.country
|
|
and shipment.delivery_address.country.code or '')
|
|
data['tel_ent'] = unspaces(shipment.customer.mobile or
|
|
shipment.customer.phone or '')[:15]
|
|
if shipment.carrier_notes:
|
|
data['obs1'] = unaccent(shipment.carrier_notes)[:38].rstrip()
|
|
|
|
resp = nacex_call(api, 'putExpedicion', data)
|
|
values = resp.text.split('|')
|
|
|
|
if len(values) == 1 or resp.status_code != 200:
|
|
message = gettext(
|
|
'carrier_send_shipments_nacex.msg_nacex_connection_error',
|
|
name=shipment.rec_name,
|
|
error=resp.text)
|
|
errors.append(message)
|
|
continue
|
|
|
|
if values[0] == 'ERROR':
|
|
message = gettext(
|
|
'carrier_send_shipments_nacex.msg_nacex_not_send_error',
|
|
name=shipment.rec_name,
|
|
error=resp.text)
|
|
errors.append(message)
|
|
continue
|
|
|
|
# response example:
|
|
# resp = codExp|agencia/numero expedicion|color|ruta|codigo agencia|nombre agencia|telf entrega|service|hora entrega|barcode|fecha prevista
|
|
# resp = '9999999|2841/9999999|GRIS|2V|0832|VILAFRANCA|938902108|NACEX 19:00H|Entregar antes de las 19:00H.|00128419999999083208|07/05/2021|'
|
|
|
|
reference = values[1]
|
|
|
|
if reference:
|
|
cls.write([shipment], {
|
|
'carrier_tracking_ref': reference,
|
|
'carrier_service': service,
|
|
'carrier_delivery': True,
|
|
'carrier_send_date': ShipmentOut.get_carrier_date(),
|
|
'carrier_send_employee': (
|
|
ShipmentOut.get_carrier_employee() or None),
|
|
})
|
|
logger.info('Send shipment %s' % (shipment.number))
|
|
references.append(shipment.number)
|
|
else:
|
|
logger.error('Not send shipment %s.' % (shipment.number))
|
|
|
|
labels += cls.print_labels_nacex(api, [shipment])
|
|
if labels:
|
|
cls.write(shipments, {'carrier_printed': True})
|
|
|
|
return references, labels, errors
|
|
|
|
@classmethod
|
|
def print_labels_nacex(cls, api, shipments):
|
|
'''
|
|
Get labels from shipments out from Nacex
|
|
'''
|
|
labels = []
|
|
dbname = Transaction().database.name
|
|
|
|
to_write = []
|
|
for shipment in shipments:
|
|
reference = shipment.carrier_tracking_ref
|
|
if not reference:
|
|
continue
|
|
|
|
try:
|
|
agencia, numero = reference.split('/')
|
|
except ValueError:
|
|
continue
|
|
|
|
data = {}
|
|
data['agencia'] = agencia
|
|
data['numero'] = numero
|
|
data['modelo'] = api.print_report
|
|
|
|
resp = nacex_call(api, 'getEtiqueta', data)
|
|
values = resp.text.split('|')
|
|
|
|
if resp.status_code != 200 or values[0] == 'ERROR':
|
|
continue
|
|
|
|
if api.print_report == 'IMAGEN_B':
|
|
try:
|
|
content = base64.urlsafe_b64decode(
|
|
resp.text + '=' * (4 - len(resp.text) % 4))
|
|
except TypeError:
|
|
continue
|
|
suffix = '.png'
|
|
else:
|
|
try:
|
|
content = resp.text.encode()
|
|
except AttributeError:
|
|
continue
|
|
suffix = None
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
prefix='%s-nacex-%s-%s-' % (dbname, agencia, numero),
|
|
suffix=suffix, delete=False) as temp:
|
|
temp.write(content)
|
|
logger.info(
|
|
'Generated tmp label %s' % (temp.name))
|
|
temp.close()
|
|
labels.append(temp.name)
|
|
|
|
to_write.extend(([shipment], {
|
|
'carrier_printed': True,
|
|
'carrier_tracking_label': fields.Binary.cast(
|
|
open(temp.name, "rb").read()),
|
|
}))
|
|
if to_write:
|
|
cls.write(*to_write)
|
|
return labels
|
|
|
|
@classmethod
|
|
def get_labels_nacex(cls, api, shipments):
|
|
binary_label = []
|
|
for label in cls.print_labels_nacex(api, shipments):
|
|
binary_label.append(fields.Binary.cast(open(label, "rb").read()))
|
|
return binary_label
|