Skip to content

Commit ed9e946

Browse files
author
Michael Michot
committed
[ADD] coliprive + fix phs label
1 parent 7b62d91 commit ed9e946

File tree

12 files changed

+399
-42
lines changed

12 files changed

+399
-42
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from . import models
2+
3+
# from . import tests
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2020 colisprive (https://www.colisprive.be)
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
{
4+
"name": "Delivery Carrier label - Colis prive",
5+
"category": "Delivery",
6+
"summary": "Delivery Carrier label - Colis prive",
7+
"version": "14.0.1.0.0",
8+
"author": "colisprive",
9+
"license": "AGPL-3",
10+
"website": "https://github.com/akretion/phs-addons",
11+
"depends": [
12+
"base",
13+
"base_delivery_carrier_label",
14+
],
15+
"data": [],
16+
"installable": True,
17+
"application": False,
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from . import delivery_carrier
2+
from . import stock_picking
3+
from . import stock_quant_package
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from odoo import fields, models
2+
3+
4+
class DeliveryCarrier(models.Model):
5+
""" Add service group """
6+
7+
_inherit = "delivery.carrier"
8+
9+
delivery_type = fields.Selection(
10+
selection_add=[("colisprive", "Colis privé")],
11+
ondelete={"colisprive": "set default"},
12+
)
13+
14+
colisprive_default_packaging_id = fields.Many2one(
15+
"product.packaging", domain=[("package_carrier_type", "=", "colisprive")]
16+
)
17+
18+
def colisprive_rate_shipment(self, order):
19+
self.ensure_one()
20+
delivery_product_price = self.product_id and self.product_id.lst_price or 0
21+
return {
22+
"success": True,
23+
"price": delivery_product_price,
24+
"error_message": False,
25+
"warning_message": False,
26+
}
27+
28+
def colisprive_send_shipping(self, pickings):
29+
"""
30+
It will generate the labels for all the packages of the picking.
31+
Packages are mandatory in this case
32+
"""
33+
labels = []
34+
for pick in pickings:
35+
pick._colisprive_set_a_default_package()
36+
labels += pick._generate_colisprive_label()
37+
38+
return labels
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import base64
2+
import re
3+
from operator import attrgetter
4+
from xml.etree import ElementTree
5+
6+
import requests
7+
8+
from odoo import _, exceptions, models
9+
10+
_compile_itemid = re.compile(r"[^0-9A-Za-z+\-_]")
11+
12+
13+
class StockPicking(models.Model):
14+
_inherit = "stock.picking"
15+
16+
def _colisprive_set_a_default_package(self):
17+
"""Pickings using this module must have a package
18+
If not this method put it one silently
19+
"""
20+
for picking in self:
21+
move_lines = picking.move_line_ids.filtered(
22+
lambda s: not (s.package_id or s.result_package_id)
23+
)
24+
if move_lines:
25+
carrier = picking.carrier_id
26+
default_packaging = carrier.colisprive_default_packaging_id
27+
package = self.env["stock.quant.package"].create(
28+
{
29+
"packaging_id": default_packaging
30+
and default_packaging.id
31+
or False
32+
}
33+
)
34+
move_lines.write({"result_package_id": package.id})
35+
36+
def _colisprive_get_packages_from_picking(self):
37+
""" Get all the packages from the picking """
38+
self.ensure_one()
39+
operation_obj = self.env["stock.move.line"]
40+
operations = operation_obj.search(
41+
[
42+
"|",
43+
("package_id", "!=", False),
44+
("result_package_id", "!=", False),
45+
("picking_id", "=", self.id),
46+
]
47+
)
48+
package_ids = set()
49+
for operation in operations:
50+
# Take the destination package. If empty, the package is
51+
# moved so take the source one.
52+
package_ids.add(operation.result_package_id.id or operation.package_id.id)
53+
54+
packages = self.env["stock.quant.package"].browse(package_ids)
55+
return packages
56+
57+
def colisprive_info_from_label(self, label):
58+
data = base64.b64encode(label["binary"])
59+
60+
return {
61+
"labels": [
62+
{
63+
"file": data,
64+
"file_type": label["file_type"],
65+
"name": "{}.{}".format(
66+
label["tracking_number"], label["file_type"]
67+
),
68+
}
69+
],
70+
"exact_price": False,
71+
"tracking_number": label["tracking_number"],
72+
}
73+
74+
def write_tracking_number_label(self, label_result, packages):
75+
"""
76+
If there are no pack defined, write tracking_number on picking
77+
otherwise, write it on parcel_tracking field of each pack.
78+
Note we can receive multiple labels for a same package
79+
"""
80+
81+
labels = []
82+
83+
# It could happen that no successful label has been returned by the API
84+
if not label_result:
85+
return labels
86+
87+
if not packages:
88+
label = label_result[0]["value"][0]
89+
self.carrier_tracking_ref = label["tracking_number"]
90+
labels.append(self.colisprive_info_from_label(label))
91+
92+
tracking_refs = []
93+
for package in packages:
94+
tracking_numbers = []
95+
for label in label_result:
96+
for label_value in label["value"]:
97+
if package.name in label_value["item_id"].split("+")[-1]:
98+
tracking_numbers.append(label_value["tracking_number"])
99+
labels.append(self.colisprive_info_from_label(label_value))
100+
package.parcel_tracking = "; ".join(tracking_numbers)
101+
tracking_refs += tracking_numbers
102+
103+
existing_tracking_ref = (
104+
self.carrier_tracking_ref and self.carrier_tracking_ref.split("; ") or []
105+
)
106+
self.carrier_tracking_ref = "; ".join(existing_tracking_ref + tracking_refs)
107+
return labels
108+
109+
def _get_itemid(self, picking, pack_no):
110+
"""Allowed characters are alphanumeric plus `+`, `-` and `_`
111+
Last `+` separates picking name and package number (if any)
112+
113+
:return string: itemid
114+
115+
"""
116+
name = _compile_itemid.sub("", picking.name)
117+
if not pack_no:
118+
return name
119+
120+
pack_no = _compile_itemid.sub("", pack_no)
121+
codes = [name, pack_no]
122+
return "+".join(c for c in codes if c)
123+
124+
def generate_colisprive_label(self, picking, packages):
125+
results = []
126+
127+
url = "https://colisprive.com/Externe/WSCP.asmx"
128+
headers = {
129+
"Content-Type": "text/xml; charset=utf-8",
130+
"SOAPAction": "http://colisprive.com/externe/1.0/SetParcel",
131+
}
132+
133+
carrier_account = self.env["carrier.account"].search(
134+
[("carrier_ids", "=", picking.carrier_id.id)], limit=1
135+
)
136+
if not carrier_account:
137+
error_message = _("No carrier account defined")
138+
raise exceptions.Error(error_message)
139+
140+
payload_con_data = {
141+
"UserName": carrier_account.account,
142+
"Password": carrier_account.password,
143+
"CPCustoID": "WK", # TODO use carrier options
144+
"AccountID": "EM270518", # TODO use carrier options
145+
}
146+
ship_payload = """<?xml version="1.0" encoding="UTF-8"?>
147+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
148+
xmlns:ns1="http://colisprive.com/externe/1.0/"
149+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
150+
<SOAP-ENV:Header>
151+
<ns1:AuthenticationHeader>
152+
<ns1:UserName>{UserName}</ns1:UserName>
153+
<ns1:Password>{Password}</ns1:Password>
154+
</ns1:AuthenticationHeader>
155+
</SOAP-ENV:Header>
156+
<SOAP-ENV:Body>
157+
<ns1:SetParcel>
158+
<ns1:SetParcelRequest>
159+
<ns1:SecurityID>
160+
<ns1:CPCustoID>{CPCustoID}</ns1:CPCustoID>
161+
<ns1:AccountID>{AccountID}</ns1:AccountID>
162+
</ns1:SecurityID>
163+
<ns1:OrderID>{OrderID}</ns1:OrderID>
164+
<ns1:CltNum xsi:nil="true"/>
165+
<ns1:CsgAdd>
166+
<ns1:DlvrName>{DlvrName}</ns1:DlvrName>
167+
<ns1:DlvrAddress>
168+
<ns1:Add1>{Add1}</ns1:Add1>
169+
<ns1:Add2>{Add2}</ns1:Add2>
170+
<ns1:Add3>{Add3}</ns1:Add3>
171+
<ns1:Add4>{Add4}</ns1:Add4>
172+
<ns1:ZC>{ZC}</ns1:ZC>
173+
<ns1:City>{City}</ns1:City>
174+
<ns1:Country>{Country}</ns1:Country>
175+
</ns1:DlvrAddress>
176+
<ns1:DlvrEmail>{DlvrEmail}</ns1:DlvrEmail>
177+
<ns1:DlvrPhon>{DlvrPhon}</ns1:DlvrPhon>
178+
<ns1:DlvrGsm>{DlvrGsm}</ns1:DlvrGsm>
179+
</ns1:CsgAdd>
180+
<ns1:PclShipDate>2022 01 13</ns1:PclShipDate>
181+
<ns1:PclWeight>5000</ns1:PclWeight>
182+
<ns1:IsPclWithPOD>false</ns1:IsPclWithPOD>
183+
<ns1:LabelFormat>PDF_ZEBRA</ns1:LabelFormat>
184+
<ns1:ChargeAmnt xsi:nil="true"/>
185+
<ns1:ValueAmnt xsi:nil="true"/>
186+
</ns1:SetParcelRequest>
187+
</ns1:SetParcel>
188+
</SOAP-ENV:Body>
189+
</SOAP-ENV:Envelope>"""
190+
191+
for package in packages:
192+
file_type = "pdf"
193+
order_payload = {
194+
"OrderID": "{}-{}".format(picking.origin, picking.name),
195+
"DlvrName": picking.partner_id.name,
196+
"Add1": picking.partner_id.street,
197+
"Add2": picking.partner_id.street2 or "",
198+
"Add3": "",
199+
"Add4": "",
200+
"ZC": picking.partner_id.zip,
201+
"City": picking.partner_id.city,
202+
"Country": picking.partner_id.country_id.code,
203+
"DlvrEmail": picking.partner_id.email or "",
204+
"DlvrPhon": picking.partner_id.phone or "",
205+
"DlvrGsm": picking.partner_id.mobile or "",
206+
}
207+
208+
response = requests.request(
209+
"POST",
210+
url,
211+
headers=headers,
212+
data=ship_payload.format(**{**payload_con_data, **order_payload}),
213+
)
214+
dom = ElementTree.fromstring(response.content)
215+
216+
pdf_response = requests.get(dom[0][0][0][3].text)
217+
218+
binary = pdf_response.content
219+
220+
res = {"value": []}
221+
res["success"] = True
222+
res["value"].append(
223+
{
224+
"item_id": self._get_itemid(
225+
picking, package.name if package else None
226+
),
227+
"binary": binary,
228+
"tracking_number": dom[0][0][0][1].text,
229+
"file_type": file_type,
230+
}
231+
)
232+
results.append(res)
233+
return results
234+
235+
def _generate_colisprive_label(self, package_ids=None):
236+
""" Generate labels """
237+
self.ensure_one()
238+
239+
if package_ids is None:
240+
packages = self._colisprive_get_packages_from_picking()
241+
packages = packages.sorted(key=attrgetter("name"))
242+
else:
243+
# restrict on the provided packages
244+
package_obj = self.env["stock.quant.package"]
245+
packages = package_obj.browse(package_ids)
246+
247+
# Do not generate label for packages that are already done
248+
packages = packages.filtered(lambda p: not p.parcel_tracking)
249+
250+
label_results = self.generate_colisprive_label(self, packages)
251+
252+
# Process the success packages first
253+
success_label_results = [
254+
label for label in label_results if "errors" not in label
255+
]
256+
failed_label_results = [label for label in label_results if "errors" in label]
257+
258+
# Case when there is a failed label, rollback odoo data
259+
if failed_label_results:
260+
self._cr.rollback()
261+
262+
labels = self.write_tracking_number_label(success_label_results, packages)
263+
264+
if failed_label_results:
265+
# Commit the change to save the changes,
266+
# This ensures the label pushed recored correctly in Odoo
267+
self._cr.commit() # pylint: disable=invalid-commit
268+
error_message = "\n".join(label["errors"] for label in failed_label_results)
269+
raise exceptions.Warning(error_message)
270+
271+
return labels
272+
273+
def _generate_colisprive_shipping_label(self, package_ids=None):
274+
""" Add label generation for colisprive """
275+
self.ensure_one()
276+
res = self._generate_colisprive_label(package_ids=package_ids)
277+
278+
return res
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from odoo import fields, models
2+
3+
4+
class StockQuantPackage(models.Model):
5+
_inherit = "stock.quant.package"
6+
7+
parcel_tracking = fields.Char("Parcel Tracking")
8+
package_carrier_type = fields.Selection(
9+
related="packaging_id.package_carrier_type",
10+
string="Packaging's Carrier",
11+
)

phs_delivery_label/models/delivery_carrier.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ def pharmasimple_send_shipping(self, pickings):
3030
It will generate the labels for all the packages of the picking.
3131
Packages are mandatory in this case
3232
"""
33+
labels = []
3334
for pick in pickings:
34-
pick._set_a_default_package()
35-
pick._generate_pharmasimple_label()
35+
pick._phs_set_a_default_package()
36+
labels += pick._generate_pharmasimple_label()
3637

37-
return [{"exact_price": False, "tracking_number": False}]
38+
return labels

0 commit comments

Comments
 (0)