Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: akretion/roulier
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.3.2
Choose a base ref
...
head repository: akretion/roulier
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: develop
Choose a head ref

Commits on Feb 19, 2018

  1. Geodis Don't send utf8 in EDI anymore

    Because, experience prove they don't know how to handle utf8
    hparfr committed Feb 19, 2018
    Copy the full SHA
    a758d0a View commit details

Commits on Mar 7, 2018

  1. Merge pull request #94 from akretion/feat/geodis_no_accents

    Geodis Don't send utf8 in EDI anymore
    hparfr authored Mar 7, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b840d2f View commit details

Commits on Sep 27, 2018

  1. Add coerce on dpd

    hparfr committed Sep 27, 2018
    Copy the full SHA
    07e0582 View commit details

Commits on Nov 26, 2018

  1. Remove trace

    hparfr committed Nov 26, 2018
    Copy the full SHA
    fa6349d View commit details
  2. Update changelog

    update version
    hparfr authored Nov 26, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    acca89e View commit details
  3. update version

    hparfr authored Nov 26, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    38f677a View commit details
  4. Merge pull request #99 from akretion/feat/dpd_coerce

    Add coerce on dpd
    hparfr authored Nov 26, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    90a96e5 View commit details
  5. Copy the full SHA
    9d70e66 View commit details
  6. Increment version

    hparfr committed Nov 26, 2018
    Copy the full SHA
    31803be View commit details
  7. Merge pull request #101 from akretion/feat/add_requirements.txt

    Feat/add requirements.txt
    hparfr authored Nov 26, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a084595 View commit details
  8. update .travis

    hparfr authored Nov 26, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9210f3b View commit details

Commits on Dec 19, 2018

  1. Copy the full SHA
    c9d28ba View commit details

Commits on Jan 7, 2019

  1. Merge pull request #103 from akretion/feat/geodis_company_name

    [FIX] Force company name in contact name for geodis
    hparfr authored Jan 7, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e1b2b96 View commit details
  2. Copy the full SHA
    3f6e45c View commit details
  3. Merge pull request #104 from akretion/fix-geodis-edi-coerce

    [FIX] geodis EDI accent coercing
    hparfr authored Jan 7, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    f8771e0 View commit details
  4. Update changelog and version

    hparfr committed Jan 7, 2019
    Copy the full SHA
    4cc87e3 View commit details
  5. Update to 0.3.7

    hparfr committed Jan 7, 2019
    Copy the full SHA
    a6f521f View commit details

Commits on Jan 17, 2019

  1. Copy the full SHA
    366adcf View commit details
  2. Merge pull request #105 from akretion/fix-geodis-edi-main-name

    Fix Geodis EDI main name if no company
    hparfr authored Jan 17, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b6ed367 View commit details

Commits on May 14, 2019

  1. FIX geodis/trs import

    bealdav committed May 14, 2019
    Copy the full SHA
    594e4f8 View commit details

Commits on May 16, 2019

  1. IMP python3 compatibility

    bealdav committed May 16, 2019
    Copy the full SHA
    9a60357 View commit details
  2. REMove TRS carrier

    bealdav committed May 16, 2019
    Copy the full SHA
    a9772e5 View commit details
  3. Merge pull request #106 from akretion/trs-remove

    REMove TRS carrier
    hparfr authored May 16, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    fb2e2fc View commit details

Commits on May 17, 2019

  1. REMove unicodecsv dep

    bealdav committed May 17, 2019
    Copy the full SHA
    240d3dd View commit details
  2. Copy the full SHA
    1d37eb7 View commit details

Commits on May 22, 2019

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    bbcc688 View commit details

Commits on Aug 26, 2019

  1. Implementation of zoom

    hparfr committed Aug 26, 2019
    Copy the full SHA
    54771e3 View commit details
  2. Add tests

    hparfr committed Aug 26, 2019
    Copy the full SHA
    b6f09b4 View commit details
  3. improve tests

    hparfr committed Aug 26, 2019
    Copy the full SHA
    0ff7e0c View commit details

Commits on Sep 2, 2019

  1. Improve api

    hparfr committed Sep 2, 2019
    Copy the full SHA
    3017395 View commit details

Commits on Sep 3, 2019

  1. Fix public api

    hparfr committed Sep 3, 2019
    Copy the full SHA
    7be2a77 View commit details

Commits on Sep 12, 2019

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    60dee47 View commit details
  2. Improve .travis

    hparfr committed Sep 12, 2019
    Copy the full SHA
    5a14a5d View commit details
  3. Merge pull request #114 from akretion/feat/geodis_zoom

    Feat/geodis zoom
    hparfr authored Sep 12, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    03b79ee View commit details
  4. Add pytest

    hparfr committed Sep 12, 2019
    Copy the full SHA
    655aa08 View commit details
  5. Merge pull request #115 from akretion/feat/impr_travis

    Improve .travis
    hparfr authored Sep 12, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    59eac76 View commit details
  6. Impr Changelog

    hparfr committed Sep 12, 2019
    Copy the full SHA
    0f39819 View commit details
  7. Improve setup.py

    hparfr committed Sep 12, 2019
    Copy the full SHA
    ee2842c View commit details

Commits on Feb 14, 2020

  1. Update geodis test endpoint

    hparfr authored Feb 14, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d374e82 View commit details

Commits on Apr 20, 2020

  1. Merge pull request #117 from akretion/update-geodis-test-servers

    Update geodis test endpoint
    florian-dacosta authored Apr 20, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7bb2167 View commit details

Commits on May 6, 2020

  1. Copy the full SHA
    a387141 View commit details

Commits on Aug 6, 2020

  1. Copy the full SHA
    f524819 View commit details

Commits on Sep 3, 2020

  1. Merge pull request #125 from akretion/fix-geodis-output-format

    Fix output format for geodis label
    florian-dacosta authored Sep 3, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    682da5e View commit details
  2. Update README.md

    bealdav authored Sep 3, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    49ea433 View commit details
Showing with 999 additions and 445 deletions.
  1. +7 −3 .travis.yml
  2. +28 −0 CHANGELOG.md
  3. +13 −5 README.md
  4. +1 −1 VERSION
  5. +2 −0 requirements.txt
  6. +1 −1 roulier.py
  7. +55 −19 roulier/api.py
  8. +0 −1 roulier/carriers/__init__.py
  9. +17 −11 roulier/carriers/dpd/dpd_api.py
  10. +3 −2 roulier/carriers/dummy/dummy_api.py
  11. +6 −2 roulier/carriers/geodis/__init__.py
  12. +45 −8 roulier/carriers/geodis/geodis.py
  13. +30 −15 roulier/carriers/geodis/geodis_api_edi.py
  14. +2 −3 roulier/carriers/geodis/geodis_api_find_localite_ws.py
  15. +216 −0 roulier/carriers/geodis/geodis_api_rest_ws.py
  16. +7 −4 roulier/carriers/geodis/geodis_api_ws.py
  17. +16 −3 roulier/carriers/geodis/geodis_common_ws.py
  18. +51 −0 roulier/carriers/geodis/geodis_decoder_rest_ws.py
  19. +1 −1 roulier/carriers/geodis/{geodis_decoder.py → geodis_decoder_ws.py}
  20. +8 −2 roulier/carriers/geodis/geodis_encoder_edi.py
  21. +34 −0 roulier/carriers/geodis/geodis_encoder_rest_ws.py
  22. +1 −1 roulier/carriers/geodis/geodis_encoder_ws.py
  23. +2 −2 roulier/carriers/geodis/geodis_transport_edi.py
  24. +116 −0 roulier/carriers/geodis/geodis_transport_rest_ws.py
  25. +2 −2 roulier/carriers/geodis/templates/geodis_demandeImpressionEtiquette.xml
  26. +1 −0 roulier/carriers/geodis/tests/__init__.py
  27. +253 −0 roulier/carriers/geodis/tests/test_rest.py
  28. +76 −51 roulier/carriers/laposte/laposte_api.py
  29. +0 −5 roulier/carriers/trs/__init__.py
  30. +0 −4 roulier/carriers/trs/templates/trs_deposit_slip.csv
  31. +0 −43 roulier/carriers/trs/templates/trs_generateLabel.zpl
  32. +0 −39 roulier/carriers/trs/trs.py
  33. +0 −45 roulier/carriers/trs/trs_api.py
  34. +0 −23 roulier/carriers/trs/trs_decoder.py
  35. +0 −34 roulier/carriers/trs/trs_encoder.py
  36. +0 −112 roulier/carriers/trs/trs_transport.py
  37. +0 −2 roulier/roulier.py
  38. +5 −1 setup.py
