Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion delivery_carrier_label_paazl/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Carrier labels for paazl
:target: https://runbot.odoo-community.org/runbot/99/12.0
:alt: Try me on Runbot

|badge1| |badge2| |badge3| |badge4| |badge5|
|badge1| |badge2| |badge3| |badge4| |badge5|

This module adds `paazl <https://paazl.com>`_ to the available carriers.

Expand All @@ -38,6 +38,7 @@ Configuration
To configure this module, you need to:

#. Add options to your paazl carrier
#. Select wanted paazl label format (PDF or ZPL) on delivery carrier options, if nothing is set default label format will be PDF.
#. Add a carrier account with delivery type ``paazl``, fill in your webshop id as account number, and the integration password as password. You find those values in Integrations/SOAP API on paazl's website
#. If you want to receive status updates (including the carrier's tracking url), you need to enable the push API in your paazl account, and fill in ``https://$yourdomain/_paazl/push_api/v1`` as notification URL. You should enable client authentication and fill in the token generated there in the `Bearer token` field of the carrier account in Odoo

Expand All @@ -49,7 +50,9 @@ To use this module, you need to:
#. Select the paazl carrier on your sale orders or pickings
#. Set exactly one delivery option
#. As a label needs to be generated for the tracking code, there's no need to manually generate labels via the button
#. Note return labels are generated for return customer pickings
#. Note that the addon passes the package's dimensions if a packaging is set on the package, and on the product's dimensions otherwise
#. Depending on the label format settings attached to the carrier option, get .zpl or .pdf format labels.

Bug Tracker
===========
Expand Down
1 change: 1 addition & 0 deletions delivery_carrier_label_paazl/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"data/delivery_carrier.xml",
"data/delivery_carrier_template_option.xml",
"views/carrier_account.xml",
"views/delivery_carrier_option.xml",
],
"demo": [],
"external_dependencies": {"python": ["zeep"]},
Expand Down
1 change: 1 addition & 0 deletions delivery_carrier_label_paazl/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
from . import carrier_account
from . import delivery_carrier
from . import stock_picking
from . import delivery_carrier_option
12 changes: 12 additions & 0 deletions delivery_carrier_label_paazl/models/delivery_carrier_option.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class DeliveryCarrierOption(models.Model):
_inherit = 'delivery.carrier.option'

