1
1
# Copyright 2013 Camptocamp SA
2
2
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3
3
import base64
4
+ import logging
5
+ from io import BytesIO
4
6
from operator import attrgetter
5
7
6
8
import lxml .html
9
+ from PIL import Image
7
10
8
11
from odoo import api , fields , models , tools
9
12
from odoo .exceptions import UserError
10
13
11
- from ..postlogistics .web_service import PostlogisticsWebService
14
+ from ..postlogistics .web_service import PostlogisticsWebService , sanitize_string
15
+
16
+ _logger = logging .getLogger (__name__ )
12
17
13
18
14
19
class StockPicking (models .Model ):
@@ -27,6 +32,16 @@ class StockPicking(models.Model):
27
32
"Mobile" , help = "For notify delivery by telephone (ZAW3213)"
28
33
)
29
34
35
+ def _get_packages_from_picking (self ):
36
+ # As CI doesn't allow warnings, we need to log as info here
37
+ # (deprecated decorator is unhelpfull).
38
+ _logger .info (
39
+ "This method will be removed in version 18.0. \
40
+ Please use _get_quant_packages_from_picking instead."
41
+ )
42
+ # TODO: remove this method in version > 18.0
43
+ return self ._get_quant_packages_from_picking ()
44
+
30
45
def _get_quant_packages_from_picking (self ):
31
46
"""Get all the quant packages from the picking"""
32
47
self .ensure_one ()
@@ -235,3 +250,209 @@ def action_generate_carrier_label(self):
235
250
if not self .carrier_id :
236
251
raise UserError (self .env ._ ("Please, set a carrier." ))
237
252
self .env ["delivery.carrier" ].postlogistics_send_shipping (self )
253
+
254
+ #
255
+ # Postlogistics specific methods allowing proper override
256
+ #
257
+
258
+ def get_package_number_hook (self , package ):
259
+ """Hook method to customize the package number retrieval"""
260
+ return None
261
+
262
+ def get_recipient_partner_hook (self ):
263
+ """Hook method to customize the partner retrieval"""
264
+ self .ensure_one ()
265
+ if self .picking_type_id .code != "outgoing" :
266
+ return (
267
+ self .location_dest_id .company_id .partner_id
268
+ or self .env .user .company_id .partner_id
269
+ )
270
+ return self .partner_id
271
+
272
+ def postlogistics_label_prepare_attributes (
273
+ self , pack = None , pack_num = None , pack_total = None , pack_weight = None
274
+ ):
275
+ """This method aims to prepare a dictionary of attributes to be sent
276
+ to the PostLogistics API"""
277
+ self .ensure_one ()
278
+ package_type = (
279
+ pack
280
+ and pack .package_type_id
281
+ or self .carrier_id .postlogistics_default_package_type_id
282
+ )
283
+ package_codes = package_type ._get_shipper_package_code_list ()
284
+
285
+ if pack_weight :
286
+ total_weight = pack_weight
287
+ else :
288
+ total_weight = pack .shipping_weight if pack else self .shipping_weight
289
+ total_weight *= 1000
290
+
291
+ if not package_codes :
292
+ raise UserError (
293
+ self .env ._ (
294
+ "No PostLogistics packaging services found "
295
+ "in package type {package_type_name}, for picking {picking_name}."
296
+ ).format (package_type_name = package_type .name , picking_name = self .name )
297
+ )
298
+
299
+ # Activate phone notification ZAW3213
300
+ # if phone call notification is set on partner
301
+ if self .partner_id .postlogistics_notification == "phone" :
302
+ package_codes .append ("ZAW3213" )
303
+
304
+ attributes = {
305
+ "weight" : int (total_weight ),
306
+ }
307
+
308
+ # Remove the services if the delivery fixed date is not set
309
+ if "ZAW3217" in package_codes :
310
+ if self .delivery_fixed_date :
311
+ attributes ["deliveryDate" ] = self .delivery_fixed_date
312
+ else :
313
+ package_codes .remove ("ZAW3217" )
314
+
315
+ # parcelNo / parcelTotal cannot be used if service ZAW3218 is not activated
316
+ if "ZAW3218" in package_codes :
317
+ if pack_total > 1 :
318
+ attributes .update (
319
+ {"parcelTotal" : pack_total - 1 , "parcelNo" : pack_num - 1 }
320
+ )
321
+ else :
322
+ package_codes .remove ("ZAW3218" )
323
+
324
+ if "ZAW3219" in package_codes and self .delivery_place :
325
+ attributes ["deliveryPlace" ] = self .delivery_place
326
+ if self .carrier_id .postlogistics_proclima_logo :
327
+ attributes ["proClima" ] = True
328
+ else :
329
+ attributes ["proClima" ] = False
330
+
331
+ attributes ["przl" ] = package_codes
332
+
333
+ return attributes
334
+
335
+ def postlogistics_label_prepare_customer (self ):
336
+ """Create a ns0:Customer as a dict from picking
337
+
338
+ This is the PostLogistics Customer, thus the sender
339
+
340
+ :param picking: picking browse record
341
+ :return a dict containing data for ns0:Customer
342
+
343
+ """
344
+ self .ensure_one ()
345
+ company = self .company_id
346
+ partner = company .partner_id
347
+ if self .picking_type_id .code != "outgoing" :
348
+ partner = self .partner_id
349
+
350
+ partner_name = partner .name or partner .parent_id .name
351
+ if not partner_name :
352
+ raise UserError (self .env ._ ("Customer name is required." ))
353
+ customer = {
354
+ "name1" : sanitize_string (partner_name )[:25 ],
355
+ "street" : sanitize_string (partner .street )[:25 ],
356
+ "zip" : sanitize_string (partner .zip )[:10 ],
357
+ "city" : sanitize_string (partner .city )[:25 ],
358
+ "country" : partner .country_id .code ,
359
+ "domicilePostOffice" : self .carrier_id .postlogistics_office or None ,
360
+ }
361
+ logo = self .carrier_id .postlogistics_logo
362
+ if logo :
363
+ logo_image = Image .open (BytesIO (base64 .b64decode (logo )))
364
+ logo_format = logo_image .format
365
+ customer ["logo" ] = logo .decode ()
366
+ customer ["logoFormat" ] = logo_format
367
+ return customer
368
+
369
+ def postlogistics_label_prepare_recipient (self , sanitize_mapping = None ):
370
+ """Create a ns0:Recipient as a dict from a partner
371
+
372
+ :param partner: partner browse record
373
+ :return a dict containing data for ns0:Recipient
374
+
375
+ """
376
+ partner = self .get_recipient_partner_hook ()
377
+
378
+ partner_mobile = sanitize_string (
379
+ self .delivery_mobile or partner .mobile , sanitize_mapping
380
+ )
381
+ partner_phone = sanitize_string (
382
+ self .delivery_phone or partner .phone , sanitize_mapping
383
+ )
384
+
385
+ if partner .postlogistics_notification == "email" and not partner .email :
386
+ raise UserError (self .env ._ ("Email is required for notification." ))
387
+ elif partner .postlogistics_notification == "sms" and not partner_mobile :
388
+ raise UserError (
389
+ self .env ._ ("Mobile number is required for sms notification." )
390
+ )
391
+ elif partner .postlogistics_notification == "phone" and not partner_phone :
392
+ raise UserError (
393
+ self .env ._ ("Phone number is required for phone call notification." )
394
+ )
395
+
396
+ if not partner .street :
397
+ raise UserError (self .env ._ ("Partner street is required." ))
398
+
399
+ if not partner .name and not partner .parent_id .name :
400
+ raise UserError (self .env ._ ("Partner name is required." ))
401
+
402
+ if not partner .zip :
403
+ raise UserError (self .env ._ ("Partner zip is required." ))
404
+
405
+ if not partner .city :
406
+ raise UserError (self .env ._ ("Partner city is required." ))
407
+
408
+ partner_name = partner .name or partner .parent_id .name
409
+ sanitized_partner_name = sanitize_string (partner_name , sanitize_mapping )
410
+ partner_street = sanitize_string (partner .street , sanitize_mapping )
411
+ partner_zip = sanitize_string (partner .zip , sanitize_mapping )
412
+ partner_city = sanitize_string (partner .city , sanitize_mapping )
413
+ recipient = {
414
+ "name1" : sanitized_partner_name [:35 ],
415
+ "street" : partner_street [:35 ],
416
+ "zip" : partner_zip [:10 ],
417
+ "city" : partner_city [:35 ],
418
+ }
419
+
420
+ if partner .country_id .code :
421
+ country_code = sanitize_string (
422
+ partner .country_id .code .upper (), sanitize_mapping
423
+ )
424
+ recipient ["country" ] = country_code
425
+
426
+ if partner .street2 :
427
+ # addressSuffix is shown before street on label
428
+ recipient ["addressSuffix" ] = recipient ["street" ]
429
+ recipient ["street" ] = sanitize_string (
430
+ partner .street2 [:35 ], sanitize_mapping
431
+ )
432
+
433
+ company_partner_name = partner .commercial_company_name
434
+ if company_partner_name and company_partner_name != partner_name :
435
+ parent_name = sanitize_string (partner .parent_id .name , sanitize_mapping )
436
+ recipient ["name2" ] = parent_name [:35 ]
437
+ recipient ["personallyAddressed" ] = False
438
+
439
+ # Phone and / or mobile should only be displayed if instruction to
440
+ # Notify delivery by telephone is set
441
+ if partner .postlogistics_notification == "email" :
442
+ recipient ["email" ] = sanitize_string (partner .email , sanitize_mapping )
443
+ elif partner .postlogistics_notification == "phone" :
444
+ recipient ["phone" ] = sanitize_string (partner_phone , sanitize_mapping )
445
+ if partner_mobile :
446
+ recipient ["mobile" ] = partner_mobile
447
+ elif partner .postlogistics_notification == "sms" :
448
+ recipient ["mobile" ] = partner_mobile
449
+
450
+ return recipient
451
+
452
+ def postlogistics_label_cash_on_delivery (self , package = None ):
453
+ amount = (package or self ).postlogistics_cod_amount ()
454
+ amount = f"{ amount :.2f} "
455
+ return [{"Type" : "NN_BETRAG" , "Value" : amount }]
456
+
457
+ def postlogistics_label_get_item_additional_data (self , package = None ):
458
+ return []
0 commit comments