10 changes: 7 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
language: python
python:
- "2.7"
script:
- echo "1"
- "3.6"
install:
- pip install -r requirements.txt
- pip install pytest
script:
- pytest
deploy:
provider: pypi
user: hparfr
password:
secure: 1ef9l1DzCsZGYAp6TeqK6fw+IAB/GJyAM4nQwVrU+kzHGFItL50NBRL3GNjPOUscrOWV98jW1k9QJZ40caQQ+oH6WNg3jEIN3FOcERxMyFD3umLe8s/YIxCDWwBoMb1Hc+jhQ5saeUEliUkCFtecMI1WS+6McQHzdGd4+4WL9mWIp89PNdzJQHH9aHPi8fE651zAhE2mKB0TZmga+i82GSd3iHm29SZmo8wfX4vmqR0PcR+m70DVp0ETJQYb4jTdwnhEUE0XPGeGOhGu/wGPflrBZSc2q/YRfCLIQfPCQOa8zI7TfA20Ymy5HzBfE0kEa9s9OkMmelF4b82njUoEUs8Tgx/rtkJpQ7F18Y/mIm7hId64IAOOStpwBROjdwlfNjFRYbx0vBh+3BqpWUzwMFcPEdKbRyJQSo2v76NPWUG7Z7goeVvO/jEN//y3wZVAUojbIYpYULffVnSUHO1OjbzdQx6QlJtoMulntkLe/8jZLMEV1kw1YaL9/NgefFhfPMkOuhdg7EQErhk3hpSsrFV3sfPIULakKmrY2t1CuI3KyI0lEsgpcMt7kS3VzgtNfjK2fbVvG+3uwLAU/TMTad92t3rS6ifeh3U0m1YwJR7vxDjHV/KekptCgYkgvCVi99Jt/aM0cPO11Us9oOGesthJOzxUZZ8s7jnKj0s1mOk=
secure: go1MWqMNj5t21FWbZWlSvZsMYiAAJkgO8aeQRMCIv5ZQN8LGCE3nUQxjx1REY768lD9uMD6QgrXchxWWoWk+4B95cEjVOfi+CcINwOT9i3tvQoixwWhK/d4kgUmYggjCR1yOgGR75wc5CrAE5wghg36vnLAxJxxXR1mP5/L2Cz/0qSLUp7MEEal7j+Yo8Hx2o6/lBBHSjKXQugK5FlmOvyfJHHTARU4puSQdyBc/GfgKE6Uxt5vihD3lvj8n/HmXLG1nInEd0DKbIZvFc759ZAk3njKxuL1FWycdaSoGc3XVa5b3qFZa5+7LNrFOjldBm0O21MaHGDHgVE8DLs45i/PgC3L3ATSQ7eDx/1ArPazrZCSdn+umJtkxS2Id/KHLF1yDHguXmUtYwDjW4NmUSLAiVInz5Il/bnADFwXnOQ4WLdmU0GMHkWPLd+TJe4iPTj+SagVpUMVxtQXm2RgFhGSo5h8daK5/ZQnu8DEMX4dtdSKGrMF7FJkbBWFzXIMw8mGbIKKoSjw+nYIl72lNgaPorR3l9s6aR/bwHFaxF0sviSwebvNEecLKIP2TZEIU45u6CQEU5iKptCLzznWemA9mvzw/JDiUlehqm5M0cLgYiJf74QJJbrj6eTC1IK9oAo8iCfn3511u19SqPpnV2ovEenPHly1Gni0i/Gv8JQ0=
distributions: sdist bdist_wheel
on:
tags: true
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

# Next
- Misc python3 fix
- Remove TRS carrier because aborted project

Roadmap / TODO:

@@ -8,7 +11,32 @@ Roadmap / TODO:
- Improve documentation
- Support additionnal methods of api
- Write tests
- define an output API (tracking)
- generate api documentation

# 0.3.9 2019-09-12
- Improve setup.py

# 0.3.8 2019-09-12
- Geodis: add Zoom API implementation (tracking)
- Geodis: name specifications in comments in geodis.py
- Geodis: add tests about Zoom Api
- Geodis: GeodisDecoder -> GeodisDecoderWs
- Add tests in .travis

# 0.3.7 2019-01-07
- Geodis: fix sending company name
- Geodis: fix coerce for EDI
- Add coercing for oe

# 0.3.5 2018-11-26
- Add requirements.txt

# 0.3.4 2018-11-26
- DPD convert characters to ASCII (because issues on labels)

# 0.3.3 2018-02-19
- Geodis force ASCII conversion for EDI.

# 0.3.2 2018-02-06
- Geodis fix missing CTA segment
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# This BRANCH IS NO MORE UPDATED

# SEE `python2` BRANCH instead of this

# SEE `master` BRANCH for python 3

This branch in only kept for relation with PR


Roulier
===

@@ -11,7 +20,7 @@ Roulier will get a label + tracking number to your carrier for you.
* Roulier runs on your server and call each carrier API directly.
* You have to use your own credentials provided by each carriers.
* Roulier is Open Source software, AGPL-3
* Roulier integrate a multitude of carriers : Laposte, Geodis, DPD, K&N, TRS... more to come.
* Roulier integrate a multitude of carriers : Laposte, Geodis, DPD, K&N... more to come.



@@ -50,15 +59,15 @@ response = laposte.get_label({
})


print response
print(response)