file_format = fields.Selection([("PDF", "PDF"), ("ZPL", "ZPL")])
delivery_type = fields.Selection(related="carrier_id.delivery_type")
shipping_method_type = fields.Selection([('delivery', 'Delivery'), ('servicepoint', 'Service point')],
default="delivery")
88 changes: 78 additions & 10 deletions delivery_carrier_label_paazl/models/stock_picking.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,31 @@ def _paazl_generate_labels(self):
"""
self.ensure_one()
api = self._paazl_api()
# we need to create labels in order to get a tracking number
status = api.service.generateLabels(**self._paazl_generate_labels_data())
is_return = self.location_id.usage == "customer"
return_rma = all([is_return and self.picking_type_code == "incoming"])
if return_rma:
payload = self._paazl_generate_labels_data()
# Removing since generatePdfReturnLabels call doesn't accept labelFormat
if "labelFormat" in payload:
payload.pop("labelFormat")
# Generating return labels
status = api.service.generatePdfReturnLabels(**payload)
else:
# we need to create labels in order to get a tracking number
status = api.service.generateLabels(**self._paazl_generate_labels_data())
self._paazl_raise_error(status)
# as we have the labels anyways, attach them
account = self._get_carrier_account()
status_label = status.label if return_rma else status.labels
file_type = (account.file_format or "").lower() or "pdf"
options_paazl_label_format = self.option_ids.filtered(
lambda x: x.file_format
)
if options_paazl_label_format:
file_type = options_paazl_label_format[0].file_format.lower()
label = {
"name": self._paazl_reference(),
"file": base64.b64encode(status.labels),
"file": base64.b64encode(status_label),
"filename": "{}.{}".format(self._paazl_reference(), file_type),
"file_type": file_type,
}
Expand Down Expand Up @@ -197,31 +213,56 @@ def _paazl_commit_order_data(self):
"""Return a dict that can be passed as parameters to the `commitOrder`
endpoint of the paazl soap api"""
self.ensure_one()
is_return = self.location_id.usage == "customer"
shipping_address = self._paazl_partner_to_order_data(self.partner_id)
shipping_address["customerName"] = shipping_address.pop("addresseeLine")
if self.partner_id.parent_id:
shipping_address["companyName"] = self.partner_id.parent_id.name
return dict(
warehouse_partner_id = self.picking_type_id.warehouse_id.partner_id
if is_return and self.picking_type_code == "incoming":
shipping_address = self._paazl_partner_to_order_data(warehouse_partner_id)
shipper_address = self.partner_id
shipping_address["customerName"] = shipping_address.pop("addresseeLine")
if warehouse_partner_id.parent_id:
shipping_address["companyName"] = warehouse_partner_id.parent_id.name
else:
shipping_address = self._paazl_partner_to_order_data(self.partner_id)
shipper_address = warehouse_partner_id
shipping_address["customerName"] = shipping_address.pop("addresseeLine")
if self.partner_id.parent_id:
shipping_address["companyName"] = self.partner_id.parent_id.name
if self.option_ids[:1].shipping_method_type == "servicepoint":
identifier = self._paazl_get_servicepoints().shippingOption[0]["servicePoint"][0]["code"]
servicepointNotificationEmail = self.partner_id.email
else:
identifier = 0
servicepointNotificationEmail = False
res = dict(
**self._paazl_auth_order_id(),
pendingOrderReference=self._paazl_reference(),
customerEmail=self.partner_id.email,
customerPhoneNumber=self.partner_id.phone,
shippingMethod=dict(
type="delivery",
identifier=0,
type=self.option_ids[:1].shipping_method_type,
identifier=identifier,
option=self.option_ids[:1].code,
servicepointNotificationEmail=servicepointNotificationEmail,
orderWeight=sum(
product["quantity"] * product["weight"]
for product in self._paazl_order_data()["products"]["product"]
),
packageCount=self._paazl_package_count(),
),
shipperAddress=self._paazl_partner_to_order_data(
self.picking_type_id.warehouse_id.partner_id,
shipper_address,
),
shippingAddress=shipping_address,
totalAmount=0,
)
if is_return and self.picking_type_code == "incoming":
# in case of a return set the return address to the shipper
res.update(returnAddress=self._paazl_partner_to_order_data(
shipper_address,
))
return res

def _paazl_change_order_data(self):
"""Return a dict that can be passed as parameters to the `changeOrder`
Expand Down Expand Up @@ -259,11 +300,22 @@ def _paazl_partner_to_order_data(self, partner):
def _paazl_generate_labels_data(self):
"""Return a dict to be passed to the generateLabels endpoint"""
self.ensure_one()
is_return = self.location_id.usage == "customer"
return_rma = all([is_return and self.picking_type_code == "incoming"])
auth_order_id = self._paazl_auth_order_id()
is_return = self.location_id.usage == "customer"
if is_return and self.picking_type_code == "incoming":
auth_order_id['shippingOption'] = self.option_ids.tmpl_option_id.code
account = self._get_carrier_account()
file_format = account.file_format or "PDF"
options_paazl_label_format = self.option_ids.filtered(
lambda x: x.file_format
)
if options_paazl_label_format and not return_rma:
file_format = options_paazl_label_format[0].file_format
return {
"webshop": auth_order_id.pop("webshop"),
"labelFormat": account.file_format or "PDF",
"labelFormat": file_format,
"order": auth_order_id,
"includeMetaData": True,
}
Expand All @@ -287,3 +339,19 @@ def _paazl_reference(self):
"""Return a reference for a picking to be used for paazl"""
self.ensure_one()
return self.name

def _paazl_get_servicepoints(self):
"""Return servicepoints response for the order's partner hardcoded to only return the nearest DHL servicepoint
"""
api = self._paazl_api()
account = self._get_carrier_account()
country = self.partner_id.country_id.code
postcode = self.partner_id.zip
hash_data = hashlib.sha1((account.account + account.password + country + postcode).encode()).hexdigest()
webshop = account.account
response = api.service.servicePoints(
hash=hash_data, webshop=webshop, country=country, postcode=postcode,
shippingOption="DHL_PARCEL_CONNECT_SHOP",
limit=1,
)
return response
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ def _setup_mock_client(self):
mock_client.service.generateLabels.return_value = Mock(
error=False, labels=b"hello world", metaData=[{"trackingNumber": "number"}],
)
mock_client.service.servicePoints.return_value = Mock(
error=False, shippingOption=[{"servicePoint": [{"code": "1234-NL-123456"}]}],
)
mock_client.service.generatePdfReturnLabels.return_value = Mock(
error=False,
label=b"generatePdfReturnLabel",
metaData=[{"trackingNumber": "number"}],
)
with patch.object(zeep, "Client") as mock_client_class:
mock_client_class.return_value = mock_client
yield mock_client
Expand Down Expand Up @@ -114,6 +122,7 @@ def _request(self, path, method="POST", data=None, headers=None):
class TestDeliveryCarrierLabelPaazl(
DeliveryCarrierLabelPaazlCase,
carrier_label_case.TestCarrierLabel):

def test_cancel_shipment(self):
self.assertTrue(self.picking.carrier_tracking_ref)
with self._setup_mock_client() as mock_client:
Expand Down Expand Up @@ -176,6 +185,23 @@ def test_push_api(self):
)
self.assertTrue(self.picking.message_ids - messages_before)

def test_label_type_format_from_paazl(self):
"""
Test if getting .zpl format label after using delivery option with zpl format
"""
picking = self.picking.copy()
picking.option_ids.file_format = "ZPL"
self.assertEqual(picking.option_ids.file_format, "ZPL")
picking.move_lines.quantity_done = 1
with self._setup_mock_client():
payload = picking._paazl_generate_labels()

labels = payload["labels"][0]
self.assertTrue(labels)
self.assertTrue(labels["file"])
self.assertEqual(labels["file_type"], "zpl")
self.assertEqual(labels["filename"].split('.')[1], "zpl")

def test_change_order(self):
"""Test change order in paazl"""
self.assertTrue(self.picking.carrier_tracking_ref)
Expand All @@ -186,6 +212,45 @@ def test_change_order(self):
self.picking._paazl_send_update_order(change_order=True)
self.assertTrue(self.picking.carrier_tracking_ref)

def test_servicepoint_order(self):
"""Test committing an order with type servicepoint"""
picking = self.picking.copy()
picking.option_ids.shipping_method_type = "servicepoint"
picking.partner_id.country_id = self.env.ref("base.nl")
picking.partner_id.zip = "1092AD"
with self._setup_mock_client() as mock_client:
mock_client.service.servicePoints.return_value = Mock(
error=False, shippingOption=[{"servicePoint": [{"code": "1234-NL-123456"}]}],
)
picking._paazl_send_update_order()

def test_paazl_return_pdf_label(self):
"""
Test paazl generatePdfReturnLabel API endpoint to generate return labels.
"""
picking = self.picking.copy()
customer_location = self.env['stock.location'].search(
[("usage", "=", "customer")]
)
self.assertTrue(customer_location)
picking.location_id = customer_location[0].id
picking.picking_type_code = "incoming"
self.assertEqual(picking.picking_type_code, "incoming")
with self._setup_mock_client() as mock_client:
mock_client.service.commitOrder.return_value = Mock(
error=Mock(code=1003, message="")
)
picking._paazl_send_update_order(change_order=True)
picking.move_lines.quantity_done = 1
with self._setup_mock_client():
payload = picking._paazl_generate_labels()

labels = payload["labels"][0]
self.assertTrue(labels)
self.assertTrue(labels["file"])
self.assertEqual(labels["file_type"], "pdf")
self.assertEqual(labels["filename"].split('.')[1], "pdf")


class TestDeliveryCarrierLabelPaazlPackaging(TestDeliveryCarrierLabelPaazl):
def _picking_data(self):
Expand Down
15 changes: 15 additions & 0 deletions delivery_carrier_label_paazl/views/delivery_carrier_option.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="delivery_carrier_option_view_form" model="ir.ui.view">
<field name="name">delivery_base.delivery_carrier_option.view_form</field>
<field name="model">delivery.carrier.option</field>
<field name="inherit_id" ref="base_delivery_carrier_label.delivery_carrier_option_view_form"/>
<field name="arch" type="xml">
<field name="tmpl_option_id" position="after">
<field name="delivery_type" invisible="1"/>
<field name="file_format" attrs="{'invisible': [('delivery_type', '!=', 'paazl')]}"/>
<field name="shipping_method_type" attrs="{'invisible': [('delivery_type', '!=', 'paazl')]}"/>
</field>
</field>
</record>
</odoo>