```


Get supported carriers:
```python
from roulier import roulier
print roulier.get_carriers()
print(roulier.get_carriers())
```

To get the full list of parameters:
@@ -82,7 +91,7 @@ laposte = roulier.get('laposte')
api = laposte.api()
api['auth']['login'] = '12345'
...
print laposte.get_label(api)
print(laposte.get_label(api))

# {
# label: {
@@ -194,4 +203,3 @@ l_api._parcel()
* [Requests](http://docs.python-requests.org/) - HTTP requests
* [zplgrf](https://github.com/kylemacfarlane/zplgrf) - PNG to ZPL conversion
* [unidecode](https://pypi.python.org/pypi/Unidecode) - Remove accents from ZPL
* [unicodecsv](https://github.com/jdunck/python-unicodecsv) - CSV generation
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.2
0.3.7
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Cerberus==1.1
.
2 changes: 1 addition & 1 deletion roulier.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-

if __name__ == "__main__":
print "from Cli"
print("from Cli")
74 changes: 55 additions & 19 deletions roulier/api.py
Original file line number Diff line number Diff line change
@@ -7,14 +7,6 @@
class MyValidator(Validator):
"""Custom validator."""

def _validate_description(self, description, field, value):
"""Allow 'description' in schema.
The rule's arguments are validated against this schema:
{ 'description': 'a string'}
"""
pass

def _normalize_coerce_zpl(self, value):
"""Sanitze input for ZPL.
@@ -35,6 +27,44 @@ def _normalize_coerce_zpl(self, value):
val = val.replace("%c" % ctrl, "")
return val

def _normalize_coerce_accents(self, value):
"""Sanitize accents for some WS."""
if not isinstance(value, basestring):
return value
sanitized = (
value
# quick and dirty replacement
# of common accentued chars in french
# because some ws don't handle well utf8
.replace(u"é", "e")
.replace(u"è", "e")
.replace(u"ë", "e")
.replace(u"ê", "e")
.replace(u"ô", "o")
.replace(u"ï", "i")
.replace(u"ö", "o")
.replace(u"à", "a")
.replace(u"â", "a")
.replace(u"ç", "c")
.replace(u"û", "u")
.replace(u"ù", "u")
.replace(u"É", "E")
.replace(u"È", "E")
.replace(u"Ë", "E")
.replace(u"Ê", "E")
.replace(u"Ô", "O")
.replace(u"Ï", "I")
.replace(u"Ö", "O")
.replace(u"À", "A")
.replace(u"Â", "A")
.replace(u"Ç", "C")
.replace(u"Û", "U")
.replace(u"Ù", "U")
.replace(u"œ", "oe")
.replace(u"Œ", "OE")
).encode('ascii', 'ignore') # cut remaining chars
return sanitized


class Api(object):
"""Define expected fields of carriers.
@@ -53,14 +83,15 @@ def _validator(self):

def _address(self):
return {
'company': {'type': 'string', 'default': '', 'description': 'Company'},
'company': {'type': 'string', 'default': ''},
'name': {'type': 'string', 'default': '', 'required': True, 'empty': False},
'street1': {'type': 'string', 'default': ''},
'street2': {'type': 'string', 'default': ''},
'country': {'type': 'string', 'default': '', 'description': 'ISO 3166-1 alpha-2 '},
'country': {'type': 'string', 'default': ''},
# , 'description': 'ISO 3166-1 alpha-2 '},
'city': {'type': 'string', 'default': ''},
'zip': {'type': 'string', 'default': ''},
'phone': {'type': 'string', 'default': '', 'description': 'Phone'},
'phone': {'type': 'string', 'default': ''},
'email': {'type': 'string', 'default': ''},
}

@@ -78,8 +109,9 @@ def _to_address(self):

def _parcel(self):
return {
"weight": {'type': 'float', 'default': '', 'description': 'Weight in kg', 'required': True, 'empty': False},
"weight": {'type': 'float', 'default': '', 'required': True, 'empty': False},
}
# 'description': 'Weight in kg',

def _parcels(self):
v = MyValidator()
@@ -91,16 +123,20 @@ def _parcels(self):

def _service(self):
return {
"product": {'default': '', 'description': ''},
"agencyId": {'default': '', 'description': ''},
"customerId": {'default': '', 'description': ''},
"product": {'default': ''},
"agencyId": {'default': ''},
"customerId": {'default': ''},
"shippingId": {'default': ''},
'shippingDate': {'default': '', 'type': 'string', 'required': True, 'empty': False, 'description': 'When the carrier has the package. Format: YYYY/MM/DD'},
'reference1': {'type': 'string', 'default': '', 'description': 'Additionnal info visible by the client. Example : order number'},
# 'description': 'When the carrier has the package. Format: YYYY/MM/DD'
'shippingDate': {'default': '', 'type': 'string', 'required': True, 'empty': False},
# 'description': 'Additionnal info visible by the client. Example : order number'
'reference1': {'type': 'string', 'default': ''},
'reference2': {'type': 'string', 'default': ''},
'reference3': {'type': 'string', 'default': ''},
"labelFormat": {'description': 'Format of output (usually pdf or zpl)', 'default': ''},
"instructions": {'description': 'Additionnal instructions for delivery', 'default': ''},
# 'description': 'Format of output (usually pdf or zpl)'
"labelFormat": {'default': ''},
# 'description': 'Additionnal instructions for delivery',
"instructions": {'default': ''},
}

def _auth(self):
1 change: 0 additions & 1 deletion roulier/carriers/__init__.py
Original file line number Diff line number Diff line change
@@ -4,4 +4,3 @@
from . import geodis
from . import dummy
from . import dpd
from . import trs
28 changes: 17 additions & 11 deletions roulier/carriers/dpd/dpd_api.py
Original file line number Diff line number Diff line change
@@ -30,13 +30,16 @@ def _service(self):
schema['labelFormat'].update({'required': True, 'empty': False})
schema['agencyId'].update({
'required': True, 'empty': False,
'description': 'Agency code int(3)'})
# 'description': 'Agency code int(3)'
})
schema['customerCountry'] = {
'required': True, 'empty': False,
'description': 'Customer country code (France = 250) int(3)'}
# 'description': 'Customer country code (France = 250) int(3)'
}
schema['customerId'].update({
'required': True, 'empty': False,
'description': 'Customer number int(6)'})
# 'description': 'Customer number int(6)'
})
schema['shippingDate'].update({'required': False, 'empty': True})

# mettre ça ensemble ?
@@ -47,34 +50,37 @@ def _service(self):
'empty': False,
'required': True,
'default': DPD_PRODUCTS[0],
'description': 'Type de produit',
# 'description': 'Type de produit',
'allowed': DPD_PRODUCTS
})

schema['dropOffLocation'] = {
'default': '',
'empty': True,
'required': False,
'description': 'Drop-off Location id (Relais Colis)'
# 'description': 'Drop-off Location id (Relais Colis)'
}

return schema

def _address(self):
schema = super(DpdApi, self)._address()
schema['street2']['description'] = (
"N/A for DPD. It will be appended to street1")
# schema['street2']['description'] = (
# "N/A for DPD. It will be appended to street1")
schema['country'].update({'required': True, 'empty': False})
schema['zip'].update({'required': True, 'empty': False})
schema['city'].update({'required': True, 'empty': False})
return schema

def _to_address(self):
schema = super(DpdApi, self)._to_address()
schema['firstName'] = {'default': '', 'description': """First name"""}
schema['door1'] = {'default': '', 'description': """Door code 1"""}
schema['door2'] = {'default': '', 'description': """Door code 2"""}
schema['intercom'] = {'default': '', 'description': """Intercom"""}
# , 'description': """First name""",
schema['firstName'] = {'default': '', 'coerce': 'accents'}
schema['door1'] = {'default': ''} # 'description': """Door code 1"""
schema['door2'] = {'default': ''} # 'description': """Door code 2"""}
schema['intercom'] = {'default': ''} # 'description': """Intercom"""}
for field in ['city', 'company', 'name', 'street1', 'street2']:
schema[field].update({'coerce': 'accents'})
return schema

def _from_address(self):
5 changes: 3 additions & 2 deletions roulier/carriers/dummy/dummy_api.py
Original file line number Diff line number Diff line change
@@ -16,12 +16,13 @@ def _service(self):

def _to_address(self):
schema = super(DummyApi, self)._to_address()
schema['dept'] = {'default': '', 'description': 'Region code'}
# , 'description': 'Region code'
schema['dept'] = {'default': ''}
return schema

def _parcel(self):
schema = super(DummyApi, self)._parcel()
schema['reference'] = {'default': '', 'description': 'Parcel reference'}
schema['reference'] = {'default': ''}
return schema

def _auth(self):
8 changes: 6 additions & 2 deletions roulier/carriers/geodis/__init__.py
Original file line number Diff line number Diff line change
@@ -4,8 +4,12 @@
from . import geodis_api_ws
from . import geodis_api_find_localite_ws
from . import geodis_api_edi
from . import geodis_api_rest_ws
from . import geodis_encoder_ws
from . import geodis_encoder_edi
from . import geodis_decoder
from . import geodis_encoder_rest_ws
from . import geodis_decoder_ws
from . import geodis_decoder_rest_ws
from . import geodis_transport_ws
from . import geodis_transport_edi
from . import geodis_transport_edi
from . import geodis_transport_rest_ws
53 changes: 45 additions & 8 deletions roulier/carriers/geodis/geodis.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
# -*- coding: utf-8 -*-
"""Implementation for Geodis."""
from geodis_encoder_edi import GeodisEncoderEdi
from geodis_encoder_ws import GeodisEncoderWs
from geodis_decoder import GeodisDecoder
from geodis_transport_ws import GeodisTransportWs
from geodis_transport_edi import GeodisTransportEdi

from .geodis_encoder_edi import GeodisEncoderEdi
from .geodis_encoder_ws import GeodisEncoderWs
from .geodis_encoder_rest_ws import GeodisEncoderRestWs
from .geodis_decoder_ws import GeodisDecoderWs
from .geodis_decoder_rest_ws import GeodisDecoderRestWs
from .geodis_transport_ws import GeodisTransportWs
from .geodis_transport_edi import GeodisTransportEdi
from .geodis_transport_rest_ws import GeodisTransportRestWs

from roulier.carrier import Carrier
from roulier.exception import InvalidAction

# Specifications:
# rest
# usage: tracking
# document: GEODIS - GUIDE TECHNIQUE SIC - Nouveau service Zoom - v1.1.docx
# date: 2018
# edi:
# usage: deposit slip
# document: IFCSUM_D96A_1026_Client_Construit_FR.doc
# date: 2012
# xml:
# usage: label and findLocalite
# document: GEODIS_Nouvelle eětiquette_GEOLABEL_v FR_V1.9.1.pdf
# date: 2016


class Geodis(Carrier):
"""Implementation for Geodis."""
@@ -44,9 +63,15 @@ def get_label(self, data, api=False):
def address_validator(self, data, api=False):
return self._get_ws(data, api, 'findLocalite')

def get_tracking(self, data, api=False):
return self._get_rest_ws(data, api, 'tracking')

def get_tracking_list(self, data, api=False):
return self._get_rest_ws(data, api, 'trackingList')

def _get_ws(self, data, api=False, action=None):
encoder = GeodisEncoderWs()
decoder = GeodisDecoder()
decoder = GeodisDecoderWs()
transport = GeodisTransportWs()

if api:
@@ -60,9 +85,21 @@ def _get_ws(self, data, api=False, action=None):
request['infos'],
)

def _get_rest_ws(self, data, api=False, action=None):
encoder = GeodisEncoderRestWs()
decode = GeodisDecoderRestWs()
transport = GeodisTransportRestWs()
if api:
return encoder.api(action=action)
request = encoder.encode(data, action)
response = transport.send(request)
return decode.decode(response, action)

ACTIONS = {
'label': get_label,
'findLocalite': address_validator,
'demandeImpressionEtiquette': get_label,
'edi': get_edi
}
'edi': get_edi,
'tracking': get_tracking,
'trackingList': get_tracking_list,
}
45 changes: 30 additions & 15 deletions roulier/carriers/geodis/geodis_api_edi.py
Original file line number Diff line number Diff line change
@@ -8,19 +8,19 @@ class GeodisApiEdi(Api):
def _service(self):
schema = {
"depositId": {
'type': 'string',
'type': 'string', 'coerce': 'accents',
'default': '', 'empty': False, 'required': True},
"depositDate": {
'type': 'datetime',
'type': 'datetime', 'coerce': 'accents',
'default': '', 'empty': False, 'required': True},
"customerId": {
'type': 'string',
'type': 'string', 'coerce': 'accents',
'default': '', 'empty': False, 'required': True},
"interchangeSender": {
'type': 'string',
'type': 'string', 'coerce': 'accents',
'default': '', 'empty': False, 'required': True},
"interchangeRecipient": {
'type': 'string',
'type': 'string', 'coerce': 'accents',
'default': '', 'empty': False, 'required': True},
}
return schema
@@ -32,6 +32,9 @@ def _address(self):
'maxlength': 2})
schema['zip'].update({'required': True, 'empty': False})
schema['city'].update({'required': True, 'empty': False})
for key in schema:
if schema[key].get('type', '') == 'string':
schema[key].update({'coerce': 'accents'})
return schema

def _from_address(self):
@@ -41,26 +44,35 @@ def _from_address(self):
schema['siret'] = {
'type': 'string', 'required': True,
'default': '', 'empty': False}
for key in schema:
if schema[key].get('type', '') == 'string':
schema[key].update({'coerce': 'accents'})
return schema

def _parcel(self):
weight = GeodisApiWs()._parcel()['weight']
return {
'weight': weight,
# 'description': 'Barcode of the parcel'
'barcode': {
'type': 'string', 'empty': False, 'default': '',
'required': True,
'description': 'Barcode of the parcel'}
'required': True, 'coerce': 'accents'}
}

def _to_address(self):
schema = GeodisApiWs()._to_address()
for key in schema:
if schema[key].get('type', '') == 'string':
schema[key].update({'coerce': 'accents'})
return schema

def _shipments(self):
ws_api = GeodisApiWs()
v = MyValidator()
schema = {
'to_address': {
'type': 'dict',
'schema': ws_api._to_address(),
'default': v.normalized({}, ws_api._to_address())
'schema': self._to_address(),
'default': v.normalized({}, self._to_address())
},
'parcels': {
'type': 'list',
@@ -76,18 +88,20 @@ def _shipments(self):
'productPriority': {
'type': 'string', 'default': '', 'empty': True,
'required': False,
'description': """4219, 1: express, 3: normal speed"""},
# 'description': """4219, 1: express, 3: normal speed"""
},
'productOption': {
'type': 'string', 'default': '', 'empty': True,
'required': False,
'description': """7273, like RDV, B2C, BRT, AEX, A2P..."""},
# 'description': """7273, like RDV, B2C, BRT, AEX, A2P..."""
},
'notifications': {
'type': 'string',
'default': GEODIS_ALLOWED_NOTIFICATIONS[0],
'allowed': GEODIS_ALLOWED_NOTIFICATIONS,
'required': False,
'description': """7085 : Notify recipient by
M(ail), S(ms), P(=M+S)"""},
# 'description': """7085 : Notify recipient by M(ail), S(ms), P(=M+S)"""
},
'shippingId': {
'type': 'string', 'default': '', 'empty': False,
'required': True},
@@ -102,9 +116,10 @@ def _shipments(self):
}

def _schemas(self):
return {
schemas = {
'service': self._service(),
'shipments': self._shipments(),
'agency_address': self._from_address(),
'from_address': self._from_address(),
}
return schemas
5 changes: 2 additions & 3 deletions roulier/carriers/geodis/geodis_api_find_localite_ws.py
Original file line number Diff line number Diff line change
@@ -7,9 +7,8 @@ class GeodisApiFindLocaliteWs(Api):

def _service(self):
schema = {}
schema['is_test'] = {
'type': 'boolean', 'default': True,
'description': 'Use test Ws'}
# 'description': 'Use test Ws'
schema['is_test'] = {'type': 'boolean', 'default': True}
return schema

def _address(self):
216 changes: 216 additions & 0 deletions roulier/carriers/geodis/geodis_api_rest_ws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# -*- coding: utf-8 -*-
"""Implementation of Geodis Api."""
from roulier.api import Api


class GeodisApiRestWs(Api):

def _schemas(self):
return {
'service': self._service(),
'auth': self._auth(),
}

def normalize(self, data):
externalApi = super(GeodisApiRestWs, self)
internalApi = self._interal_api()
step1 = externalApi.normalize(data)
step2 = internalApi.normalize(step1)
return step2

def api_values(self):
"""Return a dict containing expected keys.
It's a normalized version of the schema.
only internal api
"""
return self._validator().normalized({}, self.api_schema())

def _interal_api(self):
pass


class GeodisMappingIn(Api):
"""Internal API"""

def flatten(self, data, out):
for (key, val) in data.items():
if isinstance(val, dict):
self.flatten(val, out)
else:
out[key] = val

def normalize(self, data):
without_auth = {
key: val
for (key, val) in data.items()
if key != 'auth'
}
flat = {
'auth': data['auth'],
'service': {}
}
self.flatten(without_auth, flat['service'])
normalized = super(GeodisMappingIn, self).normalize(flat)
return normalized


class GeodisApiTrackingListMapping(GeodisMappingIn):
"""Internal API
Used to rename fields."""
def _schemas(self):
return {"service": {
"shippingDate": {"rename": "dateDepart"},
"shippingDateStart": {"rename": "dateDepartDebut"},
"shippingDateEnd": {"rename": "dateDepartFin"},
"agencyId": {"rename": "codeSa"},
"customerId": {"rename": "codeClient"},
"reference1": {"rename": "reference1"},
"reference2": {"rename": "refDest"},
"name": {"rename": "nomDest"},
"zip": {"rename": "codePostalDest"},
'estDeliveryDate': {"rename": 'dateLivraison'},
"shippingId": {"rename": "noRecepisse"},
"barcode": {"rename": "cabColis"},
"trackingId": {"rename": "noSuivi"},
}}


class GeodisApiTrackingMapping(GeodisMappingIn):
"""Internal API
Used to rename fields."""
def _schemas(self):
return {"service": {
"trackingId": {"rename": "noSuivi"},
}}


class GeodisApiTracking(GeodisApiRestWs):

def _service(self):
schema = {
"refUniExp": {"type": "string", "default": "", "empty": True},
"trackingId": {"type": "string", "default": "", "empty": True},
}
return schema

def _interal_api(self):
return GeodisApiTrackingMapping()


class GeodisApiTrackingList(GeodisApiRestWs):
def _service(self):
return {
"shippingDate": {"type": "string", "default": "", "empty": True},
"shippingDateStart": {"type": "string", "default": "", "empty": True},
"shippingDateEnd": {"type": "string", "default": "", "empty": True},
"agencyId": {"type": "string", "default": "", "empty": True},
"customerId": {"type": "string", "default": "", "empty": True},
"reference1": {"type": "string", "default": "", "empty": True},
"reference2": {"type": "string", "default": "", "empty": True},
}

def _tracking(self):
return {
"estDeliveryDate": {"type": "string", "default": "", "empty": True},
"shippingId": {"type": "string", "default": "", "empty": True},
"barcode": {"type": "string", "default": "", "empty": True},
"trackingId": {"type": "string", "default": "", "empty": True},
}

def _to_address(self):
return {
"name": {"type": "string", "default": "", "empty": True},
"zip": {"type": "string", "default": "", "empty": True},
}

def _schemas(self):
schema = super(GeodisApiTrackingList, self)._schemas()
schema['tracking'] = self._tracking()
schema['to_address'] = self._to_address()
return schema

def _interal_api(self):
return GeodisApiTrackingListMapping()


class GeodisMappingOut(Api):

def normalize(self, data):
schema = self.schema()
# self.add_tracking_code(data)
return self.visit(data, schema)

def visit(self, data, schema):
out = {}
for (key, val) in schema.items():
if isinstance(val, dict):
out[key] = self.visit(data, val)
else:
out[key] = data[val]
return out


class GeodisApiTrackingListOut(GeodisMappingOut):

def to_address(self):
return {
'street1': 'adresse1Dest',
'street2': 'adresse2Dest',
'country': 'codePaysDest',
'zip': 'codePostalDest',
'country_name': 'libellePaysDest',
'name': 'nomDest',
'city': 'villeDest',
}

def from_address(self):
return {
'street1': 'adresse1Exp',
'street2': 'adresse2Exp',
'country': 'codePaysExp',
'zip': 'codePostalExp',
'country_name': 'libellePaysExp',
'name': 'nomExp',
'city': 'villeExp',
}

def parcels(self):
return {
'weight': 'poids',
}

def service(self):
return {
"product": 'codeProduit',
"agencyId": 'codeSa',
"customerId": 'codeClient',
"shippingId": 'noRecepisse',
'shippingDate': 'dateDepart',
'reference1': 'reference1',
'reference2': 'reference2',
'reference3': 'refDest',
'option': 'codeOption',
}

def tracking(self):
return {
'statusDate': 'dateEtat',
'estDeliveryDate': 'dateLivraison',
'status': 'status',
'statusDetails': 'libelleLongEtat',
'trackingCode': 'noSuivi',
'publicUrl': 'urlSuiviDestinataire',
'proofUrl': 'urlImageEnlevementLivraison',
}

def schema(self):
return {
'parcels': self.parcels(),
'service': self.service(),
'from_address': self.from_address(),
'to_address': self.to_address(),
'tracking': self.tracking(),
}
11 changes: 7 additions & 4 deletions roulier/carriers/geodis/geodis_api_ws.py
Original file line number Diff line number Diff line change
@@ -27,15 +27,17 @@ def _service(self):
schema['customerId'].update({'required': True, 'empty': False})
schema['shippingId'].update({'required': True, 'empty': False})
schema['hubId'] = {
'description': 'TEOS : code agence Hub de sortie',
# 'description': 'TEOS : code agence Hub de sortie',
'default': ''
}
schema['is_test'] = {
'type': 'boolean', 'default': True,
'description': 'Use test Ws'}
# 'description': 'Use test Ws'
}
schema['option'] = {
'type': 'string', 'default': False,
'description': """Options (RDW, RDV, ETG, ...)"""}
# 'description': """Options (RDW, RDV, ETG, ...)"""
}
schema['notification'] = {
'default': GEODIS_ALLOWED_NOTIFICATIONS[0],
'allowed': GEODIS_ALLOWED_NOTIFICATIONS}
@@ -69,7 +71,8 @@ def _parcel(self):
schema['reference'] = {
'type': 'string', 'required': False,
'empty': True, 'default': '',
'description': 'Description of this parcel'}
# 'description': 'Description of this parcel'
}
return schema

def _parcels(self):
19 changes: 16 additions & 3 deletions roulier/carriers/geodis/geodis_common_ws.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
# -*- coding: utf-8 -*-
"""Store infos about ws"""
from .geodis_api_ws import GeodisApiWs
from .geodis_api_rest_ws import (
GeodisApiTracking,
GeodisApiTrackingList,)
from .geodis_api_find_localite_ws import GeodisApiFindLocaliteWs


GEODIS_INFOS = {
'demandeImpressionEtiquette': {
'xmlns': 'http://impression.service.web.etiquette.geodis.com',
'endpoint': "http://espace.geodis.com/geolabel/services/ImpressionEtiquette", # nopep8
'endpoint_test': "http://espace.recette.geodis.com/geolabel/services/ImpressionEtiquette", # nopep8
'endpoint_test': "http://espace-rct.geodis.com/geolabel/services/ImpressionEtiquette", # nopep8
'api': GeodisApiWs,
},
'findLocalite': {
'xmlns': 'http://localite.service.web.etiquette.geodis.com',
'endpoint': "http://espace.geodis.com/geolabel/services/RechercherLocalite", # nopep8
'endpoint_test': "http://espace.recette.geodis.com/geolabel/services/RechercherLocalite", # nopep8
'endpoint': "https://espace.geodis.com/geolabel/services/RechercherLocalite", # nopep8
'endpoint_test': "https://espace-rct.geodis.com/geolabel/services/RechercherLocalite", # nopep8
'api': GeodisApiFindLocaliteWs,
},
'trackingList': {
'endpoint': 'https://espace-client.geodis.com/services',
'service': 'api/zoomclient/recherche-envois',
'api': GeodisApiTrackingList,
},
'tracking': {
'endpoint': 'https://espace-client.geodis.com/services',
'service': 'api/zoomclient/recherche-envoi',
'api': GeodisApiTracking,
}
}
51 changes: 51 additions & 0 deletions roulier/carriers/geodis/geodis_decoder_rest_ws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
"""Geodis XML -> Python."""
from roulier.codec import Decoder
from .geodis_api_rest_ws import GeodisApiTrackingListOut

class GeodisDecoderRestWs(Decoder):
"""Geodis XML -> Python."""

def decode(self, payload, action):
"""Geodis JSON -> Python.
payload[body] : dict
payload[request] : Requests obj
"""
body = payload['body']
if action == 'trackingList':
mapping = GeodisApiTrackingListOut()
formatted = []
for line in body:
self.add_tracking_code(line)
formatted.append(
mapping.normalize(line)
)
else:
# NOT implemented
formatted = body
return formatted



def add_tracking_code(self, data):
# MVL: mise en livraison
# AAR: en cours acheminement
# None: en attente prise en charge
# LIV: livré
# CFM: conforme
if data['codeSituation'] == 'LIV':
state = 'DELIVERED'
elif data['codeSituation'] == 'SOL':
state = 'RETURNED'
elif data['codeSituation'] in ('MLV', 'AAR'):
state = 'TRANSIT'
else:
state = 'UNKNOWN'
data['status'] = state

# CODES=
# 'UNKNOWN',
# 'DELIVERED',
# 'TRANSIT',
# 'FAILURE',
# 'RETURNED'
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
from .geodis_common_ws import GEODIS_INFOS


class GeodisDecoder(Decoder):
class GeodisDecoderWs(Decoder):
"""Geodis XML -> Python."""

def decode(self, body, parts, infos):
10 changes: 8 additions & 2 deletions roulier/carriers/geodis/geodis_encoder_edi.py
Original file line number Diff line number Diff line change
@@ -31,6 +31,12 @@ def encode(self, api_input):
def encode_shipment(self, shipment, service, idx):
packs = shipment['parcels']
to_address = shipment['to_address']
# There is company notion in to_address, so if company exists
# we put it in dest name and person name in contact name.
# if it does not exist, we just put person name in both dest name
# and contact name. Same is done in webservice side.
dest_name = to_address.get('company') or to_address['name']
contact_name = to_address['name']
lines = [
['CNI', '%s' % idx, shipment['shippingId']],
['TSR', '2', # 4065
@@ -51,14 +57,14 @@ def encode_shipment(self, shipment, service, idx):
["NAD", "CN",
"",
"", # C058
to_address['name'],
dest_name,
[to_address['street1'], to_address['street2']],
to_address['city'],
"", # 3164
to_address['zip'],
to_address['country']
],
['CTA', 'IC', ['', to_address['name']]],
['CTA', 'IC', ['', contact_name]],
]
if to_address['email']:
lines.append(['COM', [to_address['email'], 'EM']])
34 changes: 34 additions & 0 deletions roulier/carriers/geodis/geodis_encoder_rest_ws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
"""Implementation of Geodis Api."""
from roulier.codec import Encoder
from roulier.exception import InvalidApiInput
from .geodis_common_ws import GEODIS_INFOS


class GeodisEncoderRestWs(Encoder):

def api(self, action):
api = GEODIS_INFOS[action]['api']()
return api.api_values()

def encode(self, api_input, action):
api = GEODIS_INFOS[action]['api']()
if not api.validate(api_input):
raise InvalidApiInput(
'Input error : %s' % api.errors(api_input))
data = api.normalize(api_input)

infos = {
'url': "%s/%s" % (
GEODIS_INFOS[action]['endpoint'],
GEODIS_INFOS[action]['service'],
),
'service': GEODIS_INFOS[action]['service'],
'action': action,
}

return {
'headers': data['auth'],
'body': data['service'],
'infos': infos,
}
2 changes: 1 addition & 1 deletion roulier/carriers/geodis/geodis_encoder_ws.py
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ def encode(self, api_input, action):
}

def request_impression_etiquette():
infos["output_format"] = api_input['service']['labelFormat'],
infos["output_format"] = api_input['service']['labelFormat']
data['service']['labelFormat'] = self.lookup_label_format(
data['service']['labelFormat'])
data['service']['shippingDate'] = (
4 changes: 2 additions & 2 deletions roulier/carriers/geodis/geodis_transport_edi.py
Original file line number Diff line number Diff line change
@@ -53,7 +53,7 @@ def parse_lines(lines):
return "\n".join([parse_segment(segment) for segment in lines])

def sanitize(token):
# replace escapable chars by space
# remove accents and replace escapable chars by space
# because, replacing "'" by "?'"
# increase length of the token
# which is limited by 35
@@ -63,7 +63,7 @@ def sanitize(token):
.replace("'", " ")
.replace("+", " ")
.replace(":", " ")
)
).encode('ascii', 'ignore') # cut remaining chars
return sanitized

return parse_lines(arr)
116 changes: 116 additions & 0 deletions roulier/carriers/geodis/geodis_transport_rest_ws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
"""Implement geodisWS."""
import requests

from roulier.transport import Transport
from roulier.exception import CarrierError
import json
import logging
import hashlib
import time

log = logging.getLogger(__name__)


class GeodisTransportRestWs(Transport):
"""Implement Geodis Rest WS communication."""

def get_token(self, id, timestamp, lang, hash):
params = [id, timestamp, lang, hash]
return ';'.join(params)

def get_hash(self, api_key, id, timestamp, lang, service, json_data):
return hashlib.sha256(
";".join([
api_key, id, timestamp, lang, service,
json_data
]).encode("utf-8")
).hexdigest()

def prepare_data(self, data, login, api_key, service):
timestamp = "%d" % (time.time() * 1000)
lang = "fr"
body = json.dumps(data)
hash = self.get_hash(api_key, login, timestamp, lang, service, body)
token = self.get_token(login, timestamp, lang, hash)
return body, token

def send(self, payload):
"""Call this function.
Args:
payload.body: JSON
payload.header : auth
payload.infos: { url: string, xmlns: string}
Return:
{
response: (Requests.response)
body: XML response (without soap)
parts: empty dict // compat with WS
}
"""
body, token = self.prepare_data(
payload['body'],
payload['headers']['login'],
payload['headers']['password'],
payload['infos']['service']
)

infos = payload['infos']
infos['token'] = token

response = self.send_request(body, infos)
log.info('WS response time %s' % response.elapsed.total_seconds())
return self.handle_response(response)

def send_request(self, body, infos):
"""Send body to geodis WS."""
ws_url = infos['url']
token = infos['token']
return requests.post(
ws_url,
headers={
'X-GEODIS-Service': token,
},
data=body)

def handle_500(self, response):
"""Handle reponse in case of ERROR 500 type."""
# TODO : put a try catch (like wrong server)
log.warning('Geodis error 500')
errors = [{
"id": "",
"message": "",
}]
raise CarrierError(response, errors)

def handle_true_negative_error(self, response, payload):
"""When servers answer an error with a 200 status code."""
errors = [{
"id": payload.get('codeErreur', ''),
"message": payload.get('texteErreur', '')
}]
raise CarrierError(response, errors)

def handle_200(self, response):
"""Handle response type 200."""
payload = json.loads(response.text)
if payload['ok'] is not True:
self.handle_true_negative_error(response, payload)
return {
"body": payload.get('contenu', []),
"parts": [],
"response": response,
}

def handle_response(self, response):
"""Handle response of webservice."""
if response.status_code == 500:
return self.handle_500(response)
elif response.status_code == 200:
return self.handle_200(response)
else:
raise CarrierError(response, [{
'id': None,
'message': "Unexpected status code from server",
}])
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@
<imp:telexp>{{ address.phone }}</imp:telexp>
{% endwith %}
{% with address = receiver_address %}
<imp:nomdst>{{ address.name }}</imp:nomdst>
<imp:nomdst>{{ address.company or address.name }}</imp:nomdst>
<imp:ad1dst>{{ address.street1 }}</imp:ad1dst>
<imp:ad2dst>{{ address.street2 }}</imp:ad2dst>
<imp:paydst>{{ address.country }}</imp:paydst>
@@ -39,4 +39,4 @@
{% endfor %}
<imp:refclt>{{ service.reference1 }}</imp:refclt>
<imp:codopt>{{ service.option }}</imp:codopt>
</imp:demandeImpressionEtiquette>
</imp:demandeImpressionEtiquette>
1 change: 1 addition & 0 deletions roulier/carriers/geodis/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_rest
253 changes: 253 additions & 0 deletions roulier/carriers/geodis/tests/test_rest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# -*- coding: utf-8 -*-

import json
from collections import OrderedDict

from ..geodis_decoder_rest_ws import GeodisDecoderRestWs
from ..geodis_encoder_rest_ws import GeodisEncoderRestWs
from ..geodis_transport_rest_ws import GeodisTransportRestWs
from ..geodis_api_rest_ws import GeodisApiTrackingListOut


def test_encode():
encoder = GeodisEncoderRestWs()
api = encoder.api('trackingList')
payload = encoder.encode(api, 'trackingList')
infos = payload['infos']
assert infos['action'] == 'trackingList'
assert infos['service'] in infos['url']


def test_encode_api():
encoder = GeodisEncoderRestWs()
data = encoder.api('trackingList')

data['service']['shippingDateStart'] = "2019-07-02"
data['service']['shippingDateEnd'] = "2019-07-02"
data['auth']['login'] = 'Akretion'
data['auth']['password'] = '121221789271'

payload = encoder.encode(data, 'trackingList')
body = payload['body']
assert body['dateDepartFin'] == "2019-07-02"


def test_transport_hash():
"""Ensure we encode the has the same way as the specification
We create the exact same json as they (ordonned, separators)
"""
api_key = "d89b703bfe0d440a966ff3d996f5936a"
login = "QTTCLT"
timestamp = "1546941256145"
lang = "fr"
service = "api/zoomclient/recherche-envois"
params = OrderedDict([
("dateDepart", ""),
("dateDepartDebut", "2018-12-09"),
("dateDepartFin", "2019-01-08"),
("noRecepisse", ""),
("reference1", ""),
("cabColis", ""),
("noSuivi", ""),
("codeSa", "084135"),
("codeClient", ""),
("codeProduit", ""),
("typePrestation", "EXP"),
("dateLivraison", ""),
("refDest", ""),
("nomDest", ""),
("codePostalDest", ""),
("natureMarchandise", ""),
])
body = json.dumps(params, separators=(',', ':'))
transport = GeodisTransportRestWs()
hash = transport.get_hash(api_key, login, timestamp, lang, service, body)
token = transport.get_token(login, timestamp, lang, hash)
expected = (
"QTTCLT;1546941256145;fr;" +
"1b59ef28395469bdd3c823adc2603c469c657ed968e96375b0095307e0460fdf")
assert token == expected


def test_decoder_visit():
api = GeodisApiTrackingListOut()
schema = {
'str': 'simple_string',
'int': 'simple_integer',
'inner': {
'inner_a': 'inner_a',
'inner_b': 'inner_b',
}
}
data = {
'simple_string': 'abcdef',
'simple_integer': 3,
'inner_a': 'a',
'inner_b': 'b',
'no_lookup': 'should not fail',
}
ret = api.visit(data, schema)
assert ret['str'] == data['simple_string']
assert ret['int'] == data['simple_integer']
assert ret['inner']['inner_a'] == data['inner_a']


data = (
"""{"contenu": [{"statutServicesLivraison": "","""
""" "libelleLongEtat": "Livr\u00e9e", "dateEtat": "2019-07-10","""
""" "libelleLivraison": "Livraison le", "emissionEqc": null,"""
""" "libelleStatutServicesLivraison": "", "adresse1Exp": "axxxZ","""
""" "villeExp": "STRASBOURG", "temperatureMin": null, "codeOption":"""
""" "RET", "libellePrestation": "Retour/Trans. Fr. - Express","""
""" "urlPreuveService": "", "adresse2Exp": "wcvwv", "dateEtatFrs":"""
""" "10/07/2019", "avecMatiereDangereuse": false, "dateDepartFrs":"""
""" "09/07/2019", "urlImageEnlevementLivraison":"""
""" "http://github.com/akretion/roulier", "emissionEqa": null,"""
""" "dateLivraisonFrs": "10/07/2019", "noRecepisse": "12212122","""
""" "nbErreursNotification": 0, "listServicesLivraison": [],"""
""" "delaiInstruction": 0, "adresse1Dest": "27 rue Henri Rolland","""
""" "libellePaysExp": "France","""
""" "codePostalExp": "69100", "envoiRegroupement": false,"""
""" "refDest": "", "temperatureMed": null,"""
""" "dateLimiteInstruction": null, "nomDest": "TdsdsfsfsfsT","""
""" "avecInstructionDonnee": false, "uniteInstruction": "", "refUniEnl":"""
""" 1212121221, "libelleEtat": "Livr\u00e9e", "nomExp": "RETOUR","""
""" "urlSuiviDestinataire": "https://akretion.com", "adresse2Dest": "","""
""" "emissionPar": null, "codePaysDest": "FR", "nbExcursionsTemp": null,"""
""" "noRecepRegroupement": "", "envoiRegroupe": false, "reference1":"""
""" "abcdeefefefe", "dateLivraison": "2019-07-10", "loginDestinataire":"""
""" "12121212121", "nbPalettes": 0, "refUniExp": 1212212121,"""
""" "avecAttenteInstruction": false, "codePaysExp": "FR", "codeClient":"""
""" "01234", "listEnvoisRegroupes": [], "codeProduit": "ENE","""
""" "codeJustification": "NEM", "villeDest": "LESQUIN", "nbColis": 2,"""
""" "codePostalDest": "59810", "libellePaysDest": "France","""
""" "typePrestation": "EXP", "refUniRegroupement": 0, "codeSa":"""
""" "122121", "noSuivi": "12122121", "dateLimiteInstructionFrs": "","""
""" "codeSituation": "LIV", "dateDepart": "2019-07-09","""
""" "temperatureMax": null, "poids": 9.0, "reference2": ""},"""
""" {"statutServicesLivraison": "", "libelleLongEtat": "Livr\u00e9e","""
""" "dateEtat": "2019-07-10", "libelleLivraison": "Livraison le","""
""" "emissionEqc": null, "libelleStatutServicesLivraison": "","""
""" "adresse1Exp": "Akretion", "villeExp": "VILLEURBANNE","""
""" "temperatureMin": null, "codeOption": "RET","""
""" "libellePrestation": "Retour/Trans. Fr. - Express","""
""" "urlPreuveService": "", "adresse2Exp":"""
""" "GRAND", "dateEtatFrs": "10/07/2019", "avecMatiereDangereuse":"""
""" false, "dateDepartFrs": "09/07/2019","""
""" "urlImageEnlevementLivraison": "http://github.com/akretion/roulier","""
""" "emissionEqa": null, "dateLivraisonFrs": "10/07/2019", """
""" "noRecepisse": "1212121ae", "nbErreursNotification": 0,"""
""" "listServicesLivraison": [], "delaiInstruction": 0,"""
""" "adresse1Dest": "27 Rue Henri Rolland","""
""" "libellePaysExp": "France", "codePostalExp": "69100","""
""" "envoiRegroupement": false, "refDest": "","""
""" "temperatureMed": null, "dateLimiteInstruction": null, "nomDest":"""
""" "Akretion", "avecInstructionDonnee": false, "uniteInstruction":"""
""" "", "refUniEnl": 121212, "libelleEtat": "Livr\u00e9e", "nomExp":"""
""" "RETOUR POUR AVARIE", "urlSuiviDestinataire": "http://akretion.com","""
""" "adresse2Dest": "", "emissionPar": null, "codePaysDest": "FR","""
""" "nbExcursionsTemp": null, "noRecepRegroupement": "","""
""" "envoiRegroupe": false, "reference1": "Shopinvader","""
""" "dateLivraison": "2019-07-10", "loginDestinataire": "1279217912121","""
""" "nbPalettes": 0, "refUniExp": 12321212,"""
""" "avecAttenteInstruction": false, "codePaysExp": "FR", "codeClient":"""
""" "1212121", "listEnvoisRegroupes": [], "codeProduit": "ENE","""
""" "codeJustification": "NEM", "villeDest": "Villeurbanne","""
""" "nbColis": 2, "codePostalDest": "69100","""
""" "libellePaysDest": "France","""
""" "typePrestation": "EXP", "refUniRegroupement": 0, "codeSa": "12121","""
""" "noSuivi": "12212121", "dateLimiteInstructionFrs": "","""
""" "codeSituation": "LIV", "dateDepart": "2019-07-09","""
""" "temperatureMax": null, "poids":"""
""" 5.0, "reference2": ""}], "codeErreur": null,"""
""" "ok": true, "texteErreur": null}"""
)

ret_val = [{
'from_address': {
'city': u'STRASBOURG',
'country': u'FR',
'country_name': u'France',
'name': u'RETOUR',
'street1': u'axxxZ',
'street2': u'wcvwv',
'zip': u'69100'},
'parcels': {'weight': 9.0},
'service': {
'agencyId': u'122121',
'customerId': u'01234',
'option': u'RET',
'product': u'ENE',
'reference1': u'abcdeefefefe',
'reference2': u'',
'reference3': u'',
'shippingDate': u'2019-07-09',
'shippingId': u'12212122'},
'to_address': {
'city': u'LESQUIN',
'country': u'FR',
'country_name': u'France',
'name': u'TdsdsfsfsfsT',
'street1': u'27 rue Henri Rolland',
'street2': u'',
'zip': u'59810'},
'tracking': {
'estDeliveryDate': u'2019-07-10',
'proofUrl': u'http://github.com/akretion/roulier',
'publicUrl': u'https://akretion.com',
'status': 'DELIVERED',
'statusDate': u'2019-07-10',
'statusDetails': u'Livr\xe9e',
'trackingCode': u'12122121'}
}, {
'from_address': {
'city': u'VILLEURBANNE',
'country': u'FR',
'country_name': u'France',
'name': u'RETOUR POUR AVARIE',
'street1': u'Akretion',
'street2': u'GRAND',
'zip': u'69100'},
'parcels': {'weight': 5.0},
'service': {
'agencyId': u'12121',
'customerId': u'1212121',
'option': u'RET',
'product': u'ENE',
'reference1': u'Shopinvader',
'reference2': u'',
'reference3': u'',
'shippingDate': u'2019-07-09',
'shippingId': u'1212121ae'},
'to_address': {
'city': u'Villeurbanne',
'country': u'FR',
'country_name': u'France',
'name': u'Akretion',
'street1': u'27 Rue Henri Rolland',
'street2': u'',
'zip': u'69100'},
'tracking': {
'estDeliveryDate': u'2019-07-10',
'proofUrl': u'http://github.com/akretion/roulier',
'publicUrl': u'http://akretion.com',
'status': 'DELIVERED',
'statusDate': u'2019-07-10',
'statusDetails': u'Livr\xe9e',
'trackingCode': u'12212121'
}}
]


def test_decode():
payload = json.loads(data)
rep = {
"body": payload.get('contenu', []),
"parts": [],
"response": None,
}
decode = GeodisDecoderRestWs()
ret = decode.decode(rep, 'trackingList')

assert ret == ret_val
127 changes: 76 additions & 51 deletions roulier/carriers/laposte/laposte_api.py
Original file line number Diff line number Diff line change
@@ -23,34 +23,43 @@ def _service(self):
schema['labelFormat'].update({'required': True, 'empty': False})
schema['product'].update({'required': True, 'empty': False})
schema['pickupLocationId'] = {
'default': '', 'description':
"""Si productCode = A2P, BPR, ACP, CDI, CMT, BDP. "
"Identifiant du point de retrait "
"(dans le cas d’une livraison Colissimo hors domicile)"""}
'default': '',
# 'description': """Si productCode = A2P, BPR, ACP, CDI, CMT, BDP. "
# "Identifiant du point de retrait "
# "(dans le cas d’une livraison Colissimo hors domicile)"""
}
schema['totalAmount'] = {
'default': '', 'description': 'Needed for cn23'}
'default': '',
# 'description': 'Needed for cn23'
}
schema['orderNumber'] = {'default': ''}
schema['commercialName'] = {
'default': '', 'description':
"""Obligatoire pour les produits DOM, DOS, BPR, A2P. "
"Nom commercial du chargeur qui sera affiché dans "
"les notifications par email aux destinataires des colis"""}
'default': '',
# 'description':
# """Obligatoire pour les produits DOM, DOS, BPR, A2P. "
# "Nom commercial du chargeur qui sera affiché dans "
# "les notifications par email aux destinataires des colis"""
}
schema['returnTypeChoice'] = {
'default': '', 'description':
"""Obligatoire pour certains colis à l’international, "
"selon les zones tarifaires applicables. Indique si "
"le colis doit être retourné à l’expéditeur en cas de "
"non distribution du colis"""}
'default': '',
# 'description':
# """Obligatoire pour certains colis à l’international, "
# "selon les zones tarifaires applicables. Indique si "
# "le colis doit être retourné à l’expéditeur en cas de "
# "non distribution du colis"""
}
schema['returnType'] = {
'default': '', 'description':
"""Utilisé pour le Colissimo Retour uniquement. "
"Définit le mode de transmission de l’étiquette"""}
schema['reference1'].update({
'description': """Référence expediteur ('Réf client')"""
})
schema['reference2'].update({
'description': """Référence destinataire ('Réf destinataire')"""
})
'default': '',
# 'description':
# """Utilisé pour le Colissimo Retour uniquement. "
# "Définit le mode de transmission de l’étiquette"""
}
# schema['reference1'].update({
# 'description': """Référence expediteur ('Réf client')"""
# })
# schema['reference2'].update({
# 'description': """Référence destinataire ('Réf destinataire')"""
# })
return schema

def _address(self):
@@ -60,20 +69,28 @@ def _address(self):
schema['city'].update({'required': True, 'empty': False})
schema['street0'] = {
'required': False, 'empty': True,
'description': """Entrée, bâtiment, immeuble, résidence. """
"""Non utilisé pour la Belgique."""}
# 'description': """Entrée, bâtiment, immeuble, résidence. """
# """Non utilisé pour la Belgique."""
}
schema['street2'].update({
'default': '', 'description': 'Etage, couloir, escalier, appart.'})
'default': ''
# , 'description': 'Etage, couloir, escalier, appart.'
})
schema['street1'].update({
'required': True, 'empty': False,
'description': 'Numéro et libellé de voie. Ex : 5 rue du Bellay'})
# 'description': 'Numéro et libellé de voie. Ex : 5 rue du Bellay'
})
schema['street3'] = {
'default': '',
'description': """Lieu dit ou autre mention. """
"""Non utilisé pour la Belgique."""}
schema['door1'] = {'default': '', 'description': """Code porte 1"""}
schema['door2'] = {'default': '', 'description': """Code porte 2"""}
schema['intercom'] = {'default': '', 'description': """Interphone"""}
# 'description': """Lieu dit ou autre mention. """
# """Non utilisé pour la Belgique."""
}
# 'description': """Code porte 1"""
schema['door1'] = {'default': ''}
# 'description': """Code porte 2"""
schema['door2'] = {'default': ''}
# 'description': """Interphone"""
schema['intercom'] = {'default': ''}

return schema

@@ -85,41 +102,49 @@ def _to_address(self):
schema = super(LaposteApi, self)._to_address()
schema['firstName'] = {
'default': '',
'description': """Prénom. Obligatoire pour So Colissimo"""}
}
# 'description': """Prénom. Obligatoire pour So Colissimo"""
return schema

def _parcel(self):
schema = super(LaposteApi, self)._parcel()
schema['nonMachinable'] = {
'type': 'boolean', 'default': False,
'description': """Passer à true pour indiquer que le "
"format du colis est non standard"""}
# 'description': """Passer à true pour indiquer que le "
# "format du colis est non standard"""
}
schema['instructions'] = {
'default': '', 'description': """Indications complémentaires "
"pour la livraison"""}
'default': '',
# 'description': """Indications complémentaires pour la livraison"""
}
schema['insuranceValue'] = {
'type': 'number', 'default': 0,
'description': """Valeur assurée. Max= 1500€ "
"Passer 1230 pour 12,30€ Cette valeur sera arrondie "
"à l’entier le plus proche (Ex : 12 euros si 1232 est envoyé) "
"Par défaut, renseigner « 0 » (zéro)"""}
# 'description': """Valeur assurée. Max= 1500€ "
# "Passer 1230 pour 12,30€ Cette valeur sera arrondie "
# "à l’entier le plus proche (Ex : 12 euros si 1232 est envoyé) "
# "Par défaut, renseigner « 0 » (zéro)"""
}
schema['recommendationLevel'] = {
'default': '', 'description': """Niveau de recommandation "
"(cf. III.2) Peut valoir « R1 », ou « R2 », ou « R3 »"""}
'default': '',
# 'description': """Niveau de recommandation "
# "(cf. III.2) Peut valoir « R1 », ou « R2 », ou « R3 »"""
}
schema['cod'] = {
'type': 'boolean', 'default': False,
'description': """Passer à true si la livraison doit se "
"faire contre remboursement."""}
# 'description': """Passer à true si la livraison doit se faire contre remboursement."""
}
schema['codAmount'] = {
'type': 'number', 'default': 0,
'description': """Montant attendu lors de la "
"livraison contre remboursement. Par défaut, renseigner "
"« 0 » (zéro)"""}
# 'description': """Montant attendu lors de la "
# "livraison contre remboursement. Par défaut, renseigner "
# "« 0 » (zéro)"""
}
schema['ftd'] = {
'type': 'boolean', 'default': False,
'description': """Pour les envois vers "
"l’Outre-Mer uniquement Indique si le colis est franc de taxes "
"et de droits."""}
# 'description': """Pour les envois vers "
# "l’Outre-Mer uniquement Indique si le colis est franc de taxes "
# "et de droits."""
}
return schema

def _auth(self):
5 changes: 0 additions & 5 deletions roulier/carriers/trs/__init__.py

This file was deleted.

4 changes: 0 additions & 4 deletions roulier/carriers/trs/templates/trs_deposit_slip.csv

This file was deleted.

43 changes: 0 additions & 43 deletions roulier/carriers/trs/templates/trs_generateLabel.zpl

This file was deleted.

39 changes: 0 additions & 39 deletions roulier/carriers/trs/trs.py

This file was deleted.

45 changes: 0 additions & 45 deletions roulier/carriers/trs/trs_api.py

This file was deleted.

23 changes: 0 additions & 23 deletions roulier/carriers/trs/trs_decoder.py

This file was deleted.

34 changes: 0 additions & 34 deletions roulier/carriers/trs/trs_encoder.py

This file was deleted.

112 changes: 0 additions & 112 deletions roulier/carriers/trs/trs_transport.py

This file was deleted.

2 changes: 0 additions & 2 deletions roulier/roulier.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
from .carriers.dummy.dummy import Dummy
from .carriers.geodis.geodis import Geodis
from .carriers.dpd.dpd import Dpd
from .carriers.trs.trs import Trs


def _carriers():
@@ -17,7 +16,6 @@ def _carriers():
"dummy": Dummy,
"geodis": Geodis,
"dpd": Dpd,
"trs": Trs,
}


6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -5,18 +5,22 @@

version = open('VERSION').read().strip()
download_url = "https://github.com/akretion/roulier/archive/%s.zip" % version
with open("README.md", "r") as fh:
long_description = fh.read()

setup(
name="roulier",
version=version,
packages=find_packages(),
install_requires=[
'lxml', 'Jinja2', 'requests', 'cerberus', 'zplgrf',
'unicodecsv', 'unidecode'],
'unidecode'],
author="Hparfr <https://github.com/hparfr>",
author_email="roulier@hpar.fr",
description="Label parcels without pain",
include_package_data=True,
long_description=long_description,
long_description_content_type="text/markdown",
package_data={'roulier': ['*.xml', '*.xsl', '*.zpl']},
url="https://github.com/akretion/roulier",
download_url=download_url,