├── .gitignore ├── CHANGES ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── pyshipping ├── 3dbpp.c ├── __init__.py ├── addressvalidation.py ├── binpack.py ├── binpack_simple.py ├── carriers │ ├── __init__.py │ └── dpd │ │ ├── __init__.py │ │ ├── georoute.py │ │ ├── georoute_test.py │ │ └── georoutetables │ │ ├── COUNTRY │ │ ├── DEPOTS.gz │ │ ├── LOCATION.DE │ │ ├── LOCATION.EN │ │ ├── LOCATION.FR │ │ ├── ROUTES.gz │ │ ├── SERVICE │ │ ├── SERVICEINFO.CS │ │ ├── SERVICEINFO.CZ │ │ ├── SERVICEINFO.DE │ │ ├── SERVICEINFO.EE │ │ └── SERVICEINFO.EN ├── fortras │ ├── __init__.py │ ├── bordero.py │ ├── entl.py │ ├── fakt.py │ ├── fortras_stat.py │ └── test.py ├── package.py └── shipment.py ├── requirements.txt ├── setup.py └── testdata.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.so 4 | *~ 5 | build 6 | dist 7 | html 8 | pyShipping.egg-info 9 | *.bak 10 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 1.9: neue Routeninfos von DPD zum 2011-05-07 2 | 1.8: neue Routeninfos von DPD zum 2011-01-03 3 | 1.7: neue Routeninfos von DPD zum 6.9.2010 4 | 1.6: addressvalidation added 5 | 1.5: Keep Package dimensions sorted, buendelung(), __add__() and hat_gleiche_seiten() 6 | added binpacking (c) David Pisinger, Silvano Martello, Daniele Vigo 7 | and an independent pure Python implementation 8 | unified PackageSize and Package, made Package sortable 9 | 1.4: Gemeinsame Basisklasse fuer Exceptions in pyshipping.carriers.dpd.georoute 10 | 1.3p3: Added new Routing Table for DPD (Routendatenbank Januar - April 2010) 11 | 1.3p2: Added new Routing Table for DPD 12 | 1.3p1: Provide extra argument basedir to fortras.bordero.ship(). Bordero files will be saved in this directory. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Original code in this disturibution is 2 | Copyright 2008, 2009 HUDORA GmbH. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are 5 | permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of 8 | conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | of conditions and the following disclaimer in the documentation and/or other materials 12 | provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY HUDORA GmbH ``AS IS'' AND ANY EXPRESS OR IMPLIED 15 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 16 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR 17 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | This distribution contains software by third parties which might be licensed differently. 25 | See the respective sourcefiles for details. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include pyshipping/carriers/dpd/georoutetables/* -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # setting the PATH seems only to work in GNUmake not in BSDmake 2 | PATH := ./testenv/bin:$(PATH) 3 | 4 | check: 5 | pep8 -r --ignore=E501 pyshipping/ 6 | sh -c 'PYTHONPATH=. pyflakes pyshipping/' 7 | -sh -c 'PYTHONPATH=. pylint -iy --max-line-length=110 pyshipping/' # -rn 8 | 9 | build: 10 | python setup.py build 11 | 12 | test: 13 | PYTHONPATH=. python pyshipping/__init__.py # find import errors 14 | PYTHONPATH=. python pyshipping/shipment.py 15 | PYTHONPATH=. python pyshipping/package.py 16 | PYTHONPATH=. python pyshipping/fortras/test.py 17 | PYTHONPATH=. python pyshipping/binpack.py 18 | # These tests tend to fail because of routing table updates 19 | PYTHONPATH=. python pyshipping/carriers/dpd/georoute_test.py 20 | 21 | dependencies: 22 | virtualenv testenv 23 | pip -q install -E testenv -r requirements.txt 24 | 25 | statistics: 26 | sloccount --wide --details pyshipping | tee sloccount.sc 27 | 28 | upload: build doc 29 | python setup.py sdist upload 30 | 31 | doc: build 32 | rm -Rf html 33 | mkdir -p html 34 | mkdir -p html/fortras 35 | mkdir -p html/carriers 36 | sh -c '(cd html; pydoc -w ../pyshipping/*.py)' 37 | sh -c '(cd html/fortras; pydoc -w ../../pyshipping/*.py)' 38 | sh -c '(cd html/carriers; pydoc -w ../../pyshipping/*.py)' 39 | 40 | install: build 41 | sudo python setup.py install 42 | 43 | clean: 44 | rm -Rf testenv build dist html test.db pyShipping.egg-info pylint.out sloccount.sc pip-log.txt 45 | find . -name '*.pyc' -or -name '*.pyo' -delete 46 | 47 | .PHONY: test build clean check upload doc install 48 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pyShipping provides connections to interface with shipping companies and to transport shipping related information. 2 | 3 | * package - shipping/cargo related calculations based on a unit of shipping (box, crate, package), includes 4 | a bin packing implementation in pure Python 5 | * sendung - defines an abstract shippment (Sendung), with packages and calculations based on that 6 | * addressvalidation - check if an address is valid 7 | * carriers.dpd - calculation of DPD/Georoutes routing data and labels. Included tables are for shippments from Wuppertal but it should work with all other german routing tables. See this Blogpost_ about updating routing information. 8 | * fortras - tools for reading and writing Fortras messages. Fortras is a EDI standard for logistics related information somewhat common in Germany. See Wikipedia_ for further enlightenment 9 | 10 | .. _Wikipedia: http://de.wikipedia.org/wiki/Fortras 11 | .. _Blogpost: https://cybernetics.hudora.biz/intern/wordpress/2010/09/dpd-routeninformationen-aktualisieren/ 12 | 13 | It also comes with the only python based `3D Bin Packing `_ implementation I'm aware of. The Algorithm has sufficient performance to be used in everyday shipping and warehousing applications. 14 | 15 | You can get the whole Package at http://pypi.python.org/pypi/pyShipping 16 | 17 | This code is BSD Licensed. 18 | 19 | .. image:: https://d2weczhvl823v0.cloudfront.net/hudora/pyshipping/trend.png 20 | :alt: Bitdeli badge 21 | :target: https://bitdeli.com/free 22 | 23 | -------------------------------------------------------------------------------- /pyshipping/__init__.py: -------------------------------------------------------------------------------- 1 | """pyShipping contains routines related to shipping and warehousing.""" 2 | -------------------------------------------------------------------------------- /pyshipping/addressvalidation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | addressvalidation.py - check the validity of addresses 5 | 6 | Should once integrate with 7 | http://www.deutschepost.de/dpag?tab=1&skin=hi&check=yes&lang=de_DE&xmlFile=link1015574_1021 8 | http://www.isogmbh.de/leistungen/dataquality-management/adressvalidierung.html 9 | or http://opengeodb.hoppe-media.com/ 10 | 11 | 12 | Created by Maximillian Dornseif on 2009-09-03. 13 | Copyright (c) 2009, 2010 HUDORA. All rights reserved. 14 | """ 15 | 16 | import unittest 17 | 18 | 19 | def validate(adr, servicelevel=1): 20 | """Validates an address and returns a possibly corrected address. 21 | 22 | 'adr' should be a object conforming to the address protocol 23 | - see http://cybernetics.hudora.biz/projects/wiki/AddressProtocol 24 | 'servicelevel' can be an integer with the following values: 25 | 1 - generic validation, no money/effort should be spend on correction and suggestions 26 | 2 - TBD. 27 | 28 | returns (status, message, [corrected addresses and variants]) 29 | 30 | status can be: 31 | '10invalid' - address is for sure non deliverable in this form 32 | '20troubled' - bounced before or is unlikely to be correct - possible alternatives are returned 33 | '30ok' - likely to work 34 | '31ok' - likely to work but was corrected 35 | '40verified' - we are sure it works 36 | """ 37 | 38 | adr['land'] = adr['land'].strip() 39 | adr['plz'] = adr['plz'].strip() 40 | 41 | if adr['land'] != 'IE' and not adr['plz']: 42 | return ('10invalid', 'Postleitzahl fehlt', [adr]) 43 | 44 | if adr['land'] == 'DE' and len(adr.get('plz', '')) != 5: 45 | return ('10invalid', 'Postleitzahl fehlerhaft', [adr]) 46 | 47 | return ('30ok', '', [adr]) 48 | 49 | 50 | class AddressvalidationTests(unittest.TestCase): 51 | """Tests for the address validation facility.""" 52 | 53 | def setUp(self): 54 | """Set up test address base.""" 55 | self.address = {'name1': 'HUDORA GmbH', 56 | 'name2': 'Abt. Cybernetics', 57 | 'strasse': 'Jägerwald 13', 58 | 'land': 'DE', 59 | 'plz': '42897', 60 | 'ort': 'Remscheid', 61 | 'tel': '+49 2191 60912 0', 62 | 'fax': '+49 2191 60912 50', 63 | 'mobil': '+49 175 00000xx', 64 | 'email': 'nobody@hudora.de'} 65 | 66 | def test_good_address(self): 67 | """Test if correct addresses are considered correct.""" 68 | self.assertEqual(validate(self.address)[0], '30ok') 69 | self.assertEqual(validate(self.address)[1], '') 70 | 71 | def test_missing_zip(self): 72 | """Test if correct addresses are considered correct.""" 73 | self.address['plz'] = '' 74 | self.assertEqual(validate(self.address)[0], '10invalid') 75 | 76 | def test_short_zip(self): 77 | """Test if correct addresses are considered correct.""" 78 | self.address['plz'] = '123' 79 | self.assertEqual(validate(self.address)[0], '10invalid') 80 | 81 | def test_long_zip(self): 82 | """Test if correct addresses are considered correct.""" 83 | self.address['plz'] = '12345 Rade' 84 | self.assertEqual(validate(self.address)[0], '10invalid') 85 | 86 | 87 | if __name__ == '__main__': 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /pyshipping/binpack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | binpack.py 5 | 6 | Created by Maximillian Dornseif on 2010-08-16. 7 | Copyright (c) 2010 HUDORA. All rights reserved. 8 | """ 9 | 10 | 11 | import binpack_simple 12 | 13 | 14 | def binpack(packages, bin=None, iterlimit=5000): 15 | return binpack_simple.binpack(packages, bin, iterlimit) 16 | 17 | 18 | def test(func): 19 | import time 20 | from package import Package 21 | fd = open('testdata.txt') 22 | vorher = 0 23 | nachher = 0 24 | start = time.time() 25 | counter = 0 26 | for line in fd: 27 | counter += 1 28 | if counter > 450: 29 | break 30 | packages = [Package(pack) for pack in line.strip().split()] 31 | if not packages: 32 | continue 33 | bins, rest = func(packages) 34 | if rest: 35 | print "invalid data", rest, line 36 | else: 37 | vorher += len(packages) 38 | nachher += len(bins) 39 | print time.time() - start, 40 | print vorher, nachher, float(nachher) / vorher * 100 41 | 42 | 43 | if __name__ == '__main__': 44 | print "py", 45 | test(binpack) 46 | 47 | 48 | import time 49 | from pyshipping.package import Package 50 | -------------------------------------------------------------------------------- /pyshipping/binpack_simple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | binpack_simple.py 5 | 6 | This code implemnts 3D bin packing in pure Python 7 | 8 | Bin packing in this context is calculating the best way to store a number of differently sized boxes in a 9 | number of fixed sized "bins". It is what usually happens in a Warehouse bevore shipping. 10 | 11 | The Algorithm has a simple fit first approach, but can archive relative good results because it tries 12 | different rectangular rotations of the packages. Since the Algorithm can't interate over all possible 13 | combinations we use a heuristic approach. 14 | 15 | For a few dozen packages it reaches adaequate runtime. Below are the results calculated about a set of 16 | 500 real world packing problems. 17 | 18 | Binsize Runtime Recuction in shipped Packages 19 | 600x400x400 31.5993559361 4970 2033 40.9054325956 20 | 600x445x400 31.5596890450 4970 1854 37.3038229376 21 | 600x500x400 29.1432909966 4970 1685 33.9034205231 22 | 23 | 24 | On the datasets we operate on we can archive comparable preformance to academic higly optimized C code 25 | like David Pisinger's 3bpp: 26 | 27 | Runtime Recuction in shipped Packages 28 | py 11.3468761444 2721 1066 39.1767732451 29 | 3bpp 9.95857691765 2721 1086 39.9117971334 30 | 31 | The Python implementation is somewhat slower but can archive slightly better packing results on our 32 | datasets. 33 | 34 | 35 | Created by Maximillian Dornseif on 2010-08-14. 36 | Copyright (c) 2010 HUDORA. All rights reserved. 37 | """ 38 | 39 | 40 | import time 41 | import random 42 | 43 | 44 | def packstrip(bin, p): 45 | """Creates a Strip which fits into bin. 46 | 47 | Returns the Packages to be used in the strip, the dimensions of the strip as a 3-tuple 48 | and a list of "left over" packages. 49 | """ 50 | # This code is somewhat optimized and somewhat unreadable 51 | s = [] # strip 52 | r = [] # rest 53 | ss = sw = sl = 0 # stripsize 54 | bs = bin.heigth # binsize 55 | sapp = s.append # speedup 56 | rapp = r.append # speedup 57 | ppop = p.pop # speedup 58 | while p and (ss <= bs): 59 | n = ppop(0) 60 | nh, nw, nl = n.size 61 | if ss + nh <= bs: 62 | ss += nh 63 | sapp(n) 64 | if nw > sw: 65 | sw = nw 66 | if nl > sl: 67 | sl = nl 68 | else: 69 | rapp(n) 70 | return s, (ss, sw, sl), r + p 71 | 72 | 73 | def packlayer(bin, packages): 74 | strips = [] 75 | layersize = 0 76 | layerx = 0 77 | layery = 0 78 | binsize = bin.width 79 | while packages: 80 | strip, (sizex, stripsize, sizez), rest = packstrip(bin, packages) 81 | if layersize + stripsize <= binsize: 82 | packages = rest 83 | if not strip: 84 | # we were not able to pack anything 85 | break 86 | layersize += stripsize 87 | layerx = max([sizex, layerx]) 88 | layery = max([sizez, layery]) 89 | strips.extend(strip) 90 | else: 91 | # Next Layer please 92 | packages = strip + rest 93 | break 94 | return strips, (layerx, layersize, layery), packages 95 | 96 | 97 | def packbin(bin, packages): 98 | packages.sort() 99 | layers = [] 100 | contentheigth = 0 101 | contentx = 0 102 | contenty = 0 103 | binsize = bin.length 104 | while packages: 105 | layer, (sizex, sizey, layersize), rest = packlayer(bin, packages) 106 | if contentheigth + layersize <= binsize: 107 | packages = rest 108 | if not layer: 109 | # we were not able to pack anything 110 | break 111 | contentheigth += layersize 112 | contentx = max([contentx, sizex]) 113 | contenty = max([contenty, sizey]) 114 | layers.extend(layer) 115 | else: 116 | # Next Bin please 117 | packages = layer + rest 118 | break 119 | return layers, (contentx, contenty, contentheigth), packages 120 | 121 | 122 | def packit(bin, originalpackages): 123 | packedbins = [] 124 | packages = sorted(originalpackages) 125 | while packages: 126 | packagesinbin, (binx, biny, binz), rest = packbin(bin, packages) 127 | if not packagesinbin: 128 | # we were not able to pack anything 129 | break 130 | packedbins.append(packagesinbin) 131 | packages = rest 132 | # we now have a result, try to get a better result by rotating some bins 133 | 134 | return packedbins, rest 135 | 136 | 137 | # In newer Python versions these van be imported: 138 | # from itertools import permutations 139 | def product(*args, **kwds): 140 | # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy 141 | # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 142 | pools = map(tuple, args) * kwds.get('repeat', 1) 143 | result = [[]] 144 | for pool in pools: 145 | result = [x + [y] for x in result for y in pool] 146 | for prod in result: 147 | yield tuple(prod) 148 | 149 | 150 | def permutations(iterable, r=None): 151 | pool = tuple(iterable) 152 | n = len(pool) 153 | r = n if r is None else r 154 | for indices in product(range(n), repeat=r): 155 | if len(set(indices)) == r: 156 | yield tuple(pool[i] for i in indices) 157 | 158 | 159 | class Timeout(Exception): 160 | pass 161 | 162 | 163 | def allpermutations_helper(permuted, todo, maxcounter, callback, bin, bestpack, counter): 164 | if not todo: 165 | return counter + callback(bin, permuted, bestpack) 166 | else: 167 | others = todo[1:] 168 | thispackage = todo[0] 169 | for dimensions in set(permutations((thispackage[0], thispackage[1], thispackage[2]))): 170 | thispackage = Package(dimensions, nosort=True) 171 | if thispackage in bin: 172 | counter = allpermutations_helper(permuted + [thispackage], others, maxcounter, callback, 173 | bin, bestpack, counter) 174 | if counter > maxcounter: 175 | raise Timeout('more than %d iterations tries' % counter) 176 | return counter 177 | 178 | 179 | def trypack(bin, packages, bestpack): 180 | bins, rest = packit(bin, packages) 181 | if len(bins) < bestpack['bincount']: 182 | bestpack['bincount'] = len(bins) 183 | bestpack['bins'] = bins 184 | bestpack['rest'] = rest 185 | if bestpack['bincount'] < 2: 186 | raise Timeout('optimal solution found') 187 | return len(packages) 188 | 189 | 190 | def allpermutations(todo, bin, iterlimit=5000): 191 | random.seed(1) 192 | random.shuffle(todo) 193 | bestpack = dict(bincount=len(todo) + 1) 194 | try: 195 | # First try unpermuted 196 | trypack(bin, todo, bestpack) 197 | # now try permutations 198 | allpermutations_helper([], todo, iterlimit, trypack, bin, bestpack, 0) 199 | except Timeout: 200 | pass 201 | return bestpack['bins'], bestpack['rest'] 202 | 203 | 204 | def binpack(packages, bin=None, iterlimit=5000): 205 | """Packs a list of Package() objects into a number of equal-sized bins. 206 | 207 | Returns a list of bins listing the packages within the bins and a list of packages which can't be 208 | packed because they are to big.""" 209 | if not bin: 210 | bin = Package("600x400x400") 211 | return allpermutations(packages, bin, iterlimit) 212 | 213 | 214 | def test(): 215 | fd = open('testdata.txt') 216 | vorher = 0 217 | nachher = 0 218 | start = time.time() 219 | for line in fd: 220 | packages = [Package(pack) for pack in line.strip().split()] 221 | if not packages: 222 | continue 223 | bins, rest = binpack(packages) 224 | if rest: 225 | print "invalid data", rest, line 226 | else: 227 | vorher += len(packages) 228 | nachher += len(bins) 229 | print time.time() - start, 230 | print vorher, nachher, float(nachher) / vorher * 100 231 | 232 | 233 | if __name__ == '__main__': 234 | import cProfile 235 | cProfile.run('test()') 236 | 237 | 238 | from pyshipping.package import Package 239 | -------------------------------------------------------------------------------- /pyshipping/carriers/__init__.py: -------------------------------------------------------------------------------- 1 | """Function for specific freight carriers.""" 2 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/__init__.py: -------------------------------------------------------------------------------- 1 | """Functions specific to DPD/Geopost.""" 2 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | georoute.py - get DPD related routng information 5 | 6 | Originally coded by md, cleand up and extended by jmv and then again reworked by md. 7 | Copyright 2006, 2007 HUDORA GmbH. Published under a BSD License. 8 | """ 9 | 10 | import os 11 | import os.path 12 | import gzip 13 | import logging 14 | import sqlite3 15 | 16 | 17 | ROUTETABLES_BASE = os.path.join(os.path.split(os.path.abspath(__file__))[0], 'georoutetables') 18 | ROUTES_DB_BASE = '/tmp/dpdroutes' 19 | 20 | 21 | # Quelle: http://de.wikipedia.org/wiki/Liste_der_Kfz-Nationalitätszeichen 22 | ISO2CAR = { 23 | 'AT': 'A', 24 | 'BE': 'B', 25 | 'FR': 'F', 26 | } 27 | 28 | 29 | class InvalidFormatError(Exception): 30 | """Invalid input file format.""" 31 | pass 32 | 33 | 34 | class GeorouteException(Exception): 35 | """Base class for all routing exceptions""" 36 | pass 37 | 38 | 39 | class CountryError(GeorouteException): 40 | """Unknown country.""" 41 | pass 42 | 43 | 44 | class DepotError(GeorouteException): 45 | """Unknown depot.""" 46 | pass 47 | 48 | 49 | class ServiceError(GeorouteException): 50 | """Unknown service.""" 51 | pass 52 | 53 | 54 | class TranslationError(GeorouteException): 55 | """Cannot translate city and country to postcode.""" 56 | pass 57 | 58 | 59 | class RoutingDepotError(GeorouteException): 60 | """Unknown routing depot.""" 61 | pass 62 | 63 | 64 | class NoRouteError(GeorouteException): 65 | """Route not found.""" 66 | pass 67 | 68 | 69 | class Parcel(object): 70 | """Parcel destination data.""" 71 | 72 | # depreciated 73 | def __init__(self, depot='142', service='101', country='DE', city=None, postcode=None): 74 | import warnings 75 | warnings.warn("Parcel() is deprecated", DeprecationWarning, stacklevel=2) 76 | 77 | self.service = service 78 | self.country = country 79 | self.city = city 80 | self.postcode = postcode 81 | 82 | 83 | class Destination(object): 84 | """Parcel destination data.""" 85 | 86 | def __init__(self, country='DE', postcode=None, city=None, service='101'): 87 | self.service = service 88 | self.country = country 89 | self.city = city 90 | self.postcode = postcode 91 | 92 | 93 | class Route: 94 | """Output of the routing algorithm.""" 95 | 96 | def __init__(self, d_depot, o_sort, d_sort, grouping_priority, barcode_id, 97 | iata_code, service_text, service_mark, country, serviceinfo, countrynum, 98 | routingtable_version, postcode): 99 | self.d_depot = d_depot 100 | self.o_sort = o_sort 101 | self.d_sort = d_sort 102 | self.grouping_priority = grouping_priority 103 | self.barcode_id = barcode_id 104 | self.iata_code = iata_code 105 | self.service_text = service_text 106 | self.service_mark = service_mark 107 | self.country = country 108 | self.serviceinfo = serviceinfo 109 | self.countrynum = countrynum 110 | self.routingtable_version = routingtable_version 111 | self.postcode = postcode 112 | 113 | def __unicode__(self): 114 | output = u"""Output parameters: 115 | Country: %s 116 | D-Depot: %s 117 | O-Sort: %s 118 | D-Sort: %s 119 | Grouping priority: %s 120 | Barcode ID: %s 121 | ITA Code: %s 122 | Service Text: %s""" % (self.country, self.d_depot, self.o_sort, self.d_sort, 123 | self.grouping_priority, self.barcode_id, self.iata_code, 124 | self.service_text) 125 | if self.service_mark: 126 | output += "\nService Mark: %s" % self.service_mark 127 | if self.iata_code: 128 | output += "\nIATA Code: %s" % self.iata_code 129 | if self.serviceinfo: 130 | output += "\nService Info: %s" % self.serviceinfo 131 | 132 | return output 133 | 134 | def __repr__(self): 135 | return repr(vars(self)) 136 | 137 | def routingdata(self): 138 | return {'o_sort': self.o_sort, 'd_sort': self.d_sort, 139 | 'd_depot': self.d_depot, 'country': self.country, 140 | 'service_text': self.service_text, 'serviceinfo': self.serviceinfo} 141 | 142 | 143 | def _readfile(filename): 144 | """Read file line-by-line skipping comments.""" 145 | if os.path.exists(filename + '.gz'): 146 | fhandle = gzip.GzipFile(filename + '.gz') 147 | else: 148 | fhandle = file(filename) 149 | for line in fhandle: 150 | line = line.strip().decode('latin1') 151 | if line.startswith('#'): 152 | continue 153 | yield line.split('|') 154 | 155 | 156 | class RouteData(object): 157 | """More convenient representation of the georoute data.""" 158 | 159 | def __init__(self, routingdepot='0142'): 160 | """Routingdepot the depot from where you are sending.""" 161 | self.routingdepot = routingdepot 162 | self.routingdepotgroups = '' 163 | self.routingdepotcountry = '' 164 | 165 | services = os.path.join(ROUTETABLES_BASE, 'SERVICE') 166 | self.version = None 167 | for line in file(services): 168 | if line.startswith('#Version: '): 169 | self.version = line.split(':')[1].strip() 170 | break 171 | if self.version is None: 172 | raise InvalidFormatError("There's no version in the SERVICE file") 173 | 174 | self.countries = {} 175 | for line in _readfile(os.path.join(ROUTETABLES_BASE, 'COUNTRY')): 176 | isonum, isoname = line[:2] 177 | self.countries[isoname.upper()] = isonum 178 | 179 | self.depots = {} 180 | for line in _readfile(os.path.join(ROUTETABLES_BASE, 'DEPOTS')): 181 | geopostdepotnumber = line[0] 182 | self.depots[geopostdepotnumber] = tuple(line) 183 | 184 | self.services = {} 185 | for line in _readfile(os.path.join(ROUTETABLES_BASE, 'SERVICE')): 186 | servicecode = line[0] 187 | self.services[servicecode] = tuple(line) 188 | 189 | self.serviceinfo = {} 190 | for line in _readfile(os.path.join(ROUTETABLES_BASE, 'SERVICEINFO.DE')): 191 | servicecode = line[0] 192 | self.serviceinfo[servicecode] = line[1] 193 | 194 | filename = ROUTES_DB_BASE + ('-%s-%s.db' % (routingdepot, self.version)) 195 | self.db = sqlite3.connect(filename) 196 | 197 | self.read_depots(ROUTETABLES_BASE) 198 | self.read_locations(ROUTETABLES_BASE) 199 | self.read_routes(ROUTETABLES_BASE) 200 | 201 | def read_depots(self, path): 202 | """Read DEPOTS file and save all the information in a 203 | SQLite database.""" 204 | c = self.db.cursor() 205 | 206 | c.execute("""SELECT COUNT(*) 207 | FROM sqlite_master 208 | WHERE type = 'table' AND name = 'depots'""") 209 | if not c.fetchone()[0]: 210 | logging.info("regenerating depots table") 211 | c.execute("""CREATE TABLE depots 212 | (DepotNumber TEXT PRIMARY KEY, 213 | IATACode TEXT, 214 | GroupId TEXT, 215 | Name1 TEXT, 216 | Name2 TEXT, 217 | Address1 TEXT, 218 | Address2 TEXT, 219 | Postcode TEXT, 220 | CityName TEXT, 221 | Country TEXT, 222 | Phone TEXT, 223 | Fax TEXT, 224 | Mail TEXT, 225 | Web TEXT)""") 226 | 227 | for line in _readfile(os.path.join(path, 'DEPOTS')): 228 | c.execute("""INSERT INTO depots 229 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", 230 | line[:14]) 231 | if line[0] == self.routingdepot: 232 | self.routingdepotgroups = line[2] 233 | self.routingdepotgrouplist = line[2].split(',') 234 | self.routingdepotcountry = line[9] 235 | c.execute('VACUUM;') 236 | 237 | def read_locations(self, path): 238 | """Read LOCATION file and save all the information in a SQLite database.""" 239 | c = self.db.cursor() 240 | 241 | c.execute("""SELECT COUNT(*) 242 | FROM sqlite_master 243 | WHERE type='table' AND name='location'""") 244 | if not c.fetchone()[0]: 245 | logging.info("regenerating location table") 246 | c.execute("""CREATE TABLE location 247 | (Area TEXT, 248 | City TEXT, 249 | Country TEXT, 250 | Postcode TEXT)""") 251 | 252 | for line in _readfile(os.path.join(path, 'LOCATION.DE')): 253 | c.execute('INSERT INTO location VALUES (?,?,?,?)', 254 | line[:4]) 255 | c.execute('VACUUM;') 256 | 257 | def read_routes(self, path): 258 | """Read ROUTES file and save all the information in a SQLite database.""" 259 | # self.db = sqlite3.connect(ROUTES_DB) 260 | c = self.db.cursor() 261 | 262 | c.execute("""SELECT COUNT(*) 263 | FROM sqlite_master 264 | WHERE type='table' AND name='routes'""") 265 | if not c.fetchone()[0]: 266 | logging.info("regenerating routes table") 267 | c.execute("""CREATE TABLE routes 268 | (id INTEGER PRIMARY KEY, 269 | DestinationCountry TEXT, 270 | BeginPostCode TEXT, 271 | EndPostCode TEXT, 272 | ServiceCodes TEXT, 273 | RoutingPlaces TEXT, 274 | SendingDate TEXT, 275 | OSort TEXT, 276 | DDepot TEXT, 277 | GroupingPriority TEXT, 278 | DSort TEXT, 279 | BarcodeID TEXT)""") 280 | 281 | c.execute("""SELECT COUNT(*) 282 | FROM sqlite_master 283 | WHERE type='table' AND name='routedepots'""") 284 | if not c.fetchone()[0]: 285 | logging.info("regenerating routedepots table") 286 | c.execute("""CREATE TABLE routedepots 287 | (route INTEGER, 288 | depot TEXT)""") 289 | 290 | c.execute("PRAGMA synchronous=OFF;") 291 | c.execute("PRAGMA temp_store=MEMORY;") 292 | i = 1 293 | for line in _readfile(os.path.join(path, 'ROUTES')): 294 | services = self.expand_services(line[3]) 295 | c.execute('INSERT INTO routes VALUES (?,?,?,?,?,?,?,?,?,?,?,?)', 296 | [i] + line[:3] + [services] + line[4:-1]) 297 | self.expand_depots(i, line[4], c) 298 | i += 1 299 | 300 | c.execute("CREATE INDEX routes_DestinationCountry ON routes(DestinationCountry)") 301 | c.execute("CREATE INDEX routes_BeginPostCode ON routes(BeginPostCode)") 302 | c.execute("CREATE INDEX routes_EndPostCode ON routes(EndPostCode)") 303 | c.execute("CREATE INDEX routedepots_route ON routedepots(route)") 304 | c.execute("CREATE INDEX routedepots_depot ON routedepots(depot)") 305 | c.execute('VACUUM;') # also commits the database 306 | 307 | def expand_services(self, services): 308 | """Expand services list.""" 309 | services_list = [] 310 | for service in services.split(','): 311 | if len(service) > 4: 312 | start = int(service[1:4]) 313 | end = int(service[4:]) 314 | for i in range(start, end + 1): 315 | services_list.append(unicode(i)) 316 | else: 317 | services_list.append(service[1:]) 318 | 319 | return ','.join(services_list) 320 | 321 | def expand_depots(self, route, depots, c): 322 | """Parse depots list and generate route->depots relationship.""" 323 | # but only four "our" depot. 324 | # if you change the self.routingdepot, you have to rebuild the database 325 | if depots == '': 326 | c.execute("""INSERT INTO routedepots(route, depot) VALUES (?, ?)""", (route, depots)) 327 | return 328 | 329 | for depot in depots.split(','): 330 | if depot.startswith('C'): 331 | if depot[1:] == self.routingdepotcountry: 332 | c.execute("""INSERT INTO routedepots(route, depot) VALUES(?, ?)""", (route, 333 | self.routingdepot)) 334 | elif depot.startswith('D'): 335 | if len(depot) > 5: 336 | start = int(depot[1:5]) 337 | end = int(depot[5:]) 338 | for i in range(start, end + 1): 339 | if ("%04d" % i) == self.routingdepot: 340 | c.execute("""INSERT INTO routedepots(route, depot) VALUES(?, ?)""", 341 | (route, self.routingdepot)) 342 | else: 343 | if depot[1:5] == self.routingdepot: 344 | c.execute("""INSERT INTO routedepots(route, depot) VALUES(?, ?)""", 345 | (route, depot[1:5])) 346 | elif depot.startswith('G'): 347 | if depot[1:] in self.routingdepotgroups: 348 | c.execute("INSERT INTO routedepots(route, depot) VALUES(?, ?)", 349 | (route, self.routingdepot)) 350 | else: 351 | raise InvalidFormatError("Unable to parse depot '%s'" % depot) 352 | 353 | def get_countrynum(self, isoname): 354 | """Return country ISO code.""" 355 | if not isoname.upper() in self.countries: 356 | raise CountryError("Country %s unknown" % isoname) 357 | return self.countries[isoname.upper()] 358 | 359 | def get_depot(self, depotnumber): 360 | """Return depot.""" 361 | if not depotnumber in self.depots: 362 | raise DepotError("Depot %s unknown" % depotnumber) 363 | return self.depots[depotnumber] 364 | 365 | def get_service(self, servicecode): 366 | """Return service.""" 367 | if not servicecode in self.services: 368 | raise ServiceError("Service %s unknown" % servicecode) 369 | return self.services[servicecode] 370 | 371 | def get_servicetext(self, servicecode): 372 | """Return service info to be printed on label.""" 373 | if not servicecode in self.serviceinfo: 374 | return '' 375 | return self.serviceinfo[servicecode] 376 | 377 | def translate_location(self, city, country): 378 | """Return postcode for given city and country.""" 379 | cur = self.db.cursor() 380 | cur.execute("SELECT Postcode FROM location WHERE City=? AND Country=?", (city, country)) 381 | rows = cur.fetchall() 382 | if not rows: 383 | raise TranslationError("Cannot find postcode for location %s, %s" % (city, country)) 384 | return rows[0][0] 385 | 386 | 387 | class Router(object): 388 | """Routes parcels.""" 389 | 390 | def __init__(self, data): 391 | self.route_data = data 392 | self.db = self.route_data.db 393 | 394 | def route(self, parcel): 395 | """Find route.""" 396 | 397 | self.current_subset = [] 398 | self.conditions = ['1=1'] 399 | self.cleanup_postcode(parcel) 400 | 401 | self.select_country(parcel) 402 | if parcel.postcode is None: 403 | parcel.postcode = self.route_data.translate_location(parcel.city, parcel.country) 404 | self.subsetstack.append(self.subset) 405 | 406 | self.select_postcode(parcel) 407 | self.select_service(parcel) 408 | self.select_depot(parcel) 409 | # Sending date is not used yet, according to documentation 410 | 411 | # If there are several routes, always use the first one. 412 | # In prior versions, an exception was raised instead. 413 | if len(self.current_subset) >= 1: 414 | rows = self.select_routes("1=1") 415 | (service_text, service_mark) = self.route_data.get_service(parcel.service)[1:3] 416 | depot = self.route_data.get_depot(rows[0][8]) 417 | iata_code = depot[1] 418 | country = depot[9] 419 | if not country: 420 | country = parcel.country 421 | serviceinfo = self.route_data.get_servicetext(parcel.service) 422 | 423 | return Route(rows[0][8], rows[0][7], rows[0][10], rows[0][9], rows[0][11], 424 | iata_code, service_text, service_mark, country, 425 | serviceinfo, self.route_data.get_countrynum(country), 426 | self.route_data.version, parcel.postcode) 427 | raise NoRouteError("No route found for %r|%r|%r" % \ 428 | (parcel.country, parcel.postcode, parcel.service)) 429 | 430 | def add_condition(self, condition): 431 | self.conditions.append(condition) 432 | 433 | def select_routes(self, condition, params=()): 434 | """Find routes matching condition and currently selected subset of the routes table. 435 | If routes are found, save their ids for narrowing future searches. 436 | If no routes are found, do not change current subset. 437 | """ 438 | 439 | subsetcondition = ' AND '.join(self.conditions) 440 | cur = self.db.cursor() 441 | cur.execute("SELECT * FROM routes WHERE %s AND %s" % (subsetcondition, condition), params) 442 | rows = cur.fetchall() 443 | 444 | # Save matched rows if there were any results 445 | if rows: 446 | self.add_condition(condition) 447 | self.current_subset = [unicode(row[0]) for row in rows] 448 | return rows 449 | 450 | def select_country(self, parcel): 451 | """Select all routes with the given country.""" 452 | rows = self.select_routes("DestinationCountry='%s'" % (parcel.country.upper().replace("'", ''), )) 453 | if not rows: 454 | raise CountryError("Country %s unknown" % parcel.country) 455 | 456 | def cleanup_postcode(self, parcel): 457 | """Removes spaces and country prefixes from postcodes.""" 458 | 459 | if not parcel.postcode: 460 | return 461 | parcel.postcode = parcel.postcode.replace(' ', '').strip() 462 | if parcel.postcode.startswith('-'): 463 | parcel.postcode = parcel.postcode[1:] 464 | self.cleanup_postcode(parcel) 465 | if parcel.postcode.upper().startswith(parcel.country.upper()): 466 | parcel.postcode = parcel.postcode[len(parcel.country):] 467 | self.cleanup_postcode(parcel) 468 | if parcel.country.upper() in ISO2CAR: 469 | if parcel.postcode.upper().startswith(ISO2CAR[parcel.country.upper()]): 470 | parcel.postcode = parcel.postcode[len(ISO2CAR[parcel.country.upper()]):] 471 | self.cleanup_postcode(parcel) 472 | if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('CH-'): 473 | parcel.country = 'CH' 474 | parcel.postcode = parcel.postcode[2:] 475 | self.cleanup_postcode(parcel) 476 | if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('BE-'): 477 | parcel.country = 'BE' 478 | parcel.postcode = parcel.postcode[2:] 479 | self.cleanup_postcode(parcel) 480 | if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('B-'): 481 | parcel.country = 'BE' 482 | parcel.postcode = parcel.postcode[1:] 483 | self.cleanup_postcode(parcel) 484 | if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('AT-'): 485 | parcel.country = 'AT' 486 | parcel.postcode = parcel.postcode[2:] 487 | self.cleanup_postcode(parcel) 488 | if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('A-'): 489 | parcel.country = 'AT' 490 | parcel.postcode = parcel.postcode[1:] 491 | self.cleanup_postcode(parcel) 492 | 493 | def select_postcode(self, parcel): 494 | """Select all routes matching the given postcode.""" 495 | 496 | # direct match 497 | rows = self.select_routes("BeginPostCode='%s'" % parcel.postcode.replace("'", '')) 498 | if not rows: 499 | # range 500 | rows = self.select_routes("BeginPostCode<='%s' AND EndPostCode>='%s'" 501 | % (parcel.postcode.replace("'", ''), parcel.postcode.replace("'", ''))) 502 | if not rows: 503 | # catch all 504 | rows = self.select_routes("BeginPostCode=''") 505 | if not rows: 506 | raise NoRouteError("Postcode %r|%r unknown" % (parcel.country, parcel.postcode)) 507 | 508 | def select_service(self, parcel): 509 | """Select all routes with the given service code.""" 510 | 511 | # we have to redo postcode query as a backoff strategy 512 | self.conditions.pop() 513 | postcodequeries = ["BeginPostCode='%s'" % parcel.postcode.replace("'", ''), 514 | "BeginPostCode<='%s' AND EndPostCode>='%s'" % (parcel.postcode.replace("'", ''), 515 | parcel.postcode.replace("'", '')), 516 | "BeginPostCode=''"] 517 | for postcodequery in postcodequeries: 518 | rows = self.select_routes("%s AND ServiceCodes LIKE '%%%s%%'" % (postcodequery, parcel.service)) 519 | if not rows: 520 | # catch all 521 | rows = self.select_routes("%s AND ServiceCodes = ''" % (postcodequery)) 522 | if rows: 523 | break 524 | if not rows: 525 | raise ServiceError("No route for service found %r|%r|%r unknown" % \ 526 | (parcel.country, parcel.postcode, parcel.service)) 527 | 528 | def select_depot(self, parcel): 529 | """Select all routes with the given depot.""" 530 | subset = "route IN (%s)" % ','.join([unicode(route) for route in self.current_subset]) 531 | cur = self.db.cursor() 532 | cur.execute("SELECT route FROM routedepots WHERE depot=%s AND %s" % (self.route_data.routingdepot, 533 | subset)) 534 | rows = cur.fetchall() 535 | if not rows: 536 | cur.execute("SELECT route FROM routedepots WHERE %s" % (subset)) 537 | rows = cur.fetchall() 538 | if not rows: 539 | raise RoutingDepotError("No route found for %r|%r|%r|%r|%r" % \ 540 | (parcel.country, parcel.postcode, parcel.service, self.route_data.routingdepot, subset)) 541 | self.current_subset = [unicode(row[0]) for row in rows] 542 | 543 | 544 | def get_route_without_cache(country=None, postcode=None, city=None, servicecode='101'): 545 | router = Router(RouteData()) 546 | return router.route(Destination(country, postcode, city)) 547 | 548 | 549 | def get_route(country=None, postcode=None, city=None, servicecode='101'): 550 | # this includes somewhat overly complex caching 551 | filename = ROUTES_DB_BASE + ('_cache.db') 552 | cache_db = sqlite3.connect(filename, isolation_level=None) 553 | cur = cache_db.cursor() 554 | # ensure table exists 555 | cur.execute("""SELECT COUNT(*) 556 | FROM sqlite_master 557 | WHERE type = 'table' AND name = 'routing_cache'""") 558 | if not cur.fetchone()[0]: 559 | logging.info("regenerating cache table") 560 | cur.execute("""CREATE TABLE routing_cache 561 | (country_postcode_servicecode TEXT PRIMARY KEY, 562 | d_depot TEXT, 563 | o_sort TEXT, 564 | d_sort TEXT, 565 | grouping_priority TEXT, 566 | barcode_id TEXT, 567 | iata_code TEXT, 568 | service_text TEXT, 569 | service_mark TEXT, 570 | country TEXT, 571 | serviceinfo TEXT, 572 | countrynum TEXT, 573 | routingtable_version TEXT, 574 | postcode TEXT 575 | )""") 576 | 577 | # check if entry is cached 578 | cur.execute("SELECT * FROM routing_cache WHERE country_postcode_servicecode='%s'" 579 | % ("%s_%s_%s" % (country, postcode, servicecode))) 580 | rows = cur.fetchall() 581 | if not rows: 582 | # nothing found 583 | route = get_route_without_cache(country, postcode, city, servicecode) 584 | cur.execute("""INSERT INTO routing_cache 585 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", 586 | ("%s_%s_%s" % (country, postcode, servicecode), 587 | route.d_depot, 588 | route.o_sort, 589 | route.d_sort, 590 | route.grouping_priority, 591 | route.barcode_id, 592 | route.iata_code, 593 | route.service_text, 594 | route.service_mark, 595 | route.country, 596 | route.serviceinfo, 597 | route.countrynum, 598 | route.routingtable_version, 599 | route.postcode)) 600 | # For some reason closing the cache generated occasionally runtime errors. 601 | # cache_db.close() 602 | else: 603 | if len(rows) > 1: 604 | raise RuntimeError("to many cache hits!") 605 | route = Route(*rows[0][1:]) 606 | return route 607 | 608 | 609 | # compability layer to old georoute code prior to huLOG revision 1710 610 | 611 | def find_route(depot, servicecode, land, plz): 612 | """Legacy method - to be removed.""" 613 | 614 | import warnings 615 | warnings.warn("georoute.find_route() is deprecated use get_route() instead", 616 | DeprecationWarning, stacklevel=2) 617 | 618 | if unicode(depot) != '0142': 619 | raise RuntimeError("wrong depot") 620 | return get_route(unicode(land), unicode(plz), servicecode=unicode(servicecode)) 621 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoute_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | 4 | """Test routing resolver for DPD. Coded by jmv, extended by md""" 5 | 6 | import time 7 | import unittest 8 | from pyshipping.carriers.dpd.georoute import get_route, get_route_without_cache 9 | from pyshipping.carriers.dpd.georoute import RouteData, Router, Destination 10 | from pyshipping.carriers.dpd.georoute import ServiceError, CountryError, TranslationError 11 | 12 | 13 | class TestCase(unittest.TestCase): 14 | """Provide sophisticated dictionary comparision.""" 15 | 16 | def assertDicEq(self, dict1, dict2): 17 | """Asserts if two dicts are unequal. 18 | 19 | Raise an Exception which mentions the different entries of those dicts. 20 | """ 21 | if dict1 != dict2: 22 | difference = set() 23 | for key, value in dict1.items(): 24 | if dict2.get(key) != value: 25 | difference.add((key, value, dict2.get(key))) 26 | for key, value in dict2.items(): 27 | if dict1.get(key) != value: 28 | difference.add((key, dict1.get(key), value)) 29 | raise self.failureException, \ 30 | ('%r != %r: %s' % (dict1, dict2, list(difference))) 31 | 32 | 33 | class RouteDataTest(TestCase): 34 | 35 | def setUp(self): 36 | self.data = RouteData() 37 | self.db = self.data.db 38 | 39 | def test_version(self): 40 | self.assertEqual(self.data.version, '20110905') 41 | 42 | def test_get_country(self): 43 | self.assertRaises(CountryError, self.data.get_countrynum, 'URW') 44 | self.assertEqual(self.data.get_countrynum('JP'), '392') 45 | self.assertEqual(self.data.get_countrynum('DE'), '276') 46 | self.assertEqual(self.data.get_countrynum('de'), '276') 47 | 48 | def test_read_depots(self): 49 | c = self.db.cursor() 50 | c.execute("""SELECT * FROM depots WHERE DepotNumber=?""", ('0015', )) 51 | rows = c.fetchall() 52 | self.assertEqual(1, len(rows)) 53 | self.assertEqual((u'0015', u'', u'', u'Betriebsgesellschaft DPD GmbH', 54 | u'', u'Otto-Hahn-Strasse 5', u'', u'59423', u'Unna', u'DE', 55 | u'+49-(0) 23 03-8 88-0', u'+49-(0) 23 03-8 88-31', u'', u''), 56 | rows[0]) 57 | 58 | def test_expand_depots(self): 59 | c = self.db.cursor() 60 | c.execute("""SELECT id 61 | FROM routes 62 | WHERE DestinationCountry='DE' AND BeginPostCode='42477'""") 63 | rows = c.fetchall() 64 | # Interestingly we sometimes get two routes 65 | # self.assertEqual(1, len(rows)) 66 | route = rows[0][0] 67 | c.execute("SELECT depot FROM routedepots WHERE route=?", (route, )) 68 | rows = c.fetchall() 69 | self.assertEqual(1, len(rows)) 70 | 71 | def test_get_service(self): 72 | self.assertEqual(self.data.get_service('180'), ('180', 'AM1-NO', '', '022,160', '')) 73 | self.assertRaises(ServiceError, self.data.get_service, '100000') 74 | 75 | def test_get_servicetext(self): 76 | text = self.data.get_servicetext('185') 77 | self.assertEqual('DPD 10:00 Unfrei / ex works', text) 78 | 79 | def test_translate_location(self): 80 | self.assertEqual('1', self.data.translate_location('Dublin', 'IE')) 81 | self.assertRaises(TranslationError, self.data.translate_location, 'Cahir', 'IE') 82 | 83 | 84 | class RouterTest(TestCase): 85 | 86 | def setUp(self): 87 | self.data = RouteData() 88 | self.router = Router(self.data) 89 | 90 | def test_known_routes_de(self): 91 | route = self.router.route(Destination(postcode='42477')) 92 | self.assertDicEq(route.routingdata(), {'d_depot': u'0142', 'serviceinfo': '', 'country': u'DE', 93 | 'd_sort': u'65', 'o_sort': u'42', 'service_text': u'D'}) 94 | route = self.router.route(Destination(postcode='42897')) 95 | self.assertDicEq(route.routingdata(), {'d_depot': '0142', 'serviceinfo': '', 'country': 'DE', 96 | 'd_sort': '15', 'o_sort': '42', 'service_text': 'D'}) 97 | route = self.router.route(Destination(postcode='53111')) 98 | self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE', 99 | 'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'}) 100 | route = self.router.route(Destination(postcode='53111', country='DE')) 101 | self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE', 102 | 'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'}) 103 | route = self.router.route(Destination('DE', '53111')) 104 | self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE', 105 | 'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'}) 106 | route = self.router.route(Destination('DE', '53111', city='Bonn')) 107 | self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE', 108 | 'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'}) 109 | route = self.router.route(Destination('DE', '53111', 'Bonn')) 110 | self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE', 111 | 'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'}) 112 | 113 | def test_known_routes_world(self): 114 | route = self.router.route(Destination(postcode='66400', country='FR')) 115 | self.assertDicEq(route.routingdata(), {'d_depot': u'0470', 'serviceinfo': '', 'country': u'FR', 116 | 'd_sort': u'U50', 'o_sort': u'16', 'service_text': u'D'}) 117 | route = self.router.route(Destination('FR', '66400', 'Ceret')) 118 | self.assertDicEq(route.routingdata(), {'d_depot': '0470', 'serviceinfo': '', 'country': 'FR', 119 | 'd_sort': 'U50', 'o_sort': '16', 'service_text': 'D'}) 120 | route = self.router.route(Destination('BE', '3960', 'Bree/Belgien')) 121 | self.assertDicEq(route.routingdata(), {'d_depot': '0532', 'serviceinfo': '', 'country': 'BE', 122 | 'd_sort': u'A353', 'o_sort': '52', 'service_text': 'D'}) 123 | route = self.router.route(Destination('CH', '6005', 'Luzern')) 124 | self.assertDicEq(route.routingdata(), {'d_depot': '0616', 'serviceinfo': '', 'country': 'CH', 125 | 'd_sort': '40', 'o_sort': '78', 'service_text': 'D'}) 126 | route = self.router.route(Destination('AT', '1210', 'Wien')) 127 | self.assertDicEq(route.routingdata(), {'d_depot': '0622', 'serviceinfo': '', 'country': 'AT', 128 | 'd_sort': '10', 'o_sort': '62', 'service_text': 'D'}) 129 | route = self.router.route(Destination('AT', '4820', 'Bad Ischl')) 130 | self.assertDicEq(route.routingdata(), {'d_depot': '0624', 'serviceinfo': '', 'country': 'AT', 131 | 'd_sort': '63', 'o_sort': '62', 'service_text': 'D'}) 132 | route = self.router.route(Destination('AT', '7400', 'Oberwart')) 133 | self.assertDicEq(route.routingdata(), {'d_depot': '0628', 'serviceinfo': '', 'country': 'AT', 134 | 'd_sort': u'3270', 'o_sort': '62', 'service_text': 'D'}) 135 | route = self.router.route(Destination('AT', '4400', 'Steyr')) 136 | self.assertDicEq(route.routingdata(), {'d_depot': '0624', 'serviceinfo': '', 'country': 'AT', 137 | 'd_sort': '70', 'o_sort': '62', 'service_text': 'D'}) 138 | route = self.router.route(Destination('AT', '1220', 'Wien')) 139 | self.assertDicEq(route.routingdata(), {'d_depot': '0622', 'serviceinfo': '', 'country': 'AT', 140 | 'd_sort': '30', 'o_sort': '62', 'service_text': 'D'}) 141 | route = self.router.route(Destination('AT', '6890', 'Lustenau')) 142 | self.assertDicEq(route.routingdata(), {'d_depot': '0627', 'serviceinfo': '', 'country': 'AT', 143 | 'd_sort': '01', 'o_sort': '62', 'service_text': 'D'}) 144 | route = self.router.route(Destination('BE', '3520', 'ZONHOVEN')) 145 | self.assertDicEq(route.routingdata(), {'d_depot': u'0532', 'serviceinfo': '', 'country': u'BE', 146 | 'd_sort': u'A369', 'o_sort': u'52', 'service_text': u'D'}) 147 | route = self.router.route(Destination('BE', '4890', 'Thimister')) 148 | self.assertDicEq(route.routingdata(), {'d_depot': '0532', 'serviceinfo': '', 'country': 'BE', 149 | 'd_sort': u'B326', 'o_sort': '52', 'service_text': 'D'}) 150 | route = self.router.route(Destination('CH', '8305', 'Dietlikon')) 151 | self.assertDicEq(route.routingdata(), {'d_depot': '0615', 'serviceinfo': '', 'country': 'CH', 152 | 'd_sort': '77', 'o_sort': '78', 'service_text': 'D'}) 153 | route = self.router.route(Destination('CH', '4051', 'Basel')) 154 | self.assertDicEq(route.routingdata(), {'d_depot': '0610', 'serviceinfo': '', 'country': 'CH', 155 | 'd_sort': u'16', 'o_sort': '78', 'service_text': 'D'}) 156 | route = self.router.route(Destination('CH', '8808', 'Pfffikon')) 157 | self.assertDicEq(route.routingdata(), {'d_depot': '0615', 'serviceinfo': '', 'country': 'CH', 158 | 'd_sort': '71', 'o_sort': '78', 'service_text': 'D'}) 159 | route = self.router.route(Destination('DK', '9500', 'Hobro')) 160 | self.assertDicEq(route.routingdata(), {'d_depot': '0504', 'serviceinfo': '', 'country': 'DK', 161 | 'd_sort': u'405', 'o_sort': '20', 'service_text': 'D'}) 162 | # Lichtenstein is routed via CH 163 | route = self.router.route(Destination('LI', '8399', 'Windhof / Luxembourg')) 164 | self.assertDicEq(route.routingdata(), {'d_depot': '0617', 'serviceinfo': '', 'country': 'CH', 165 | 'd_sort': '', 'o_sort': '78', 'service_text': 'D'}) 166 | route = self.router.route(Destination('LI', '9495', 'Triesen')) 167 | self.assertDicEq(route.routingdata(), {'d_depot': '0617', 'serviceinfo': '', 'country': 'CH', 168 | 'd_sort': '', 'o_sort': '78', 'service_text': 'D'}) 169 | route = self.router.route(Destination('LI', '8440', 'Steinfort')) 170 | self.assertDicEq(route.routingdata(), {'d_depot': '0617', 'serviceinfo': '', 'country': 'CH', 171 | 'd_sort': '', 'o_sort': '78', 'service_text': 'D'}) 172 | route = self.router.route(Destination('CZ', '41742', 'Krupka 1')) 173 | self.assertDicEq(route.routingdata(), {'d_depot': '1380', 'serviceinfo': '', 'country': 'CZ', 174 | 'd_sort': '21', 'o_sort': '10', 'service_text': 'D'}) 175 | route = self.router.route(Destination('ES', '28802', 'Alcala de Henares (Madrid)')) 176 | self.assertDicEq(route.routingdata(), {'d_depot': '0728', 'serviceinfo': '', 'country': 'ES', 177 | 'd_sort': '01', 'o_sort': '16', 'service_text': 'D'}) 178 | route = self.router.route(Destination('ES', '28010', 'Madrid')) 179 | self.assertDicEq(route.routingdata(), {'d_depot': '0728', 'serviceinfo': '', 'country': 'ES', 180 | 'd_sort': '01', 'o_sort': '16', 'service_text': 'D'}) 181 | route = self.router.route(Destination('FR', '84170', 'MONTEUX')) 182 | self.assertDicEq(route.routingdata(), {'d_depot': '0447', 'serviceinfo': '', 'country': 'FR', 183 | 'd_sort': u'S65', 'o_sort': '16', 'service_text': 'D'}) 184 | route = self.router.route(Destination('FR', '91044', 'Evry Cedex')) 185 | self.assertDicEq(route.routingdata(), {'d_depot': u'0408', 'serviceinfo': '', 'country': u'FR', 186 | 'd_sort': u'S61', 'o_sort': u'50', 'service_text': u'D'}) 187 | 188 | def test_difficult_routingdepots(self): 189 | route = self.router.route(Destination('AT', '3626', 'Hnibach')) 190 | self.assertDicEq(route.routingdata(), {'d_depot': u'0623', 'serviceinfo': u'', 'country': 'AT', 191 | 'd_sort': u'01', 'o_sort': u'62', 'service_text': u'D'}) 192 | route = self.router.route(Destination('AT', '8225', 'Pllau')) 193 | self.assertDicEq(route.routingdata(), {'d_depot': u'0628', 'serviceinfo': u'', 'country': 'AT', 194 | 'd_sort': u'1290', 'o_sort': u'62', 'service_text': u'D'}) 195 | route = self.router.route(Destination('AT', '5020', 'Salzburg')) 196 | self.assertDicEq(route.routingdata(), {'d_depot': u'0625', 'serviceinfo': u'', 'country': 'AT', 197 | 'd_sort': u'1000', 'o_sort': u'62', 'service_text': u'D'}) 198 | route = self.router.route(Destination('SE', '65224', 'Karlstad')) 199 | self.assertDicEq(route.routingdata(), {'d_depot': u'0307', 'serviceinfo': u'', 'country': 'SE', 200 | 'd_sort': u'01', 'o_sort': u'20', 'service_text': u'D'}) 201 | route = self.router.route(Destination('AT', '2734', 'Buchberg/Schneeberg')) 202 | self.assertDicEq(route.routingdata(), {'d_depot': u'0621', 'serviceinfo': u'', 'country': 'AT', 203 | 'd_sort': u'64', 'o_sort': u'62', 'service_text': u'D'}) 204 | 205 | def test_difficult_service(self): 206 | route = self.router.route(Destination('AT', '4240', 'Freistadt Österreich')) 207 | self.assertDicEq(route.routingdata(), {'d_depot': u'0634', 'serviceinfo': '', 'country': u'AT', 208 | 'd_sort': u'22', 'o_sort': u'62', 'service_text': u'D'}) 209 | route = self.router.route(Destination('AT', '5101', 'Bergheim bei Salzburg')) 210 | self.assertDicEq(route.routingdata(), {'d_depot': u'0625', 'serviceinfo': u'', 'country': 'AT', 211 | 'd_sort': u'2509', 'o_sort': u'62', 'service_text': u'D'}) 212 | route = self.router.route(Destination('AT', '8230', 'Hartberg')) 213 | self.assertDicEq(route.routingdata(), {'d_depot': u'0628', 'serviceinfo': u'', 'country': 'AT', 214 | 'd_sort': u'1290', 'o_sort': u'62', 'service_text': u'D'}) 215 | route = self.router.route(Destination('AT', '8045', 'Graz/<96>sterreich')) 216 | self.assertDicEq(route.routingdata(), {'d_depot': u'0628', 'serviceinfo': u'', 'country': 'AT', 217 | 'd_sort': u'2840', 'o_sort': u'62', 'service_text': u'D'}) 218 | 219 | def test_postcode_with_country(self): 220 | route = self.router.route(Destination(postcode='FR-66400', country='FR')) 221 | self.assertDicEq(route.routingdata(), {'d_depot': u'0470', 'serviceinfo': '', 'country': u'FR', 222 | 'd_sort': u'U50', 'o_sort': u'16', 'service_text': u'D'}) 223 | route = self.router.route(Destination(postcode='FR 66400', country='FR')) 224 | self.assertDicEq(route.routingdata(), {'d_depot': u'0470', 'serviceinfo': '', 'country': u'FR', 225 | 'd_sort': u'U50', 'o_sort': u'16', 'service_text': u'D'}) 226 | route = self.router.route(Destination(postcode='FR66400', country='FR')) 227 | self.assertDicEq(route.routingdata(), {'d_depot': '0470', 'serviceinfo': '', 'country': 'FR', 228 | 'd_sort': 'U50', 'o_sort': '16', 'service_text': 'D'}) 229 | route = self.router.route(Destination(postcode='F-66400', country='FR')) 230 | self.assertDicEq(route.routingdata(), {'d_depot': '0470', 'serviceinfo': '', 'country': 'FR', 231 | 'd_sort': 'U50', 'o_sort': '16', 'service_text': 'D'}) 232 | 233 | def test_postcode_spaces(self): 234 | route = self.router.route(Destination(postcode='42 477')) 235 | self.assertDicEq(route.routingdata(), {'o_sort': '42', 'serviceinfo': '', 'country': 'DE', 236 | 'd_sort': '65', 'd_depot': '0142', 'service_text': 'D'}) 237 | route = self.router.route(Destination(postcode=' 42477')) 238 | self.assertDicEq(route.routingdata(), {'d_depot': u'0142', 'serviceinfo': '', 'country': u'DE', 239 | 'd_sort': u'65', 'o_sort': u'42', 'service_text': u'D'}) 240 | route = self.router.route(Destination(postcode=' 42477 ')) 241 | self.assertDicEq(route.routingdata(), {'d_depot': '0142', 'serviceinfo': '', 'country': 'DE', 242 | 'd_sort': '65', 'o_sort': '42', 'service_text': 'D'}) 243 | # real live sample 244 | route = self.router.route(Destination('GB', 'GU148HN', 'Hampshire')) 245 | self.assertDicEq(route.routingdata(), {'d_depot': '1550', 'serviceinfo': '', 'country': 'GB', 246 | 'd_sort': '', 'o_sort': '52', 'service_text': 'D'}) 247 | route = self.router.route(Destination('GB', 'GU 14 8HN', 'Hampshire')) 248 | self.assertDicEq(route.routingdata(), {'d_depot': '1550', 'serviceinfo': '', 'country': 'GB', 249 | 'd_sort': '', 'o_sort': '52', 'service_text': 'D'}) 250 | 251 | def test_problematic_routes(self): 252 | # Lichtenstein is problematic because usually it is routed trough Swizerland. 253 | route = self.router.route(Destination('LI', '8440')) 254 | self.assertDicEq(route.routingdata(), {'d_depot': u'0617', 'serviceinfo': '', 'country': u'CH', 255 | 'd_sort': u'', 'o_sort': u'78', 'service_text': u'D'}) 256 | route = self.router.route(Destination('LI', '8440', 'Steinfort')) 257 | self.assertDicEq(route.routingdata(), {'d_depot': u'0617', 'serviceinfo': '', 'country': u'CH', 258 | 'd_sort': u'', 'o_sort': u'78', 'service_text': u'D'}) 259 | 260 | def test_international(self): 261 | # AR | 1426 | Buenos Aire 262 | self.assertDicEq(get_route('AR', '1426').routingdata(), 263 | {'d_depot': u'0920', 'serviceinfo': u'', 'country': u'AR', 'd_sort': u'', 'o_sort': u'16', 264 | 'service_text': u'D'}) 265 | # AZ | 1073 | Baku 266 | self.assertDicEq(get_route('AZ', '1073').routingdata(), 267 | {'d_depot': u'0918', 'serviceinfo': '', 'country': 'AZ', 'd_sort': u'CDG', 'o_sort': u'16', 268 | 'service_text': u'D'}) 269 | # BE | 3960 | Bree/Belgien 270 | self.assertDicEq(get_route('BE', '3960').routingdata(), 271 | {'d_depot': u'0532', 'serviceinfo': u'', 'country': u'BE', 'd_sort': u'A353', 'o_sort': u'52', 272 | 'service_text': u'D'}) 273 | # BG | 1766 | Sofia 274 | self.assertDicEq(get_route('BG', '1766').routingdata(), 275 | {'d_depot': u'1660', 'serviceinfo': '', 'country': u'BG', 'd_sort': u'', 'o_sort': u'62', 'service_text': u'D'}) 276 | # CH | 3601 | Thun/Schweiz 277 | self.assertDicEq(get_route('CH', '3601').routingdata(), 278 | {'d_depot': u'0612', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78', 279 | 'service_text': u'D'}) 280 | # CZ | 16300 | Praha 6- Repy 281 | self.assertDicEq(get_route('CZ', '16300').routingdata(), 282 | {'d_depot': u'1391', 'serviceinfo': u'', 'country': u'CZ', 'd_sort': u'B854', 'o_sort': u'10', 283 | 'service_text': u'D'}) 284 | # CZ | 71000 | Ostrava 285 | self.assertDicEq(get_route('CZ', '71000').routingdata(), 286 | {'d_depot': u'1384', 'serviceinfo': u'', 'country': u'CZ', 'd_sort': u'412', 'o_sort': u'10', 287 | 'service_text': u'D'}) 288 | # DE | 04316 | Leipzig 289 | self.assertDicEq(get_route('DE', '04316').routingdata(), 290 | {'d_depot': u'0104', 'serviceinfo': '', 'country': u'DE', 'd_sort': u'2CPO', 'o_sort': u'10', 291 | 'service_text': u'D'}) 292 | # DE | 99974 | Mhlhausen / Thringen 293 | self.assertDicEq(get_route('DE', '99974').routingdata(), 294 | {'d_depot': u'0234', 'serviceinfo': u'', 'country': u'DE', 'd_sort': u'C015', 'o_sort': u'KK02', 295 | 'service_text': u'D'}) 296 | # DK | 4000 | Roskilde 297 | self.assertDicEq(get_route('DK', '4000').routingdata(), 298 | {'d_depot': u'0500', 'serviceinfo': u'', 'country': u'DK', 'd_sort': u'01', 'o_sort': u'20', 299 | 'service_text': u'D'}) 300 | # DK | 9500 | Hobro 301 | self.assertDicEq(get_route('DK', '9500').routingdata(), 302 | {'d_depot': u'0504', 'serviceinfo': u'', 'country': u'DK', 'd_sort': u'405', 'o_sort': u'20', 303 | 'service_text': u'D'}) 304 | # EE | 10621 | Tallinn 305 | self.assertDicEq(get_route('EE', '10621').routingdata(), 306 | {'d_depot': u'0560', 'serviceinfo': u'', 'country': u'EE', 'd_sort': u'0005', 'o_sort': u'13', 307 | 'service_text': u'D'}) 308 | # ES | 08227 | Terrassa 309 | self.assertDicEq(get_route('ES', '08227').routingdata(), 310 | {'d_depot': u'0708', 'serviceinfo': u'', 'country': u'ES', 'd_sort': u'01', 'o_sort': u'16', 311 | 'service_text': u'D'}) 312 | # FI | 94700 | Kemi 313 | self.assertDicEq(get_route('FI', '94700').routingdata(), 314 | {'d_depot': u'1614', 'serviceinfo': u'', 'country': u'FI', 'd_sort': u'510', 'o_sort': u'15', 315 | 'service_text': u'D'}) 316 | # FR | 91044 | EVRY-LISSES 317 | self.assertDicEq(get_route('FR', '91044').routingdata(), 318 | {'d_depot': u'0408', 'serviceinfo': '', 'country': u'FR', 'd_sort': u'S61', 'o_sort': u'50', 319 | 'service_text': u'D'}) 320 | # GB | BT387AR | Carrickfergus 321 | self.assertDicEq(get_route('GB', 'BT387AR').routingdata(), 322 | {'d_depot': u'1598', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52', 323 | 'service_text': u'D'}) 324 | # GB | CB13SW | Cambridge 325 | self.assertDicEq(get_route('GB', 'CB13SW').routingdata(), 326 | {'d_depot': u'1550', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52', 327 | 'service_text': u'D'}) 328 | # GB | G43 2DX | Glasgow 329 | self.assertDicEq(get_route('GB', 'G43 2DX').routingdata(), 330 | {'d_depot': u'1550', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52', 331 | 'service_text': u'D'}) 332 | # GB | G432DX | Glasgow 333 | self.assertDicEq(get_route('GB', 'G432DX').routingdata(), 334 | {'d_depot': u'1550', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52', 335 | 'service_text': u'D'}) 336 | # GB | N41NR | London 337 | self.assertDicEq(get_route('GB', 'N41NR').routingdata(), 338 | {'d_depot': u'1550', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52', 339 | 'service_text': u'D'}) 340 | # GR | 17341 | Athens 341 | self.assertDicEq(get_route('GR', '17341').routingdata(), 342 | {'d_depot': u'1251', 'serviceinfo': u'', 'country': u'GR', 'd_sort': u'', 'o_sort': u'62', 343 | 'service_text': u'D'}) 344 | # GR | 64200 | Chrisoupoli-KAVALA 345 | self.assertDicEq(get_route('GR', '64200').routingdata(), 346 | {'d_depot': u'1251', 'serviceinfo': u'', 'country': u'GR', 'd_sort': u'', 'o_sort': u'62', 347 | 'service_text': u'D'}) 348 | # HR | 10000 | Zagreb 349 | self.assertDicEq(get_route('HR', '10000').routingdata(), 350 | {'d_depot': u'1750', 'serviceinfo': u'', 'country': u'HR', 'd_sort': u'SVI', 'o_sort': u'62', 351 | 'service_text': u'D'}) 352 | # HR | 44000 | Sisak 353 | self.assertDicEq(get_route('HR', '44000').routingdata(), 354 | {'d_depot': u'1750', 'serviceinfo': u'', 'country': u'HR', 'd_sort': u'020', 'o_sort': u'62', 355 | 'service_text': u'D'}) 356 | # HU | 1121 | Budapest 357 | self.assertDicEq(get_route('HU', '1121').routingdata(), 358 | {'d_depot': u'1640', 'serviceinfo': u'', 'country': u'HU', 'd_sort': u'027', 'o_sort': u'62', 359 | 'service_text': u'D'}) 360 | # HU | 9400 | Sopron 361 | self.assertDicEq(get_route('HU', '9400').routingdata(), 362 | {'d_depot': u'1657', 'serviceinfo': u'', 'country': u'HU', 'd_sort': u'684', 'o_sort': u'62', 363 | 'service_text': u'D'}) 364 | # IT | 34100 | Trieste / Italien 365 | self.assertDicEq(get_route('IT', '34100').routingdata(), 366 | {'d_depot': u'0835', 'serviceinfo': u'', 'country': u'IT', 'd_sort': u'01', 'o_sort': u'16', 367 | 'service_text': u'D'}) 368 | # LI | 09494 | Schaan 369 | self.assertDicEq(get_route('LI', '09494').routingdata(), 370 | {'d_depot': u'0617', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78', 371 | 'service_text': u'D'}) 372 | # LI | 9999 | Wemperhardt 373 | self.assertDicEq(get_route('LI', '9999').routingdata(), 374 | {'d_depot': u'0617', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78', 375 | 'service_text': u'D'}) 376 | # LI | CH-9491 | Ruggell 377 | self.assertDicEq(get_route('LI', 'CH-9491').routingdata(), 378 | {'d_depot': u'0617', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78', 379 | 'service_text': u'D'}) 380 | # LI | 9491 | Ruggell 381 | self.assertDicEq(get_route('LI', '9491').routingdata(), 382 | {'d_depot': u'0617', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78', 383 | 'service_text': u'D'}) 384 | # LT | 3031 | Kaunas 385 | self.assertDicEq(get_route('LT', '3031').routingdata(), 386 | {'d_depot': u'0594', 'serviceinfo': '', 'country': u'LT', 'd_sort': u'732', 'o_sort': u'13', 387 | 'service_text': u'D'}) 388 | # LV | 1039 | Riga 389 | self.assertDicEq(get_route('LV', '1039').routingdata(), 390 | {'d_depot': u'0575', 'serviceinfo': u'', 'country': u'LV', 'd_sort': u'R39', 'o_sort': u'13', 391 | 'service_text': u'D'}) 392 | # NL | 7443TC | Nijverdal 393 | self.assertDicEq(get_route('NL', '7443TC').routingdata(), 394 | {'d_depot': u'0512', 'serviceinfo': '', 'country': u'NL', 'd_sort': u'B535', 'o_sort': u'52', 395 | 'service_text': u'D'}) 396 | # NL | 9405 JB | Assen 397 | self.assertDicEq(get_route('NL', '9405 JB').routingdata(), 398 | {'d_depot': u'0513', 'serviceinfo': u'', 'country': u'NL', 'd_sort': u'B241', 'o_sort': u'52', 399 | 'service_text': u'D'}) 400 | # NO | 6800 | Förde 401 | self.assertDicEq(get_route('NO', '6800').routingdata(), 402 | {'d_depot': u'0360', 'serviceinfo': u'', 'country': u'NO', 'd_sort': u'01', 'o_sort': u'15', 403 | 'service_text': u'D'}) 404 | # PL | 80516 | Gdansk 405 | self.assertDicEq(get_route('PL', '80516').routingdata(), 406 | {'d_depot': u'1306', 'serviceinfo': u'', 'country': u'PL', 'd_sort': u'', 'o_sort': u'13', 407 | 'service_text': u'D'}) 408 | # PL | 22300 | Krasnystaw 409 | self.assertDicEq(get_route('PL', '22300').routingdata(), 410 | {'d_depot': u'1300', 'serviceinfo': u'', 'country': u'PL', 'd_sort': u'', 'o_sort': u'13', 411 | 'service_text': u'D'}) 412 | # SE | 11358 | Stockholm 413 | self.assertDicEq(get_route('SE', '11358').routingdata(), 414 | {'d_depot': u'0312', 'serviceinfo': u'', 'country': u'SE', 'd_sort': u'01', 'o_sort': u'20', 415 | 'service_text': u'D'}) 416 | # SE | 75752 | Uppsala 417 | self.assertDicEq(get_route('SE', '75752').routingdata(), 418 | {'d_depot': u'0312', 'serviceinfo': u'', 'country': u'SE', 'd_sort': u'01', 'o_sort': u'20', 419 | 'service_text': u'D'}) 420 | # SI | 1225 | Lukovica 421 | self.assertDicEq(get_route('SI', '1225').routingdata(), 422 | {'d_depot': u'1696', 'serviceinfo': '', 'country': u'SI', 'd_sort': u'14', 'o_sort': u'62', 423 | 'service_text': u'D'}) 424 | # SK | 82105 | Bratislava 425 | self.assertDicEq(get_route('SK', '82105').routingdata(), 426 | {'d_depot': u'0660', 'serviceinfo': '', 'country': u'SK', 'd_sort': u'10', 'o_sort': u'10', 427 | 'service_text': u'D'}) 428 | 429 | def test_incorrectCountry(self): 430 | self.assertRaises(CountryError, get_route, 'URG', '42477') 431 | 432 | def test_incorrectLocation(self): 433 | self.assertRaises(TranslationError, get_route, 'DE', None) 434 | 435 | def test_incorrectService(self): 436 | self.assertRaises(ServiceError, get_route, 'DE', '0001') 437 | 438 | def test_select_routes(self): 439 | self.router.conditions = ['1=1'] 440 | rows = self.router.select_routes('DestinationCountry=?', ('UZ', )) 441 | self.assert_(len(rows) > 0) 442 | 443 | def test_cache(self): 444 | self.assertDicEq(vars(get_route('LI', '8440')), vars(get_route_without_cache('LI', '8440'))) 445 | 446 | 447 | class HighLevelTest(TestCase): 448 | 449 | def test_get_route(self): 450 | self.assertDicEq(vars(get_route('DE', '42897')), 451 | {'service_mark': u'', 'o_sort': u'42', 'serviceinfo': u'', 'barcode_id': u'37', 452 | 'grouping_priority': u'', 'country': u'DE', 'countrynum': u'276', 453 | 'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'15', 454 | 'postcode': u'42897', 'd_depot': u'0142', 'service_text': u'D'}) 455 | self.assertDicEq(vars(get_route('DE', '42897', 'Remscheid')), 456 | {'service_mark': u'', 'o_sort': u'42', 'serviceinfo': u'', 'barcode_id': u'37', 457 | 'grouping_priority': u'', 'country': u'DE', 'countrynum': u'276', 458 | 'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'15', 459 | 'postcode': u'42897', 'd_depot': u'0142', 'service_text': u'D'}) 460 | self.assertDicEq(vars(get_route('DE', '42897', 'Remscheid', '101')), 461 | {'service_mark': u'', 'o_sort': u'42', 'serviceinfo': u'', 'barcode_id': u'37', 462 | 'grouping_priority': u'', 'country': u'DE', 'countrynum': u'276', 463 | 'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'15', 464 | 'postcode': u'42897', 'd_depot': u'0142', 'service_text': u'D'}) 465 | self.assertDicEq(vars(get_route('LI', '8440')), 466 | {'service_mark': u'', 'o_sort': u'78', 'serviceinfo': u'', 'barcode_id': u'37', 467 | 'grouping_priority': u'', 'country': u'CH', 'countrynum': u'756', 468 | 'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'', 469 | 'postcode': u'8440', 'd_depot': u'0617', 'service_text': u'D'}) 470 | self.assertDicEq(vars(get_route(u'LI', '8440')), 471 | {'service_mark': u'', 'o_sort': u'78', 'serviceinfo': u'', 'barcode_id': u'37', 472 | 'grouping_priority': u'', 'country': u'CH', 'countrynum': u'756', 473 | 'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'', 474 | 'postcode': u'8440', 'd_depot': u'0617', 'service_text': u'D'}) 475 | self.assertDicEq(vars(get_route(u'LI', u'8440')), 476 | {'service_mark': u'', 'o_sort': u'78', 'serviceinfo': u'', 'barcode_id': u'37', 477 | 'grouping_priority': u'', 'country': u'CH', 'countrynum': u'756', 478 | 'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'', 479 | 'postcode': u'8440', 'd_depot': u'0617', 'service_text': u'D'}) 480 | 481 | def test_cache(self): 482 | self.assertEqual(vars(get_route('LI', '8440')), vars(get_route_without_cache('LI', '8440'))) 483 | 484 | if __name__ == '__main__': 485 | start = time.time() 486 | router = Router(RouteData()) 487 | stamp = time.time() 488 | router.route(Destination('AT', '4240', 'Freistadt Österreich')).routingdata() 489 | end = time.time() 490 | # print ("took %.3fs to find a single route (including %.3fs initialisation overhead)" 491 | # % (end-start, stamp-start)) 492 | 493 | unittest.main() 494 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/COUNTRY: -------------------------------------------------------------------------------- 1 | #Filename: COUNTRY 2 | #Version: 20140505 3 | #Expiration: 20140831 4 | #Hash: 01f9fd1927280385e2afd8287ca19c8528cb4ad2 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt 6 | #Fields: ISO-NumCountryCode|ISO-Alpha2CountryCode|ISO-Alpha3CountryCode|DestinationLanguages|FlagPostCodeNo| 7 | #Key: ISO-NumCountryCode| 8 | 004|AF|AFG|EN|0| 9 | 008|AL|ALB|SQ|0| 10 | 010|AQ|ATA|EN|1| 11 | 012|DZ|DZA|FR|0| 12 | 016|AS|ASM|EN|0| 13 | 020|AD|AND|FR,ES,CA|0| 14 | 024|AO|AGO|PT|1| 15 | 028|AG|ATG|EN|1| 16 | 031|AZ|AZE|AZ|0| 17 | 032|AR|ARG|ES|0| 18 | 036|AU|AUS|EN|0| 19 | 040|AT|AUT|DE|0| 20 | 044|BS|BHS|EN|1| 21 | 048|BH|BHR|AR|0| 22 | 050|BD|BGD|BN,EN|0| 23 | 051|AM|ARM|HY|0| 24 | 052|BB|BRB|EN|1| 25 | 056|BE|BEL|FR,NL,DE|0| 26 | 060|BM|BMU|EN|0| 27 | 064|BT|BTN|DZ|0| 28 | 068|BO|BOL|ES,AY,QU|1| 29 | 070|BA|BIH|BS,HR,SR|0| 30 | 072|BW|BWA|EN|0| 31 | 074|BV|BVT|NO,EN|1| 32 | 076|BR|BRA|PT|0| 33 | 084|BZ|BLZ|EN|0| 34 | 086|IO|IOT|EN|0| 35 | 090|SB|SLB|EN|1| 36 | 092|VG|VGB|EN|0| 37 | 096|BN|BRN|MS,EN|0| 38 | 100|BG|BGR|BG|0| 39 | 104|MM|MMR|MY|0| 40 | 108|BI|BDI|FR,RN|1| 41 | 112|BY|BLR|BE,RU|0| 42 | 116|KH|KHM|KM|0| 43 | 120|CM|CMR|FR,EN|0| 44 | 124|CA|CAN|EN,FR|0| 45 | 132|CV|CPV|PT|0| 46 | 136|KY|CYM|EN|0| 47 | 140|CF|CAF|FR,SG|0| 48 | 144|LK|LKA|SI,TA,EN|0| 49 | 148|TD|TCD|FR,AR|1| 50 | 152|CL|CHL|ES|0| 51 | 156|CN|CHN|ZH|0| 52 | 158|TW|TWN|EN|0| 53 | 162|CX|CXR|EN|1| 54 | 166|CC|CCK|EN|0| 55 | 170|CO|COL|ES|1| 56 | 174|KM|COM|FR,AR|0| 57 | 175|YT|MYT|FR,MG,SW|0| 58 | 178|CG|COG|FR,LN|0| 59 | 180|CD|COD|FR|0| 60 | 184|CK|COK|EN|0| 61 | 188|CR|CRI|ES|0| 62 | 191|HR|HRV|HR|0| 63 | 192|CU|CUB|ES|0| 64 | 196|CY|CYP|EL,TR|0| 65 | 203|CZ|CZE|CS|0| 66 | 204|BJ|BEN|FR|0| 67 | 208|DK|DNK|DA|0| 68 | 212|DM|DMA|EN|0| 69 | 214|DO|DOM|ES|0| 70 | 218|EC|ECU|ES,QU|0| 71 | 222|SV|SLV|ES|1| 72 | 226|GQ|GNQ|ES,FR|1| 73 | 231|ET|ETH|AM|0| 74 | 232|ER|ERI|AR,TI,EN|0| 75 | 233|EE|EST|ET|0| 76 | 234|FO|FRO|FO|0| 77 | 238|FK|FLK|EN|0| 78 | 239|GS|SGS|EN|0| 79 | 242|FJ|FJI|EN|0| 80 | 246|FI|FIN|FI,SV|0| 81 | 248|AX|ALA|SV|0| 82 | 250|FR|FRA|FR|0| 83 | 254|GF|GUF|FR,EN|0| 84 | 258|PF|PYF|FR,TY|0| 85 | 260|TF|ATF|FR,EN|1| 86 | 262|DJ|DJI|FR,AR,AA|1| 87 | 266|GA|GAB|FR|0| 88 | 268|GE|GEO|KA|0| 89 | 270|GM|GMB|EN|1| 90 | 275|PS|PSE|AR,EN|0| 91 | 276|DE|DEU|DE|0| 92 | 288|GH|GHA|EN|1| 93 | 292|GI|GIB|ES,EN|1| 94 | 296|KI|KIR|EN|1| 95 | 300|GR|GRC|EL,EN|0| 96 | 304|GL|GRL|DA|0| 97 | 308|GD|GRD|EN|1| 98 | 312|GP|GLP|FR|0| 99 | 316|GU|GUM|EN|0| 100 | 320|GT|GTM|ES|0| 101 | 324|GN|GIN|FR|0| 102 | 328|GY|GUY|EN|0| 103 | 332|HT|HTI|FR,HT|0| 104 | 334|HM|HMD|EN|1| 105 | 336|VA|VAT|IT|0| 106 | 340|HN|HND|ES|0| 107 | 344|HK|HKG|EN,ZH|1| 108 | 348|HU|HUN|HU,EN|0| 109 | 352|IS|ISL|IS,EN|0| 110 | 356|IN|IND|EN,HI|0| 111 | 360|ID|IDN|ID|0| 112 | 364|IR|IRN|FA|0| 113 | 368|IQ|IRQ|AR,KU|0| 114 | 372|IE|IRL|EN,GA|0| 115 | 376|IL|ISR|AR,HE|0| 116 | 380|IT|ITA|IT|0| 117 | 384|CI|CIV|FR|0| 118 | 388|JM|JAM|EN|1| 119 | 392|JP|JPN|JA,EN|0| 120 | 398|KZ|KAZ|KK,RU|0| 121 | 400|JO|JOR|AR|0| 122 | 404|KE|KEN|EN,SW|0| 123 | 408|KP|PRK|KO|1| 124 | 410|KR|KOR|KO|0| 125 | 414|KW|KWT|AR|0| 126 | 417|KG|KGZ|UZ,KY,RU|0| 127 | 418|LA|LAO|LO|0| 128 | 422|LB|LBN|AR,EN,FR|1| 129 | 426|LS|LSO|EN|0| 130 | 428|LV|LVA|LV|0| 131 | 430|LR|LBR|EN|0| 132 | 434|LY|LBY|AR|0| 133 | 438|LI|LIE|DE|0| 134 | 440|LT|LTU|LT|0| 135 | 442|LU|LUX|LB,FR,DE|0| 136 | 446|MO|MAC|PT,ZH|1| 137 | 450|MG|MDG|MG,FR,EN|0| 138 | 454|MW|MWI|EN|1| 139 | 458|MY|MYS|MS|0| 140 | 462|MV|MDV|DV|0| 141 | 466|ML|MLI|FR|1| 142 | 470|MT|MLT|MT,EN|0| 143 | 474|MQ|MTQ|FR|0| 144 | 478|MR|MRT|AR|1| 145 | 480|MU|MUS|EN,FR|0| 146 | 484|MX|MEX|ES|0| 147 | 492|MC|MCO|FR|0| 148 | 496|MN|MNG|MN|0| 149 | 498|MD|MDA|MO|0| 150 | 499|ME|MNE|SR|0| 151 | 500|MS|MSR|EN|1| 152 | 504|MA|MAR|AR|0| 153 | 508|MZ|MOZ|PT|0| 154 | 512|OM|OMN|AR|0| 155 | 516|NA|NAM|AF,EN|1| 156 | 520|NR|NRU|NA|1| 157 | 524|NP|NPL|NE|0| 158 | 528|NL|NLD|NL|0| 159 | 530|AN|ANT|NL|1| 160 | 531|CW|CUW|EN,NL|1| 161 | 533|AW|ABW|EN|1| 162 | 534|SX|SXM|EN,NL|1| 163 | 535|BQ|BES|NL|1| 164 | 540|NC|NCL|FR|0| 165 | 548|VU|VUT|FR,EN|0| 166 | 554|NZ|NZL|EN|0| 167 | 558|NI|NIC|ES|0| 168 | 562|NE|NER|FR|0| 169 | 566|NG|NGA|EN|0| 170 | 570|NU|NIU|EN|1| 171 | 574|NF|NFK|EN|1| 172 | 578|NO|NOR|NO,NB,NN|0| 173 | 580|MP|MNP|EN|1| 174 | 581|UM|UMI|EN|1| 175 | 583|FM|FSM|EN|0| 176 | 584|MH|MHL|EN|1| 177 | 585|PW|PLW|EN|0| 178 | 586|PK|PAK|EN|0| 179 | 591|PA|PAN|ES|1| 180 | 598|PG|PNG|EN|0| 181 | 600|PY|PRY|ES,GN|0| 182 | 604|PE|PER|ES,QU,AY|0| 183 | 608|PH|PHL|EN|0| 184 | 612|PN|PCN|EN|0| 185 | 616|PL|POL|PL|0| 186 | 620|PT|PRT|PT|0| 187 | 624|GW|GNB|PT|0| 188 | 626|TL|TLS|PT,ID|1| 189 | 630|PR|PRI|ES,EN|0| 190 | 634|QA|QAT|AR|1| 191 | 638|RE|REU|FR|0| 192 | 642|RO|ROU|RO|0| 193 | 643|RU|RUS|RU|0| 194 | 646|RW|RWA|FR,EN,RW|1| 195 | 654|SH|SHN|EN|0| 196 | 659|KN|KNA|EN|1| 197 | 660|AI|AIA|EN|1| 198 | 662|LC|LCA|EN|1| 199 | 663|MF|MAF|FR|0| 200 | 666|PM|SPM|FR|0| 201 | 670|VC|VCT|EN|1| 202 | 674|SM|SMR|IT|0| 203 | 678|ST|STP|PT|1| 204 | 682|SA|SAU|AR|0| 205 | 686|SN|SEN|FR|0| 206 | 688|RS|SRB|SR|0| 207 | 690|SC|SYC|FR,EN|1| 208 | 694|SL|SLE|EN|1| 209 | 702|SG|SGP|EN,MS,TA|0| 210 | 703|SK|SVK|SK|0| 211 | 704|VN|VNM|VI|0| 212 | 705|SI|SVN|SL|0| 213 | 706|SO|SOM|SO,EN|0| 214 | 710|ZA|ZAF|AF,EN|0| 215 | 716|ZW|ZWE|EN|1| 216 | 724|ES|ESP|ES|0| 217 | 728|SS|SSD|EN|0| 218 | 729|SD|SDN|AR,EN|0| 219 | 732|EH|ESH|AR,ES,FR|0| 220 | 740|SR|SUR|EN,NL|1| 221 | 744|SJ|SJM|NO|0| 222 | 748|SZ|SWZ|EN,SS|0| 223 | 752|SE|SWE|SV|0| 224 | 756|CH|CHE|DE,FR,IT|0| 225 | 760|SY|SYR|AR|0| 226 | 762|TJ|TJK|TG|0| 227 | 764|TH|THA|TH|0| 228 | 768|TG|TGO|FR|1| 229 | 772|TK|TKL|EN|0| 230 | 776|TO|TON|EN,TO|1| 231 | 780|TT|TTO|EN|0| 232 | 784|AE|ARE|AR|1| 233 | 788|TN|TUN|AR|0| 234 | 792|TR|TUR|TR|0| 235 | 795|TM|TKM|TK|0| 236 | 796|TC|TCA|EN|0| 237 | 798|TV|TUV|EN|1| 238 | 800|UG|UGA|EN,SW|1| 239 | 804|UA|UKR|UK,RU|0| 240 | 807|MK|MKD|MK|0| 241 | 818|EG|EGY|AR|0| 242 | 826|GB|GBR|EN|0| 243 | 831|GG|GGY|EN|0| 244 | 832|JE|JEY|EN|0| 245 | 833|IM|IMN|EN|0| 246 | 834|TZ|TZA|EN,SW|1| 247 | 840|US|USA|EN|0| 248 | 850|VI|VIR|EN|0| 249 | 854|BF|BFA|FR|0| 250 | 858|UY|URY|ES|0| 251 | 860|UZ|UZB|UZ|0| 252 | 862|VE|VEN|ES|0| 253 | 876|WF|WLF|FR|0| 254 | 882|WS|WSM|SM|1| 255 | 887|YE|YEM|AR|0| 256 | 894|ZM|ZMB|EN|0| 257 | 991|IC|ISC|ES|0| 258 | 999|ZZ|ZZZ|EN|0| 259 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/DEPOTS.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hudora/pyShipping/089c502db5d79182dbd69f0b95c475e0eddda355/pyshipping/carriers/dpd/georoutetables/DEPOTS.gz -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/LOCATION.DE: -------------------------------------------------------------------------------- 1 | #Filename: LOCATION.DE 2 | #Version: 20140505 3 | #Expiration: 20140831 4 | #Hash: 6eedc807e450c4e8a76ee039ff22226d38b95f30 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt 6 | #Fields: AreaName|CityName|ISO-Alpha2CountryCode|PostCode| 7 | #Key: AreaName|CityName| 8 | |Dublin|IE|1| 9 | |Irland ohne Dublin|IE|2| 10 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/LOCATION.EN: -------------------------------------------------------------------------------- 1 | #Filename: LOCATION.EN 2 | #Version: 20140505 3 | #Expiration: 20140831 4 | #Hash: 6f7eff135d73b9bf60bc6d9c983313d8811fbcb2 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt 6 | #Fields: AreaName|CityName|ISO-Alpha2CountryCode|PostCode| 7 | #Key: AreaName|CityName| 8 | |Dublin|IE|1| 9 | |Ireland excluding Dublin|IE|2| 10 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/LOCATION.FR: -------------------------------------------------------------------------------- 1 | #Filename: LOCATION.FR 2 | #Version: 20140505 3 | #Expiration: 20140831 4 | #Hash: 71ba3db7983f8bcfa2a1b1cd327572ffcafaa331 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt 6 | #Fields: AreaName|CityName|ISO-Alpha2CountryCode|PostCode| 7 | #Key: AreaName|CityName| 8 | |Dublin|IE|1| 9 | |Reste de l'Irlande (sauf Dublin)|IE|2| 10 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/ROUTES.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hudora/pyShipping/089c502db5d79182dbd69f0b95c475e0eddda355/pyshipping/carriers/dpd/georoutetables/ROUTES.gz -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/SERVICE: -------------------------------------------------------------------------------- 1 | #Filename: SERVICE 2 | #Version: 20140505 3 | #Expiration: 20140831 4 | #Hash: c8aa7333314e4057f7cf43d42ec66e9f89588345 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt 6 | #Fields: ServiceCode|ServiceText|ServiceMark|ServiceElements| 7 | #Key: ServiceCode| 8 | 101|D||001| 9 | 102|D-HAZ||001,150| 10 | 103|D-6||001,130| 11 | 104|D-6-HAZ||001,130,150| 12 | 105|D-EXW||001,110| 13 | 106|D-EXW-HAZ||001,110,150| 14 | 107|D-6-EXW||001,110,130| 15 | 108|D-6-EXW-HAZ||001,110,130,150| 16 | 109|D-COD||001,100| 17 | 110|D-COD-HAZ||001,100,150| 18 | 111|D-6-COD||001,100,130| 19 | 112|D-6-COD-HAZ||001,100,130,150| 20 | 113|D-SWAP||001,120| 21 | 114|D-SWAP-HAZ||001,120,150| 22 | 115|D-6-SWAP||001,120,130| 23 | 116|D-6-SWAP-HAZ||001,120,130,150| 24 | 117|D||001,170| 25 | 118|D-BACK||001,121| 26 | 119|D-BACK-HAZ||001,121,150| 27 | 120|D+||001,180| 28 | 121|D-HAZ+||001,150,180| 29 | 122|D-6+||001,130,180| 30 | 123|D-6-HAZ+||001,130,150,180| 31 | 124|D-EXW+||001,110,180| 32 | 125|D-EXW-HAZ+||001,110,150,180| 33 | 126|D-6-EXW+||001,110,130,180| 34 | 127|D-6-EXW-HAZ+||001,110,130,150,180| 35 | 128|D-COD+||001,100,180| 36 | 129|D-COD-HAZ+||001,100,150,180| 37 | 130|D-6-COD+||001,100,130,180| 38 | 131|D-6-COD-HAZ+||001,100,130,150,180| 39 | 132|D-SWAP+||001,120,180| 40 | 133|D-SWAP-HAZ+||001,120,150,180| 41 | 134|D-6-SWAP+||001,120,130,180| 42 | 135|D-6-SWAP-HAZ+||001,120,130,150,180| 43 | 136|D|X|002| 44 | 137|D-6|X|002,130| 45 | 138|D-EXW|X|002,110| 46 | 139|D-6-EXW|X|002,110,130| 47 | 140|D-COD|X|002,100| 48 | 141|D-6-COD|X|002,100,130| 49 | 142|D-SWAP|X|002,120| 50 | 143|D-6-SWAP|X|002,120,130| 51 | 144|D|X|002,170| 52 | 145|D-BACK|X|002,121| 53 | 146|D+|X|002,180| 54 | 147|D-6+|X|002,130,180| 55 | 148|D-EXW+|X|002,110,180| 56 | 149|D-6-EXW+|X|002,110,130,180| 57 | 150|D-COD+|X|002,100,180| 58 | 151|D-6-COD+|X|002,100,130,180| 59 | 152|D-SWAP+|X|002,120,180| 60 | 153|D-6-SWAP+|X|002,120,130,180| 61 | 154|PARCELLetter|X|005| 62 | 155|PM2||010| 63 | 156|PM2-NO||010,160| 64 | 157|PM2-HAZ||010,150| 65 | 158|PM2-EXW||010,110| 66 | 159|PM2-EXW-NO||010,110,160| 67 | 160|PM2-EXW-HAZ||010,110,150| 68 | 161|PM2-COD||010,100| 69 | 162|PM2-COD-NO||010,100,160| 70 | 163|PM2-COD-HAZ||010,100,150| 71 | 164|PM2-SWAP||010,120| 72 | 165|PM2-SWAP-HAZ||010,120,150| 73 | 166|PM2-BACK||010,121| 74 | 167|PM2-BACK-HAZ||010,121,150| 75 | 168|PM2+||010,180| 76 | 169|PM2-NO+||010,160,180| 77 | 170|PM2-HAZ+||010,150,180| 78 | 171|PM2-EXW+||010,110,180| 79 | 172|PM2-EXW-NO+||010,110,160,180| 80 | 173|PM2-EXW-HAZ+||010,110,150,180| 81 | 174|PM2-COD+||010,100,180| 82 | 175|PM2-COD-NO+||010,100,160,180| 83 | 176|PM2-COD-HAZ+||010,100,150,180| 84 | 177|PM2-SWAP+||010,120,180| 85 | 178|PM2-SWAP-HAZ+||010,120,150,180| 86 | 179|AM1||022| 87 | 180|AM1-NO||022,160| 88 | 181|AM1-HAZ||022,150| 89 | 182|AM1-6||022,130| 90 | 183|AM1-6-NO||022,130,160| 91 | 184|AM1-6-HAZ||022,130,150| 92 | 185|AM1-EXW||022,110| 93 | 186|AM1-EXW-NO||022,110,160| 94 | 187|AM1-EXW-HAZ||022,110,150| 95 | 188|AM1-6-EXW||022,110,130| 96 | 189|AM1-6-EXW-NO||022,110,130,160| 97 | 190|AM1-6-EXW-HAZ||022,110,130,150| 98 | 191|AM1-COD||022,100| 99 | 192|AM1-COD-NO||022,100,160| 100 | 193|AM1-COD-HAZ||022,100,150| 101 | 194|AM1-6-COD||022,100,130| 102 | 195|AM1-6-COD-NO||022,100,130,160| 103 | 196|AM1-6-COD-HAZ||022,100,130,150| 104 | 197|AM1-SWAP||022,120| 105 | 198|AM1-SWAP-HAZ||022,120,150| 106 | 199|AM1-6-SWAP||022,120,130| 107 | 200|AM1-6-SWAP-HAZ||022,120,130,150| 108 | 201|AM1-BACK||022,121| 109 | 202|AM1-BACK-HAZ||022,121,150| 110 | 203|AM1+||022,180| 111 | 204|AM1-NO+||022,160,180| 112 | 205|AM1-HAZ+||022,150,180| 113 | 206|AM1-6+||022,130,180| 114 | 207|AM1-6-NO+||022,130,160,180| 115 | 208|AM1-6-HAZ+||022,130,150,180| 116 | 209|AM1-EXW+||022,110,180| 117 | 210|AM1-EXW-NO+||022,110,160,180| 118 | 211|AM1-EXW-HAZ+||022,110,150,180| 119 | 212|AM1-6-EXW+||022,110,130,180| 120 | 213|AM1-6-EXW-NO+||022,110,130,160,180| 121 | 214|AM1-6-EXW-HAZ+||022,110,130,150,180| 122 | 215|AM1-COD+||022,100,180| 123 | 216|AM1-COD-NO+||022,100,160,180| 124 | 217|AM1-COD-HAZ+||022,100,150,180| 125 | 218|AM1-6-COD+||022,100,130,180| 126 | 219|AM1-6-COD-NO+||022,100,130,160,180| 127 | 220|AM1-6-COD-HAZ+||022,100,130,150,180| 128 | 221|AM1-SWAP+||022,120,180| 129 | 222|AM1-SWAP-HAZ+||022,120,150,180| 130 | 223|AM1-6-SWAP+||022,120,130,180| 131 | 224|AM1-6-SWAP-HAZ+||022,120,130,150,180| 132 | 225|AM2||023| 133 | 226|AM2-NO||023,160| 134 | 227|AM2-HAZ||023,150| 135 | 228|AM2-6||023,130| 136 | 229|AM2-6-NO||023,130,160| 137 | 230|AM2-6-HAZ||023,130,150| 138 | 231|AM2-EXW||023,110| 139 | 232|AM2-EXW-NO||023,110,160| 140 | 233|AM2-EXW-HAZ||023,110,150| 141 | 234|AM2-6-EXW||023,110,130| 142 | 235|AM2-6-EXW-NO||023,110,130,160| 143 | 236|AM2-6-EXW-HAZ||023,110,130,150| 144 | 237|AM2-COD||023,100| 145 | 238|AM2-COD-NO||023,100,160| 146 | 239|AM2-COD-HAZ||023,100,150| 147 | 240|AM2-6-COD||023,100,130| 148 | 241|AM2-6-COD-NO||023,100,130,160| 149 | 242|AM2-6-COD-HAZ||023,100,130,150| 150 | 243|AM2-SWAP||023,120| 151 | 244|AM2-SWAP-HAZ||023,120,150| 152 | 245|AM2-6-SWAP||023,120,130| 153 | 246|AM2-6-SWAP-HAZ||023,120,130,150| 154 | 247|AM2-BACK||023,121| 155 | 248|AM2-BACK-HAZ||023,121,150| 156 | 249|AM2+||023,180| 157 | 250|AM2-NO+||023,160,180| 158 | 251|AM2-HAZ+||023,150,180| 159 | 252|AM2-6+||023,130,180| 160 | 253|AM2-6-NO+||023,130,160,180| 161 | 254|AM2-6-HAZ+||023,130,150,180| 162 | 255|AM2-EXW+||023,110,180| 163 | 256|AM2-EXW-NO+||023,110,160,180| 164 | 257|AM2-EXW-HAZ+||023,110,150,180| 165 | 258|AM2-6-EXW+||023,110,130,180| 166 | 259|AM2-6-EXW-NO+||023,110,130,160,180| 167 | 260|AM2-6-EXW-6-HAZ+||023,110,130,150,180| 168 | 261|AM2-COD+||023,100,180| 169 | 262|AM2-COD-NO+||023,100,160,180| 170 | 263|AM2-COD-HAZ+||023,100,150,180| 171 | 264|AM2-6-COD+||023,100,130,180| 172 | 265|AM2-6-COD-NO+||023,100,130,160,180| 173 | 266|AM2-6-COD-HAZ+||023,100,130,150,180| 174 | 267|AM2-SWAP+||023,120,180| 175 | 268|AM2-SWAP-HAZ+||023,120,150,180| 176 | 269|AM2-6-SWAP+||023,120,130,180| 177 | 270|AM2-6-SWAP-HAZ+||023,120,130,150,180| 178 | 271|SD||040| 179 | 272|SD-HAZ||040,150| 180 | 273|SD-EXW||040,110| 181 | 274|SD-EXW-HAZ||040,110,150| 182 | 275|SD-COD||040,100| 183 | 276|SD-COD-HAZ||040,100,150| 184 | 277|SD-SWAP||040,120| 185 | 278|SD-SWAP-HAZ||040,120,150| 186 | 279|SD-BACK||040,121| 187 | 280|SD-BACK-HAZ||040,121,150| 188 | 281|SD+||040,180| 189 | 282|SD-HAZ+||040,150,180| 190 | 283|SD-EXW+||040,110,180| 191 | 284|SD-EXW-HAZ+||040,110,150,180| 192 | 285|SD-COD+||040,100,180| 193 | 286|SD-COD-HAZ+||040,100,150,180| 194 | 287|SD-SWAP+||040,120,180| 195 | 288|SD-SWAP-HAZ+||040,120,150,180| 196 | 289|STANDARD BAG||060| 197 | 290|MAIL BAG||005,060| 198 | 291|IP||080| 199 | 292|POD BOX||081| 200 | 293|EXPRESS BAG||020,060| 201 | 294|MAIL||006,060| 202 | 298|RETURN SENDER||070,071| 203 | 299|RETURN|E|020,070| 204 | 300|RETURN||070| 205 | 301|RETURN-HAZ||070,150| 206 | 302|IE2|E|030| 207 | 303|IE2-MPS|E|030,140| 208 | 304|IE2-6|E|030,130| 209 | 305|IE2-6-MPS|E|030,130,140| 210 | 306|IE2-SWAP|E|030,120| 211 | 307|IE2-SWAP-MPS|E|030,120,140| 212 | 308|IE2-6-SWAP|E|030,120,130| 213 | 309|IE2-6-SWAP-MPS|E|030,120,130,140| 214 | 310|IE2-COD|E|030,100| 215 | 311|IE2-COD-MPS|E|030,100,140| 216 | 312|IE2-6-COD|E|030,100,130| 217 | 313|IE2-6-COD-MPS|E|030,100,130,140| 218 | 314|IE1|E|031| 219 | 325|D||001,012| 220 | 326|D|X|002,012| 221 | 327|D-B2C||001,013| 222 | 328|D-B2C|X|002,013| 223 | 329|D-COD-B2C||001,013,100| 224 | 330|D-COD-B2C|X|002,013,100| 225 | 331|D-TYRE UNP||001,014| 226 | 332|RETURN||072| 227 | 333|D-B2C+||001,013,180| 228 | 334|D-B2C+|X|002,013,180| 229 | 335|D-COD-B2C+||001,013,100,180| 230 | 336|D-COD-B2C+|X|002,013,100,180| 231 | 337|D-B2C-PSD||001,013,200| 232 | 338|D-B2C-PSD|X|002,013,200| 233 | 340|DPD MAX||090| 234 | 341|D-B2C-COD-PSD||001,013,100,200| 235 | 342|D-B2C-COD-PSD|X|002,013,100,200| 236 | 350|AM0||021| 237 | 351|AM0-EXW||021,110| 238 | 352|AM0-COD||021,100| 239 | 353|AM0-SWAP||021,120| 240 | 354|AM0-BACK||021,121| 241 | 355|AM0+||021,180| 242 | 356|AM0-EXW+||021,110,180| 243 | 357|AM0-COD+||021,100,180| 244 | 358|AM0-SWAP+||021,120,180| 245 | 359|D||001,190| 246 | 360|D|X|002,190| 247 | 361|D||001,110,190| 248 | 362|D|X|002,110,190| 249 | 363|D||001,210| 250 | 364|D|X|002,210| 251 | 365|D-TYRE||001,240| 252 | 366|D-TYRE-B2C||001,240,013| 253 | 367|D-TYRE-COD||001,240,100| 254 | 800|EXP||020| 255 | 801|EXP-COD||020,100| 256 | 802|EXP-EXW||020,110| 257 | 803|PS||610| 258 | 804|PS-COD||610,100| 259 | 805|PS-EXP||610,020| 260 | 806|D-SWAP-B2C+||001,013,120,180| 261 | 807|D-6-SWAP-B2C+||001,013,120,130,180| 262 | 808|D-SWAP-COD-B2C+||001,013,100,120,180| 263 | 809|D6-SWAP-COD-B2C+||001,013,100,120,130,180| 264 | 810|AM1||022| 265 | 811|AM2||023| 266 | 812|PM2||010| 267 | 813|TFR||600| 268 | 814|AM1-6||022,130| 269 | 815|AM2-6||023,130| 270 | 816|FIX||601| 271 | 817|PRIVAT||602| 272 | 818|D-SWAP-COD+||001,100,120,180| 273 | 819|D-6-SWAP-COD+||001,100,120,130,180| 274 | 820|D-6-B2C+||001,013,130,180| 275 | 821|D-6-B2C+|X|002,013,130,180| 276 | 822|D-6-COD-B2C+||001,013,100,130,180| 277 | 823|D-6-COD-B2C+|X|002,013,100,130,180| 278 | 824|D-ECO||001,250| 279 | 825|DPD MAX||090| 280 | 826|DPD MAX - COD||090,100| 281 | 827|D-SWAP-B2C||001,013,120| 282 | 828|D-SWAP-B2C|X|002,013,120| 283 | 829|D-6-B2C||001,013,130| 284 | 830|D-6-B2C|X|002,013,130| 285 | 831|D-6-COD-B2C||001,013,100,130| 286 | 832|D-6-COD-B2C|X|002,013,100,130| 287 | 833|D-6-SWAP-B2C||001,013,120,130| 288 | 834|D-6-SWAP-B2C|X|002,013,120,130| 289 | 835|D-SWAP-COD-B2C||001,013,100,120| 290 | 836|D-SWAP-COD-B2C|X|002,013,100,120| 291 | 837|D-6-SWAP-COD-B2C||001,013,100,120,130| 292 | 838|D-6-SWAP-COD-B2C|X|002,013,100,120,130| 293 | 839|D-EVE||001,220| 294 | 840|D-EVE|X|002,220| 295 | 841|D-COD-EVE||001,100,220| 296 | 842|D-COD-EVE|X|002,100,220| 297 | 843|SPAL||230| 298 | 844|D-B2C-PRIV||001,013,620| 299 | 845|D-B2C-PRIV|X|002,013,620| 300 | 846|D-B2C-HOME||001,013,621| 301 | 847|D-B2C-HOME|X|002,013,621| 302 | 848|D-SWAP-COD||001,100,120| 303 | 849|D-ECO-COD||001,250,100| 304 | 850|AM2-ECH-SHOP-NO||023,529,511,160| 305 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/SERVICEINFO.CS: -------------------------------------------------------------------------------- 1 | #Filename: SERVICEINFO.CS 2 | #Version: 20140505 3 | #Expiration: 20140831 4 | #Hash: 755fd81761d0474d68f00ff70ce4516fa8fef173 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt 6 | #Fields: ServiceCode|ServiceFieldInfo| 7 | #Key: ServiceCode| 8 | 102|nebezpecne zbozi / hazardous goods| 9 | 103|sobota /saturday| 10 | 106|ex works nebezpecne zbozi / ex works hazardous goods| 11 | 109|dobirka / C.O.D.| 12 | 110|dobirka nebezpecne zbozi / C.O.D. hazardous goods| 13 | 113|vymena / exchange| 14 | 117|Collection-upon-Delivery| 15 | 121|nebezpecne zbozi / hazardous goods| 16 | 124|ex works| 17 | 125|ex works nebezpecne zbozi / ex works hazardous goods| 18 | 128|dobirka / C.O.D.| 19 | 129|dobirka nebezpecne zbozi / C.O.D. hazardous goods| 20 | 132|vymena / exchange| 21 | 137|sobota / saturday| 22 | 138|ex works| 23 | 140|dobirka / C.O.D.| 24 | 142|vymena / exchange| 25 | 144|Collection-upon-Delivery| 26 | 148|ex works| 27 | 150|dobirka / C.O.D.| 28 | 152|vymena / exchange| 29 | 154|DPD PARCELLetter| 30 | 155|DPD 18:00 / DPD GUARANTEE| 31 | 158|DPD 18:00 ex works / DPD GUARANTEE ex works| 32 | 161|DPD 18:00 dobirka / DPD GUARANTEE C.O.D.| 33 | 164|DPD 18:00 vymena / DPD GUARANTEE exchange| 34 | 168|DPD 18:00 / DPD GUARANTEE| 35 | 171|DPD 18:00 ex works / DPD GUARANTEE ex works| 36 | 174|DPD 18:00 dobirka / DPD GUARANTEE C.O.D.| 37 | 179|DPD 10:00| 38 | 185|DPD 10:00 ex works| 39 | 191|DPD 10:00 dobirka / C.O.D.| 40 | 197|DPD 10:00 vymena / exchange| 41 | 203|DPD 10:00| 42 | 209|DPD 10:00 ex works| 43 | 215|DPD 10:00 dobirka / C.O.D.| 44 | 225|DPD 12:00| 45 | 228|DPD 12:00 sobota / Saturday| 46 | 231|DPD 12:00 ex works| 47 | 234|DPD 12:00 sobota ex works / Saturday ex works| 48 | 237|DPD 12:00 dobirka / C.O.D.| 49 | 240|DPD 12:00 sobota dobirka / Saturday C.O.D.| 50 | 243|DPD 12:00 vymena / exchange| 51 | 249|DPD 12:00| 52 | 252|DPD 12:00 sobota / Saturday| 53 | 255|DPD 12:00 ex works| 54 | 258|DPD 12:00 sobota ex works / Saturday ex works| 55 | 261|DPD 12:00 dobirka / C.O.D.| 56 | 271|stejny den / same day| 57 | 327|PRIVATE ADDRESS / B2C| 58 | 328|PRIVATE ADDRESS / B2C| 59 | 329|PRIVATE ADDRESS / B2C - C.O.D.| 60 | 330|PRIVATE ADDRESS / B2C - C.O.D.| 61 | 333|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE| 62 | 334|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE| 63 | 335|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE| 64 | 336|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE| 65 | 352|DPD 8:30 dobirka / C.O.D.| 66 | 353|DPD 8:30 vymena / exchange| 67 | 357|DPD 8:30 dobirka / C.O.D.| 68 | 358|DPD 8:30 vymena / exchange| 69 | 839|VECERNI DORUCENI 17:00-20:00| 70 | 840|VECERNI DORUCENI 17:00-20:00| 71 | 841|VECERNI DORUCENI 17:00-20:00 DOBIRKA| 72 | 842|VECERNI DORUCENI 17:00-20:00 DOBIRKA| 73 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/SERVICEINFO.CZ: -------------------------------------------------------------------------------- 1 | #Filename: SERVICEINFO.CZ 2 | #Version: 20110905 3 | #Expiration: 20120101 4 | #Hash: de42bef2636b38cd7f371000f71df64f66cc0eda 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20110905.txt 6 | #Fields: ServiceCode|ServiceFieldInfo| 7 | #Key: ServiceCode| 8 | 102|nebezpecne zbozi / hazardous goods| 9 | 103|sobota /saturday| 10 | 106|ex works nebezpecne zbozi / ex works hazardous goods| 11 | 109|dobirka / C.O.D.| 12 | 110|dobirka nebezpecne zbozi / C.O.D. hazardous goods| 13 | 113|vymena / exchange| 14 | 117|Collection-upon-Delivery| 15 | 121|nebezpecne zbozi / hazardous goods| 16 | 124|ex works| 17 | 125|ex works nebezpecne zbozi / ex works hazardous goods| 18 | 128|dobirka / C.O.D.| 19 | 129|dobirka nebezpecne zbozi / C.O.D. hazardous goods| 20 | 132|vymena / exchange| 21 | 137|sobota / saturday| 22 | 138|ex works| 23 | 140|dobirka / C.O.D.| 24 | 142|vymena / exchange| 25 | 144|Collection-upon-Delivery| 26 | 148|ex works| 27 | 150|dobirka / C.O.D.| 28 | 152|vymena / exchange| 29 | 154|DPD PARCELLetter| 30 | 155|DPD 18:00 / DPD GUARANTEE| 31 | 158|DPD 18:00 ex works / DPD GUARANTEE ex works| 32 | 161|DPD 18:00 dobirka / DPD GUARANTEE C.O.D.| 33 | 164|DPD 18:00 vymena / DPD GUARANTEE exchange| 34 | 168|DPD 18:00 / DPD GUARANTEE| 35 | 171|DPD 18:00 ex works / DPD GUARANTEE ex works| 36 | 174|DPD 18:00 dobirka / DPD GUARANTEE C.O.D.| 37 | 179|DPD 10:00| 38 | 185|DPD 10:00 ex works| 39 | 191|DPD 10:00 dobirka / C.O.D.| 40 | 197|DPD 10:00 vymena / exchange| 41 | 203|DPD 10:00| 42 | 209|DPD 10:00 ex works| 43 | 215|DPD 10:00 dobirka / C.O.D.| 44 | 225|DPD 12:00| 45 | 228|DPD 12:00 sobota / Saturday| 46 | 231|DPD 12:00 ex works| 47 | 234|DPD 12:00 sobota ex works / Saturday ex works| 48 | 237|DPD 12:00 dobirka / C.O.D.| 49 | 240|DPD 12:00 sobota dobirka / Saturday C.O.D.| 50 | 243|DPD 12:00 vymena / exchange| 51 | 249|DPD 12:00| 52 | 252|DPD 12:00 sobota / Saturday| 53 | 255|DPD 12:00 ex works| 54 | 258|DPD 12:00 sobota ex works / Saturday ex works| 55 | 261|DPD 12:00 dobirka / C.O.D.| 56 | 271|stejny den / same day| 57 | 327|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE| 58 | 328|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE| 59 | 329|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE| 60 | 330|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE| 61 | 333|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE| 62 | 334|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE| 63 | 335|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE| 64 | 336|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE| 65 | 352|DPD 8:30 dobirka / C.O.D.| 66 | 353|DPD 8:30 vymena / exchange| 67 | 357|DPD 8:30 dobirka / C.O.D.| 68 | 358|DPD 8:30 vymena / exchange| 69 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/SERVICEINFO.DE: -------------------------------------------------------------------------------- 1 | #Filename: SERVICEINFO.DE 2 | #Version: 20140505 3 | #Expiration: 20140831 4 | #Hash: 8124d0f60be0fe5c28a7f0af019c975fc2bd3542 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt 6 | #Fields: ServiceCode|ServiceFieldInfo| 7 | #Key: ServiceCode| 8 | 102|Gefahrgut / hazardous goods| 9 | 103|Samstag / saturday| 10 | 105|Unfrei / ex works| 11 | 106|Unfrei / ex works Gefahrgut / hazardous goods| 12 | 109|Nachnahme / C.O.D.| 13 | 110|Nachnahme / C.O.D. Gefahrgut / hazardous goods| 14 | 113|Austausch / exchange| 15 | 117|Mitnahme/Collection-upon-Delivery| 16 | 121|Gefahrgut / hazardous goods| 17 | 124|Unfrei / ex works| 18 | 125|Unfrei / ex works Gefahrgut / hazardous goods| 19 | 128|Nachnahme / C.O.D.| 20 | 129|Nachnahme / C.O.D. Gefahrgut / hazardous goods| 21 | 132|Austausch / exchange| 22 | 137|Samstag / saturday| 23 | 138|Unfrei / ex works| 24 | 140|Nachnahme / C.O.D.| 25 | 142|Austausch / exchange| 26 | 144|Mitnahme/Collection-upon-Delivery| 27 | 148|Unfrei / ex works| 28 | 150|Nachnahme / C.O.D.| 29 | 152|Austausch / exchange| 30 | 154|DPD PARCELLetter| 31 | 155|DPD 18:00 / DPD GUARANTEE| 32 | 158|DPD 18:00 / DPD GUARANTEE Unfrei / ex works| 33 | 161|DPD 18:00 / DPD GUARANTEE Nachnahme / C.O.D.| 34 | 164|DPD 18:00 / DPD GUARANTEE Austausch / exchange| 35 | 168|DPD 18:00 / DPD GUARANTEE| 36 | 171|DPD 18:00 / DPD GUARANTEE Unfrei / ex works| 37 | 174|DPD 18:00 / DPD GUARANTEE Nachnahme / C.O.D.| 38 | 179|DPD 10:00| 39 | 185|DPD 10:00 Unfrei / ex works| 40 | 191|DPD 10:00 Nachnahme / C.O.D.| 41 | 197|DPD 10:00 Austausch / exchange| 42 | 203|DPD 10:00| 43 | 209|DPD 10:00 Unfrei / ex works| 44 | 215|DPD 10:00 Nachnahme / C.O.D.| 45 | 225|DPD 12:00| 46 | 228|DPD 12:00 Samstag / saturday| 47 | 231|DPD 12:00 Unfrei / ex works| 48 | 234|DPD 12:00 Samstag / saturday Unfrei / ex works| 49 | 237|DPD 12:00 Nachnahme / C.O.D.| 50 | 240|DPD 12:00 Samstag / saturday Nachnahme / C.O.D.| 51 | 243|DPD 12:00 Austausch / exchange| 52 | 249|DPD 12:00| 53 | 252|DPD 12:00 Samstag / saturday| 54 | 255|DPD 12:00 Unfrei / ex works| 55 | 258|DPD 12:00 Samstag / saturday Unfrei / ex works| 56 | 261|DPD 12:00 Nachnahme / C.O.D.| 57 | 264|DPD 12:00 Samstag / saturday Nachnahme / C.O.D.| 58 | 270|DPD 12:00 Samstag Austausch Gefahrgut / Saturday Swap Hazardous Goods| 59 | 271|same day| 60 | 294|DPD Mail| 61 | 302|DPD EXPRESS| 62 | 325|EXPRESS ECONOMY| 63 | 326|EXPRESS ECONOMY| 64 | 329|Nachnahme / C.O.D.| 65 | 330|Nachnahme / C.O.D.| 66 | 335|Nachnahme / C.O.D.| 67 | 336|Nachnahme / C.O.D.| 68 | 337|DPD PaketShop Zustellung / Parcel Shop Delivery| 69 | 338|DPD PaketShop Zustellung / Parcel Shop Delivery| 70 | 341|DPD PaketShop Zustellung Nachnahme / Parcel Shop Delivery C.O.D.| 71 | 342|DPD PaketShop Zustellung Nachnahme / Parcel Shop Delivery C.O.D.| 72 | 350|DPD 8:30| 73 | 351|DPD 8:30 Unfrei / ex works| 74 | 352|DPD 8:30 Nachnahme / C.O.D.| 75 | 353|DPD 8:30 Austausch / exchange| 76 | 354|DPD 8:30| 77 | 355|DPD 8:30| 78 | 356|DPD 8:30 Unfrei / ex works| 79 | 357|DPD 8:30 Nachnahme / C.O.D.| 80 | 358|DPD 8:30 Austausch / exchange| 81 | 361|Unfrei / ex works| 82 | 362|Unfrei / ex works| 83 | 365|Reifen / Tyre| 84 | 366|Reifen / Tyre B2C| 85 | 367|Reifen / Tyre Nachnahme / C.O.D.| 86 | 810|Service Werktag 09:00 Uhr| 87 | 811|Service Werktag 12:00 Uhr| 88 | 812|Service Werktag 17:00 Uhr| 89 | 813|Service Werktag Zeitfenster| 90 | 814|Service Samstag 09:00 Uhr| 91 | 815|Service Samstag 12:00 Uhr| 92 | 816|Service Fixtermin| 93 | 817|Privatpaket| 94 | 820|Samstag / saturday| 95 | 821|Samstag / saturday| 96 | 822|Samstag / saturday Nachnahme / C.O.D.| 97 | 823|Samstag / saturday Nachnahme / C.O.D.| 98 | 827|Austausch / exchange| 99 | 828|Austausch / exchange| 100 | 829|Samstag / saturday| 101 | 830|Samstag / saturday| 102 | 831|Samstag / saturday Nachnahme / C.O.D.| 103 | 832|Samstag / saturday Nachnahme / C.O.D.| 104 | 833|Samstag / saturday Austausch / exchange| 105 | 834|Samstag / saturday Austausch / exchange| 106 | 835|Austausch / exchange Nachnahme / C.O.D.| 107 | 836|Austausch / exchange Nachnahme / C.O.D.| 108 | 837|Samstag / saturday Austausch / exchange Nachnahme / C.O.D.| 109 | 838|Samstag / saturday Austausch / exchange Nachnahme / C.O.D.| 110 | 839|Abendzustellung / Evening Delivery| 111 | 840|Abendzustellung / Evening Delivery| 112 | 841|Abendzustellung Nachnahme / Evening Delivery C.O.D.| 113 | 842|Abendzustellung Nachnahme / Evening Delivery C.O.D.| 114 | 844|Consumer Home| 115 | 845|Consumer Home| 116 | 846|Consumer Home & Shop| 117 | 847|Consumer Home & Shop| 118 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/SERVICEINFO.EE: -------------------------------------------------------------------------------- 1 | #Filename: SERVICEINFO.EE 2 | #Version: 20110905 3 | #Expiration: 20120101 4 | #Hash: d8d55ca2e78b6aad2ecb2a4169dd58b108007c6e 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20110905.txt 6 | #Fields: ServiceCode|ServiceFieldInfo| 7 | #Key: ServiceCode| 8 | 102|hazardous goods| 9 | 103|saturday| 10 | 105|ex works| 11 | 106|ex works / hazardous goods| 12 | 109|Lunapakk (COD)| 13 | 110|Lunapakk (COD) / hazardous goods| 14 | 113|exchange| 15 | 117|Collection-upon-Delivery| 16 | 121|hazardous goods| 17 | 124|ex works| 18 | 125|ex works / hazardous goods| 19 | 128|Lunapakk (COD)| 20 | 129|Lunapakk (COD) / hazardous goods| 21 | 132|exchange| 22 | 137|saturday| 23 | 138|ex works| 24 | 140|Lunapakk (COD)| 25 | 142|exchange| 26 | 144|Collection-upon-Delivery| 27 | 148|ex works| 28 | 150|Lunapakk (COD)| 29 | 152|exchange| 30 | 154|DPD PARCELLetter| 31 | 155|DPD GUARANTEE| 32 | 158|DPD GUARANTEE ex works| 33 | 161|DPD GUARANTEE Lunapakk (COD)| 34 | 164|DPD GUARANTEE exchange| 35 | 168|DPD GUARANTEE| 36 | 171|DPD GUARANTEE ex works| 37 | 174|DPD GUARANTEE Lunapakk (COD)| 38 | 179|DPD 10:00| 39 | 185|DPD 10:00 ex works| 40 | 191|DPD 10:00 Lunapakk (COD)| 41 | 197|DPD 10:00 exchange| 42 | 203|DPD 10:00| 43 | 209|DPD 10:00 ex works| 44 | 215|DPD 10:00 Lunapakk (COD)| 45 | 225|DPD 12:00| 46 | 228|DPD 12:00 saturday| 47 | 231|DPD 12:00 ex works| 48 | 234|DPD 12:00 saturday / ex works| 49 | 237|DPD 12:00 Lunapakk (COD)| 50 | 240|DPD 12:00 saturday / Lunapakk (COD)| 51 | 243|DPD 12:00 exchange| 52 | 249|DPD 12:00| 53 | 252|DPD 12:00 saturday| 54 | 255|DPD 12:00 ex works| 55 | 258|DPD 12:00 saturday / ex works| 56 | 261|DPD 12:00 Lunapakk (COD)| 57 | 264|DPD 12:00 saturday / Lunapakk (COD)| 58 | 271|same day| 59 | 294|DPD Mail| 60 | 302|DPD EXPRESS| 61 | 325|EXPRESS ECONOMY| 62 | 326|EXPRESS ECONOMY| 63 | 329|Lunapakk (COD)| 64 | 330|Lunapakk (COD)| 65 | 335|Lunapakk (COD)| 66 | 336|Lunapakk (COD)| 67 | 350|DPD 8:30| 68 | 351|DPD 8:30 ex works| 69 | 352|DPD 8:30 Lunapakk (COD)| 70 | 353|DPD 8:30 exchange| 71 | 354|DPD 8:30| 72 | 355|DPD 8:30| 73 | 356|DPD 8:30 ex works| 74 | 357|DPD 8:30 Lunapakk (COD)| 75 | 358|DPD 8:30 exchange| 76 | 820|saturday| 77 | 821|saturday| 78 | 822|saturday / Lunapakk (COD)| 79 | 823|saturday / Lunapakk (COD)| 80 | 827|exchange| 81 | 828|exchange| 82 | 829|saturday| 83 | 830|saturday| 84 | 831|saturday / Lunapakk (COD)| 85 | 832|saturday / Lunapakk (COD)| 86 | 833|saturday / exchange| 87 | 834|saturday / exchange| 88 | 835|exchange / Lunapakk (COD)| 89 | 836|exchange / Lunapakk (COD)| 90 | 837|saturday / exchange / Lunapakk (COD)| 91 | 838|saturday / exchange / Lunapakk (COD)| 92 | 840|1 day transit time| 93 | 841|2 days transit time| 94 | 842|3 days transit time| 95 | 843|4 days transit time| 96 | 844|5 days transit time| 97 | 845|1 day transit time / Lunapakk (COD)| 98 | 846|2 days transit time / Lunapakk (COD)| 99 | 847|3 days transit time / Lunapakk (COD)| 100 | 848|4 days transit time / Lunapakk (COD)| 101 | 849|5 days transit time / Lunapakk (COD)| 102 | -------------------------------------------------------------------------------- /pyshipping/carriers/dpd/georoutetables/SERVICEINFO.EN: -------------------------------------------------------------------------------- 1 | #Filename: SERVICEINFO.EN 2 | #Version: 20140505 3 | #Expiration: 20140831 4 | #Hash: 85037002df19b84563bcfdbc1153cd85ff629e9f 5 | #Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt 6 | #Fields: ServiceCode|ServiceFieldInfo| 7 | #Key: ServiceCode| 8 | 102|hazardous goods| 9 | 103|saturday| 10 | 105|ex works| 11 | 106|ex works / hazardous goods| 12 | 109|C.O.D.| 13 | 110|C.O.D. / hazardous goods| 14 | 113|exchange| 15 | 117|Collection-upon-Delivery| 16 | 121|hazardous goods| 17 | 124|ex works| 18 | 125|ex works / hazardous goods| 19 | 128|C.O.D.| 20 | 129|C.O.D. / hazardous goods| 21 | 132|exchange| 22 | 137|saturday| 23 | 138|ex works| 24 | 140|C.O.D.| 25 | 142|exchange| 26 | 144|Collection-upon-Delivery| 27 | 148|ex works| 28 | 150|C.O.D.| 29 | 152|exchange| 30 | 154|DPD PARCELLetter| 31 | 155|DPD GUARANTEE| 32 | 158|DPD GUARANTEE ex works| 33 | 161|DPD GUARANTEE C.O.D.| 34 | 164|DPD GUARANTEE exchange| 35 | 168|DPD GUARANTEE| 36 | 171|DPD GUARANTEE ex works| 37 | 174|DPD GUARANTEE C.O.D.| 38 | 179|DPD 10:00| 39 | 185|DPD 10:00 ex works| 40 | 191|DPD 10:00 C.O.D.| 41 | 197|DPD 10:00 exchange| 42 | 203|DPD 10:00| 43 | 209|DPD 10:00 ex works| 44 | 215|DPD 10:00 C.O.D.| 45 | 225|DPD 12:00| 46 | 228|DPD 12:00 saturday| 47 | 231|DPD 12:00 ex works| 48 | 234|DPD 12:00 saturday / ex works| 49 | 237|DPD 12:00 C.O.D.| 50 | 240|DPD 12:00 saturday / C.O.D.| 51 | 243|DPD 12:00 exchange| 52 | 249|DPD 12:00| 53 | 252|DPD 12:00 saturday| 54 | 255|DPD 12:00 ex works| 55 | 258|DPD 12:00 saturday / ex works| 56 | 261|DPD 12:00 C.O.D.| 57 | 264|DPD 12:00 saturday / C.O.D.| 58 | 270|DPD 12:00 Saturday Swap Hazardous Goods| 59 | 271|same day| 60 | 294|DPD Mail| 61 | 302|DPD EXPRESS| 62 | 325|EXPRESS ECONOMY| 63 | 326|EXPRESS ECONOMY| 64 | 329|C.O.D.| 65 | 330|C.O.D.| 66 | 335|C.O.D.| 67 | 336|C.O.D.| 68 | 337|Parcel Shop Delivery| 69 | 338|Parcel Shop Delivery| 70 | 341|Parcel Shop Delivery C.O.D.| 71 | 342|Parcel Shop Delivery C.O.D.| 72 | 350|DPD 8:30| 73 | 351|DPD 8:30 ex works| 74 | 352|DPD 8:30 C.O.D.| 75 | 353|DPD 8:30 exchange| 76 | 354|DPD 8:30| 77 | 355|DPD 8:30| 78 | 356|DPD 8:30 ex works| 79 | 357|DPD 8:30 C.O.D.| 80 | 358|DPD 8:30 exchange| 81 | 361|ex works| 82 | 362|ex works| 83 | 365|Tyre| 84 | 366|Tyre B2C| 85 | 367|Tyre C.O.D.| 86 | 820|saturday| 87 | 821|saturday| 88 | 822|saturday / C.O.D.| 89 | 823|saturday / C.O.D.| 90 | 827|exchange| 91 | 828|exchange| 92 | 829|saturday| 93 | 830|saturday| 94 | 831|saturday / C.O.D.| 95 | 832|saturday / C.O.D.| 96 | 833|saturday / exchange| 97 | 834|saturday / exchange| 98 | 835|exchange / C.O.D.| 99 | 836|exchange / C.O.D.| 100 | 837|saturday / exchange / C.O.D.| 101 | 838|saturday / exchange / C.O.D.| 102 | 839|Evening Delivery| 103 | 840|Evening Delivery| 104 | 841|Evening Delivery COD| 105 | 842|Evening Delivery COD| 106 | 843|Special Pallet| 107 | 844|Consumer Home| 108 | 845|Consumer Home| 109 | 846|Consumer Home & Shop| 110 | 847|Consumer Home & Shop| 111 | 848|4 days transit time / C.O.D.| 112 | 849|5 days transit time / C.O.D.| 113 | -------------------------------------------------------------------------------- /pyshipping/fortras/__init__.py: -------------------------------------------------------------------------------- 1 | """This module contains tools for reading and writing Fortras messages. Fortras is a EDI standard for 2 | logistics related information somewhat common in Germany. See http://de.wikipedia.org/wiki/Fortras for further 3 | enlightenment.""" 4 | 5 | # You may consider this BSD licensed. 6 | 7 | from pyshipping.fortras.bordero import ship 8 | __all__ = [ship] 9 | -------------------------------------------------------------------------------- /pyshipping/fortras/bordero.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | bordero.py - implements BORD messages. BORD is similar to IFTMIN in EDIFACT. Think of it as an work order 5 | to a freight forwarder. 6 | 7 | This implementation is specific for usage between HUDORA GmbH and Mäuler but could be used as the basis for a 8 | more generic implementation. 9 | 10 | Created by Lars Ronge and Maximillian Dornseif on 2006-11-06. 11 | You may consider this BSD licensed. 12 | """ 13 | 14 | import os 15 | import time 16 | 17 | 18 | #def _transcode(data): 19 | # """Decode utf-8 to latin1 (which is used by fortras).""" 20 | # return data.decode('utf-8', 'replace').encode('latin-1', 'replace') 21 | 22 | 23 | def _clip(length, data): 24 | """Clip a string to a maximum length.""" 25 | # I wonder if this can't be done with clever formatstring usage. 26 | if not isinstance(data, str): 27 | data = data.decode('latin-1') 28 | if len(data) > length: 29 | return data[:length] 30 | return data 31 | 32 | # zwei führende Nullen weg !!! 33 | # übertragung: "übergepackt" 34 | # übertragung: Palettenformat 1.5, 1, 0.5 35 | # überticragung: lieferfenster 36 | # DFü: Teil- Und Komplettladungen - verkehrsartschluessel : statt L (LKW) ein X (komplettladung) 37 | # Mäuler Telefonummer 38 | # Liste Auslandssendungen 39 | 40 | #doctext = """Bordero-Kopf-Satz""" 41 | #Afelder = [ 42 | # dict(length=18, startpos=0, endpos=19, name='borderonr', fieldclass=IntegerFieldZeropadded), 43 | # dict(length=8, startpos=19, endpos=27, name='datum', fieldclass=DateFieldReverse, 44 | # default=datetime.date.today()), 45 | # dict(length=1, startpos=27, endpos=28, name='versandweg', fieldclass=RightAdjustedField, 46 | # default='L', choices=('L', 'X')), 47 | # dict(length=2, startpos=28, endpos=30, name='fahrzeugnummer', fieldclass=IntegerFieldZeropadded), 48 | # dict(length=10, startpos=34, endpos=44, name='versenderid', fieldclass=RightAdjustedField, 49 | # default='L515'), 50 | # dict(length=13, startpos=44, endpos=57, name='frachtfuehrer', fieldclass=RightAdjustedField, 51 | # default='Maeuler'), 52 | # dict(length=9, startpos=53, endpos=63, name='plz', fieldclass=RightAdjustedField, 53 | # default='42897'), 54 | # dict(length=12, startpos=63, endpos=76, name='ort', fieldclass=RightAdjustedField, 55 | # default='Remscheid'), 56 | # dict(length=15, startpos=76, endpos=91, name='ladeeinheit1', fieldclass=RightAdjustedField), 57 | # dict(length=15, startpos=91, endpos=106, name='ladeeinheit1', fieldclass=RightAdjustedField), 58 | # dict(length=10, startpos=106, endpos=117, name='plombe1', fieldclass=RightAdjustedField), 59 | # dict(length=10, startpos=117, endpos=127, name='plombe1', fieldclass=RightAdjustedField), 60 | # dict(length=10, startpos=117, endpos=127, name='release', fieldclass=FixedField, default='6'), 61 | #] 62 | # Verkehrsartschlüssel (Satzart ‘A’, Position 031) 63 | # B = Bahn 64 | # E = Bordero für den eigenen Nahverkehr 65 | # H = VP an ZKP 66 | # K = Kombi 67 | # L = LKW 68 | # P = Packmittel-Clearing 69 | # W = Weiterleitungsbordero 70 | # X = Teilladungs-/Ladungspartie 71 | # Y = Bordero ZKP an EB ohne Weiterverladung 72 | # Z = ZKP an EP 73 | # C = Codis 74 | # N = Night Star Express 75 | # S = SystemPlus international 76 | # T = Teppichkurier 77 | 78 | 79 | #doctext="""Versender-Adress-Satz - Teil 1""" 80 | #Bfelder = [ 81 | # dict(length=35, startpos= 0 , endpos= 35, name='versender_name1', default='HUDORA GmbH'), 82 | # dict(length=35, startpos= 35, endpos= 70, name='versender_name2'), 83 | # dict(length=35, startpos= 70, endpos=105, name='versender_strasse', default='Jägerwald 13'), 84 | # dict(length= 3, startpos=105, endpos=107, name='versender_land', default='DE'), 85 | # dict(length= 9, startpos=107, endpos=116, name='versender_plz', default='42897'), 86 | # dict(length= 3, startpos=116, endpos=119, name='frei', fieldclass=FixedField, default=' '), 87 | # dict(length= 3, startpos=119, endpos=122, name='codis_abholstelle', fieldclass=FixedField, default=' '), 88 | # dict(length= 1, startpos=122, endpos=123, name='codis_laufkennzeichen', 89 | # fieldclass=FixedField, default=' '), 90 | #] 91 | # 92 | #doctext="""Versender-Adress-Satz - Teil 2""" 93 | #Cfelder = [ 94 | # dict(length=35, startpos= 0, endpos= 35, name='versenderort'), 95 | # dict(length= 3, startpos= 35, endpos= 38, name='versender_berechnungsland', fieldclass=FixedField, 96 | # default=' '), 97 | # dict(length= 9, startpos= 38, endpos= 48, name='versender_berechnugnsplz', fieldclass=FixedField, 98 | # default=' '), 99 | # dict(length=35, startpos= 48, endpos= 83, name='versender_berechnungsort', fieldclass=FixedField, 100 | # default=' '), 101 | # dict(length=17, startpos= 83, endpos=100, name='versender_kundennummer', fieldclass=RightAdjustedField, 102 | # default='11515'), 103 | # dict(length= 9, startpos=100, endpos=109, name='warenwert', fieldclass=DecimalFieldWithoutDot, precision=2), 104 | # dict(length= 3, startpos=109, endpos=112, name='waehrung', default='EUR'), 105 | # dict(length=13, startpos=112, endpos=123, name='frei', fieldclass=FixedField, default=' '), 106 | #] 107 | # 108 | #doctext = """Empfänger-Adress-Satz - Teil 1""" 109 | #Dfelder = [ 110 | # dict(length=35, startpos= 0, endpos= 35, name='name1'), 111 | # dict(length=35, startpos= 35, endpos= 70, name='name2'), 112 | # dict(length=35, startpos= 70, endpos=105, name='stadtteil'), 113 | # dict(length=19, startpos=105, endpos=123, name='frei'), 114 | #] 115 | # 116 | #doctext = """Empfänger-Adress-Satz - Teil 2""" 117 | #Efelder = [ 118 | # dict(length=25, startpos= 0, endpos= 35, name='strasse'), 119 | # dict(length= 3, startpos= 35, endpos= 38, name='land'), 120 | # dict(length= 9, startpos= 38, endpos= 47, name='plz'), 121 | # dict(length=35, startpos= 47, endpos= 81, name='Empfängerort muss'), 122 | # dict(length= 3, startpos= 81, endpos= 84, name='Zustellbezirk Empfänger'), 123 | # dict(length=10, startpos= 84, endpos= 94, name='Matchcode Empfänger- Nachname'), 124 | # dict(length=17, startpos= 94, endpos=111, name='Kunden-Nr. Empfänger'), 125 | # dict(length=10, startpos=111, endpos=121, name='Original-ID des VP beim Empfangspartner*'), 126 | # dict(length= 2, startpos=123, endpos=123, name='Frei'), 127 | #] 128 | # 129 | #dictext="""Sendungs-Positions-Satz (je Sendungsteil; mehrfach möglich).""" 130 | #Ffelder = [ 131 | # dict(kength= 4, startpos= 0, endpos= 4, name='packstueck_anzahl'), 132 | # dict(kength= 2, startpos= 4, endpos= 6, name='verpackungsart'), # ??? 133 | # dict(kength= 4, startpos= 6, endpos= 10, name='packstueck_anzahl_auf_paletten', fieldclass=FixedField, 134 | # default=' '), 135 | # dict(kength= 2, startpos= 10, endpos= 12, name='verpackungsart_auf_paletten', fieldclass=FixedField, 136 | # default=' '), 137 | # dict(kength=20, startpos= 10, endpos= 30, name='wareninhalt'), 138 | # dict(kength=20, startpos= 30, endpos= 50, name='zeichen', 139 | # doc="Zeichen der Sendung beim Versender"), 140 | # dict(kength= 5, startpos= 50, endpos= 55, name='gewicht', fieldclass=IntegerField, 141 | # doc="Bruttogewicht in KG."), 142 | # dict(kength= 5, startpos= 55, endpos= 60, name='gewicht_frachtpflichtig', fieldclass=IntegerField, 143 | # doc="In der Regel max(gewicht, N), wobei N=150 oder 200"), 144 | # # Die Satzart ‘F’ darf nicht mit einem 2. Sendungsteil versehen werden, 145 | # # wenn die Satzarten ‘G’ bzw. ‚Y‘ und ‚Z‘ (Gefahrgut) oder Satzart ‘H’ (Packstück-Nr.) folgen! 146 | # dict(kength=68, startpos= 60, endpos=123, name='frei'), 147 | #] 148 | # Verpackungsartschlüssel (Satzart ‘F’, Positionen 009/010, 015/016, 071/072 und 077/078) 149 | # Schlüssel Art 150 | # AB Auf Bohlen 151 | # AD AD-Bahnbehälter 152 | # BD BD-Bahnbehälter 153 | # BE Beutel 154 | # BL Ballen 155 | # BU Bund 156 | # CC Collico 157 | # CO SystemPlus Collo 158 | # CD CD-Bahnbehälter 159 | # CP Chep-Palette 160 | # DO Dose 161 | # DR Drum 162 | # EI Eimer 163 | # EB Einweg-Behälter 164 | # EP Einweg-Palette 165 | # FA Fass 166 | # FK Faltkiste 167 | # FL Flasche 168 | # FP DB Euro-Flachpalette 169 | # GE Gebinde 170 | # GP Gitterboxpalette 171 | # GS Gestell 172 | # HC Haus-Haus-Corlette 173 | # HO Hobbock 174 | # HP Halbpalette 175 | # KA Kanne 176 | # KB Kundeneigener Sonderbehälter 177 | # KF Korbflasche 178 | # KI Kiste 179 | # KN Kanister 180 | # KO Korb 181 | # KP Kundeneigene Sonderpalette 182 | # KS Kasten 183 | # KT Karton 184 | # PA Paket 185 | # PK Pack 186 | # RC Rollcontainer 187 | # RG Ring 188 | # RO Rolle 189 | # SA Sack 190 | # SB Spediteureigener Behälter 191 | # ST Stück 192 | # TC Tankcontainer 193 | # TR Trommel 194 | # UV Unverpackt 195 | # VG Verschlag 196 | 197 | # 'H': '002%(barcode)-35s%(foo)35s%(foo)35s%(foo)16s', 198 | # doctext = """Packstück-Nummern-Satz (je Packstück-Nr./Gruppe; mehrfach möglich)""" 199 | # 200 | # BARCODETYP_CHOICES = { 1: 'Freie, unformatierte Markierung', 201 | # 2: 'Nummer der Versandeinheit (EAN 128)', 202 | # 3: 'Nummer der Versandeinheit (EAN 128) FORTRAS', 203 | # 4: 'Paket-Nummer DPD (2/5 Interleaved)', 204 | # 5: 'Router Label-Nummer DPD (2/5 Interleaved)', 205 | # 6: 'Packstück-Nummer SystemPlus (2/5 Interleaved)', 206 | # 7: 'Router Label-Nummer SystemPlus (2/5 Interleaved)', 207 | # 8: 'IDS-Barcode (2/5 Interleaved)', 208 | # 9: 'IDS-Barcode (39)', 209 | # 10: 'IDS-Barcode (128)', 210 | # 11: 'Nummer der Versandeinheit (Philips)', 211 | # 12: 'Wechselbehälter-Barcode (2/5 Interleaved)', 212 | # 13: 'DPD Container-Nummer (2/5 Interleaved)', 213 | # } 214 | # Hfelder = [ 215 | # dict(length= 3, startpos= 0, endpos= 3, name='barcodeart', fieldclass=IntegerFieldZerotagged, 216 | # choices=BARCODETYP_CHOICES), 217 | # dict(length=35, startpos= 3, endpos= 38, name='barcode1'), 218 | # dict(length=35, startpos= 38, endpos= 73, name='barcode2'), 219 | # dict(length=35, startpos= 73, endpos=108, name='barcode3'), 220 | # dict(length=16, startpos=108, endpos=123, name='frei'), 221 | # ] 222 | # 223 | # # 'I': ('%(sendungsnummer)-16s%(sendungskilo)05d0000000000%(ladedm)03d %(frankatur)-2s%(frankatur)-2s %(foo)30s %(foo)30s%(foo)16s '), 224 | # doctext = """Sendungs-Gewichte (je Sendung 1x)""" 225 | # 226 | # # Frankaturschlüssel des Spediteur-Übergabescheins (Satzart ‘I’, Positionen 043/044) 227 | # # kann Bordero-Frankatur wie folgt ergeben: 228 | # FRANKATUR_CHOICES = { 0: 'ohne Berechnung', 229 | # 4: 'Frei Empfangsspediteur', 230 | # 2: 'frei Haus', 231 | # 5: 'frei Haus verzollt', 232 | # 6: 'frei Haus unverzollt ', 233 | # 7: 'unfrei', 234 | # 9: 'Nachlieferung', 235 | # } 236 | # 237 | # # Hinweistextschlüssel (Satzart ‘I’, Positionen 047/048 und 079/080 sowie Satzart ‚T‘ 238 | # # Positionen 002/003, 034/035 und 066/067) 239 | # # * Exakte Definition der Formate bei Schlüsseln mit Zusatztext: 240 | # # 1) Datum (TTMMJJJJ); Feldgröße: 8 Stellen 241 | # # 2) Uhrzeit (SSMM); Feldgröße: 4 Stellen 242 | # # Die Positionen 049 - 056 enthalten das Datum (TTMMJJJJ), Position 243 | # # 057 ist leer, die Positionen 058 - 051 enthalten die Uhrzeit (SSMM). 244 | # HINWEISTEXTTYP_CHOICES = { 1: 'Sendung bitte avisieren unter Tel.-Nr.', 245 | # 2: 'Recall-Service - Nach Zustellung Rückruf unter Tel.-Nr.', 246 | # 3: 'Achtung Zollgut, Anlage Zollversandschein', 247 | # 14: 'Termindienst! Auslieferung spätestens am (nicht Eingangstag)', 248 | # 15: 'Fixtermin! Nicht früher oder später - am', 249 | # 16: 'Termingut! Unbedingt zustellen in KW:', 250 | # 17: 'SystemPlus 10-Uhr-Service - Zustellung bis 10.00 Uhr', 251 | # 18: 'SystemPlus 12-Uhr-Service - Zustellung bis 12.00 Uhr', 252 | # 19: 'SystemPlus Next-Day-Service (24 Std.-Service)', 253 | # 20: 'SystemPlus international Express Service', 254 | # 21: 'Sendung bitte nur liegend transportieren', 255 | # 22: 'Thermogut - vorgegebenen Temperaturbereich beachten', 256 | # 23: 'Empfänger kann Regalservice verlangen', 257 | # 25: 'Samstag-Service via Night Star Express', 258 | # 50: 'Warenwertnachnahme - Quick-Nachnahme', 259 | # 51: 'Zustellung unbedingt mit Hebebühnen-LKW', 260 | # 52: 'Sendung vor Zustellung telefonisch avisieren', 261 | # 53: 'Achtung Warenwertnachnahme - nur gegen bar ausliefern', 262 | # 58: 'Warenwertnachnahme - Verrechnungsscheck', 263 | # 60: 'Sendung ohne Entladung direkt ausliefern', 264 | # 61: 'Empfindliche Ware - vorsichtig behandeln', 265 | # 70: 'Lieferscheinnummer', 266 | # 71: 'Kundenauftragsnummer', 267 | # 72: 'Abholauftragsreferenz', 268 | # 73: 'Freie weitere Kundenreferenz', 269 | # 74: 'Retourenreferenz', 270 | # } 271 | # 272 | # 273 | # # Hinweistextschlüssel der Auftragsart (Satzart ‘I’, Position 127) 274 | # # 'I': ('%(sendungsnummer)-16s%(sendungskilo)05d0000000000%(ladedm)03d %(frankatur)-2s%(frankatur)-2s %(foo)30s %(foo)30s%(foo)16s '), 275 | # Ifelder = [ 276 | # dict(lentgth=16, startpos= 0, endpos= 16, name='Sendungs-Nr. Versandpartner****** muss'), 277 | # dict(lentgth= 5, startpos= 16, endpos= 21, name='gewicht', fieldclass=IntegerField, 278 | # doc="Sendungsgewicht in KG"), 279 | # dict(lentgth= 5, startpos= 21, endpos= 26, name='gewicht_frachtpflichtig', fieldclass=IntegerField, 280 | # doc="Frachtpflichtiges Sendungsgewicht in KG"), 281 | # dict(lentgth= 5, startpos= 26, endpos= 31, name='kubikdezimeter', fieldclass=IntegerField), 282 | # dict(lentgth= 3, startpos= 0, endpos=123, name='lademeter', fieldclass=DecimalFieldWithoutDot, 283 | # precision=1), 284 | # dict(lentgth= 2, startpos= 0, endpos=123, name='zusaetzliche_ladehilfsmittel', fieldclass=IntegerField), 285 | # dict(lentgth= 2, startpos= 0, endpos=123, name='verpackungsart_zusaetzliche_ladehilfsmittel'), # 2 041 - 042 286 | # dict(lentgth= 2, startpos= 0, endpos=123, name='frankatur_fpediteur-Über- gabeschein* muss', choices=FRANKATUR_CHOICES), # 2 043 - 044 287 | # dict(lentgth= 2, startpos= 0, endpos=123, name='Frankatur Bordero* muss', choices=FRANKATUR_CHOICES), # 2 045 - 046 288 | # dict(lentgth= 2, startpos= 0, endpos=123, name='Hinweistextschlüssel 1** kann'), # 2 047 - 048 289 | # dict(lentgth=30, startpos= 0, endpos=123, name='Hinweiszusatztext 1 kann'), # 30 049 - 078 290 | # dict(lentgth= 2, startpos= 0, endpos=123, name='Hinweistextschlüssel 2** kann'), # 2 079 - 080 291 | # dict(lentgth=30, startpos= 0, endpos=123, name='Hinweiszusatztext 2 kann'), # 30 081 - 110 292 | # dict(lentgth=16, startpos= 0, endpos=123, name='Sendungs-Nr. Empfangspartner*** kann'), # 16 111 - 126 293 | # dict(lentgth= 1, startpos=121, endpos=122, name='auftragsart**** kann'), # 1 127 - 127 294 | # dict(lentgth= 1, startpos=122, endpos=123, name='lieferscheindaten_folgen', fieldclass=FixedField, 295 | # default=' '), 296 | # ] 297 | #** siehe beigefügter Hinweistextschlüssel 298 | #*** bei Nachlieferungen (Original-Sendungs-Nr. des EP) bzw. bei Ersterfassung von über- 299 | #zähligen Sendungen (vorläufige Sendungs-Nr. des EP) 300 | #**** siehe beigefügter Schlüsseltabelle für Auftragsarten 301 | 302 | 303 | # 'L': ('%(sendungen)05d%(packstuecke)05d%(bruttogewicht)05d' 304 | # + '%(kostensteuerplichtig)09d%(kostensteuerfrei)09d000000000%(kostenzoll)09d' 305 | # + '%(eust)09d000%(gitterboxen)03d%(europaletten)03d000000000000' 306 | # + '%(sonstigeladehilfsmittel)03d000N%(foo)36s'), 307 | # (Muss/Kann) davon dezimal Position 308 | # Bordero-Summen-Satz muss 309 | # (je Bordero 1x) 310 | # Satzart ‘L’ muss 1 001 - 001 311 | # Konstante ‘999’ muss 3/0 002 - 004 312 | # Gesamt-Sendungs-Anzahl muss 5/0 005 - 009 313 | # Gesamt-Packstück-Anzahl muss 5/0 010 - 014 314 | # Tatsächliches Bruttoge- 315 | # wicht gesamt in kg muss 5/0 015 - 019 316 | # Gesamt-Empf.-Kosten 317 | # steuerpflichtig muss 9/2 020 - 028 318 | # Gesamt-Empf.-Kosten 319 | # steuerfrei muss 9/2 029 - 037 320 | # Gesamt-Versendernach- 321 | # nahme muss 9/2 038 - 046 322 | # Gesamt-Zoll muss 9/2 047 - 055 323 | # Gesamt-EUSt muss 9/2 056 - 064 324 | # Anzahl SB = spediteur- 325 | # eigene Behälter muss 3/0 065 - 067 326 | # Anzahl GP = Gitterbox- 327 | # Paletten** muss 3/0 068 - 070 328 | # Anzahl FP = Euro-Flach- 329 | # Paletten** muss 3/0 071 - 073 330 | # Anzahl CC = Collico muss 3/0 074 - 076 331 | # Anzahl AD = Bahnbehälter muss 3/0 077 - 079 332 | # Anzahl BD = Bahnbehälter muss 3/0 080 - 082 333 | # Anzahl CD = Bahnbehälter muss 3/0 083 - 085 334 | # Anzahl FP = zusätzliche 335 | # Ladehilfsmittel ** muss 3/0 086 - 088 336 | # Anzahl GP = zusätzliche 337 | # Ladehilfsmittel** muss 3/0 089 - 091 338 | # Clearing-Kennzeichen (J/N)*muss 1 092 - 092 339 | # Frei 36 093 - 128 340 | # Die Summenfelder für Frachtbeträge, Kosten und Nachnahmen innerhalb dieser Satzart sind 341 | # währungsneutral, d.h. es ist lediglich eine Summierung der Werte aus der Satzart K vorzunehmen. 342 | # Eine Währungsumrechnung darf nicht erfolgen. 343 | # * J = Kosten sind für das Clearing zu berücksichtigen (wird nicht verwendet) 344 | # N = Kosten sind nicht für das Clearing zu berücksichtigen (wird nicht verwendet) 345 | # 346 | # 347 | # 'T': ('%(textschluessel1)02s%(hinweistext1)-30s' 348 | # + '%(textschluessel2)02s%(hinweistext2)-30s' 349 | # + '%(textschluessel3)02s%(hinweistext3)-30s%(foo)28s'), 350 | # Textschlüssel-Satz* kann 351 | # (je Sendung maximal 3x) 352 | # Satzart ‘T’ muss 1 001 - 001 353 | # Laufende Bordero-Position muss 3/0 002 - 004 354 | # Hinweistextschlüssel 1** kann 2 005 - 006 355 | # Hinweiszusatztext 1 kann 30 007 - 036 356 | # Hinweistextschlüssel 2** kann 2 037 - 038 357 | # Hinweiszusatztext 2 kann 30 039 - 068 358 | # Hinweistextschlüssel 3** kann 2 069 - 070 359 | # Hinweiszusatztext 3 kann 30 071 - 100 360 | # Frei 361 | # 362 | # 'J': '%(zusatztext1)-62s%(zusatztext2)-62s', 363 | # Satzart ‘J’ muss 1 001 - 001 364 | # Laufende Bordero-Position muss 3/0 002 - 004 365 | # Zusatztext 1 muss 62 005 - 066 366 | # Zusatztext 2 kann 62 067 - 128 367 | 368 | class Bordero(object): 369 | """Kapselt die Daten für ein Gefäß (LKW) - Verwendung ähnlich IFTSTAR.""" 370 | 371 | def __init__(self, empfangspartner='11515'): 372 | super(Bordero, self).__init__() 373 | self.filename = "" 374 | self.empfangspartner = empfangspartner 375 | self.lieferungen = [] 376 | self.satznummer = 0 377 | self.borderonr = None 378 | self.verladung = None 379 | self.generated_output = '' 380 | # definitions of records 381 | self.satzarten = {'A': (u'%(borderonr)018d%(datum)-8s%(versandweg)s %(empfangspartner)-10s %(frachtfuehrer)' 382 | + '-13s%(plz)-9s%(ort)-12s%(foo)-49s6'), 383 | 'B': u'%(name1)-35s%(name2)-35s%(strasse)-35s%(lkz)-3s%(plz)-9s ', 384 | 'C': u'%(ort)-35s%(foo)-3s%(foo)9s%(foo)35s%(kdnnr)17s%(wert)09fEUR%(foo)-13s', 385 | 'D': u'%(name1)-35s%(name2)-35s%(foo)-35s%(foo)-19s', 386 | 'E': (u'%(strasse)-35s%(lkz)-3s%(plz)-9s%(ort)-35s%(foo)3s%(matchcode)-10s' 387 | + '%(kdnnr)17s%(foo)10s%(foo)2s'), 388 | 'F': (u'%(anzahlpackstuecke)04d%(verpackungsart)2s0000 %(wareninhalt)-20s' 389 | + '%(zeichennr)-20s%(sendungskilo)05d00000%(foo)-62s'), 390 | 'H': '002%(barcode)-35s%(foo)35s%(foo)35s%(foo)16s', 391 | 'I': (u'%(sendungsnummer)-16s%(sendungskilo)05d0000000000%(ladedm)03d ' 392 | + '%(frankatur)-2s%(frankatur)-2s %(foo)30s %(foo)30s%(foo)16s '), 393 | 'L': (u'%(sendungen)05d%(packstuecke)05d%(bruttogewicht)05d' 394 | + '%(kostensteuerplichtig)09d%(kostensteuerfrei)09d000000000%(kostenzoll)09d' 395 | + '%(eust)09d000%(gitterboxen)03d%(europaletten)03d000000000000' 396 | + '%(sonstigeladehilfsmittel)03d000N%(foo)36s'), 397 | 'T': (u'%(textschluessel1)02s%(hinweistext1)-30s' 398 | + '%(textschluessel2)02s%(hinweistext2)-30s' 399 | + '%(textschluessel3)02s%(hinweistext3)-30s%(foo)28s'), 400 | 'J': u'%(zusatztext1)-62s%(zusatztext2)-62s', 401 | } 402 | 403 | def add_lieferung(self, lieferung): 404 | """Adds a lieferung to our Bordero.""" 405 | if self.generated_output: 406 | raise RuntimeError('tried to add data to an already exported Bordero.') 407 | self.lieferungen.append(lieferung) 408 | 409 | def generate_satz(self, satzart, data): 410 | """Helper function to generate output for a Record.""" 411 | for key in data.keys(): 412 | if isinstance(data[key], str): 413 | data[key] = unicode(data[key], 'ascii', errors='ignore') 414 | ret = ((u'%s%03d' % (satzart, self.satznummer)) + 415 | (self.satzarten[satzart] % data)) 416 | if len(ret) != 128: 417 | raise RuntimeError('bordero Satz %r kaputt! (len=%r) %r %r' % (satzart, len(ret), ret, data)) 418 | return ret 419 | 420 | def generate_kopfsatz_a(self, verladung=None): 421 | """Generates bodero record A - header.""" 422 | # Bordro nummer - Fortlaufend? 423 | # Verkehrsart - immer LKW? 424 | # IDNr des Verrsandpartyners beim Empfangspartern? L515 425 | # Frachtführername - Mäuler? 426 | # Ladeeinheitnr - 1 & 2? frei 427 | # Plombennummer ? frei 428 | if not self.borderonr: 429 | raise RuntimeError('No bordero nr set.') 430 | data = {'empfangspartner': self.empfangspartner, 'frachtfuehrer': 'Maeuler', 'plz': '42897', 431 | 'ort': 'Remscheid', 'versandweg': 'L', 'foo': '', 432 | 'datum': time.strftime('%d%m%Y'), 433 | 'borderonr': self.borderonr} 434 | if verladung: 435 | if verladung.spedition == 'Direktfahrt': 436 | data['versandweg'] = 'X' 437 | return self.generate_satz('A', data) 438 | 439 | def generate_versendersatz_b(self, lieferung): 440 | """Generates bodero record B - first half of sender.""" 441 | data = {'name1': 'HUDORA GmbH', 'name2': '', 'strasse': u'Jägerwald 13', 'lkz': 'DE', 442 | 'plz': '42897'} 443 | return self.generate_satz('B', data) 444 | 445 | def generate_versendersatz_c(self, lieferung): 446 | """Generates bodero record C - second half of sender.""" 447 | data = {'ort': 'Remscheid', 'foo': ' ', 'kdnnr': self.empfangspartner, 'wert': 0} 448 | return self.generate_satz('C', data) 449 | 450 | def generate_empfaengersatz_d(self, lieferung): 451 | """Generates bodero record D - first half of recipient.""" 452 | data = {'name1': _clip(35, lieferung.name1), 'name2': _clip(35, lieferung.name2), 'foo': ' '} 453 | return self.generate_satz('D', data) 454 | 455 | def generate_empfaengersatz_e(self, lieferung): 456 | """Generates bodero record E - second half of recipient.""" 457 | data = {'strasse': _clip(35, lieferung.adresse), 'lkz': _clip(3, lieferung.land), 458 | 'plz': _clip(9, lieferung.plz), 'ort': _clip(35, lieferung.ort), 'kdnnr': lieferung.kundennummer, 459 | 'matchcode': _clip(10, (lieferung.name1 + lieferung.ort).replace(' ', '')), 'foo': ' '} 460 | return self.generate_satz('E', data) 461 | 462 | def generate_sendungspossatz_f(self, lieferung): 463 | """Generates bodero record F.""" 464 | # Packstück Anzahl? Pakete? Paletten? NVEs! 465 | # Fuer weitere Paletten typen neuen Satz 466 | # Tatsächliches Gewicht einpflegen 467 | data = {'anzahlpackstuecke': len(lieferung.packstuecke), 468 | 'verpackungsart': _clip(2, 'FP'), 469 | 'sendungskilo': int(lieferung.gewicht / 1000), 470 | 'wareninhalt': _clip(20, 'HUDORA Sportartikel'), 471 | 'zeichennr': _clip(20, '%s/%s' % (lieferung.lieferscheinnummer, lieferung.id)), 472 | 'foo': ''} 473 | return self.generate_satz('F', data) 474 | 475 | def generate_packstuecksatz(self, lieferung, packstueck): 476 | """Generates bodero record H.""" 477 | barcode = packstueck.nve 478 | if not barcode.startswith('00'): 479 | barcode = '00' + barcode 480 | data = {'barcode': barcode, 481 | 'foo': ' '} 482 | return self.generate_satz('H', data) 483 | 484 | def generate_sendungsinfosatz_i(self, lieferung): 485 | """Generates bodero record I.""" 486 | # Sendungsnummer - eindeutig! 487 | data = {'sendungsnummer': _clip(16, "%016s" % lieferung.id), 488 | 'ladedm': 0, 489 | 'frankatur': '02', # frei Haus 490 | 'foo': ' '} 491 | data['sendungskilo'] = int(lieferung.gewicht / 1000) 492 | return self.generate_satz('I', data) 493 | 494 | def generate_textsatz_t(self, lieferung, schluesselliste): 495 | """Generates bodero record(s) T - text info.""" 496 | satz = [] 497 | ret = [] 498 | for schluessel, text in schluesselliste: 499 | satz.append((schluessel, text)) 500 | if len(satz) == 3: 501 | data = {'textschluessel1': '%02d' % int(satz[0][0]), 'hinweistext1': _clip(30, satz[0][1]), 502 | 'textschluessel2': '%02d' % int(satz[1][0]), 'hinweistext2': _clip(30, satz[1][1]), 503 | 'textschluessel3': '%02d' % int(satz[2][0]), 'hinweistext3': _clip(30, satz[2][1]), 504 | 'foo': ' '} 505 | satz = [] 506 | ret.append(self.generate_satz('T', data)) 507 | if len(satz) > 0: 508 | data = {'textschluessel1': '%02s' % int(satz[0][0]), 'hinweistext1': _clip(30, satz[0][1]), 509 | 'textschluessel2': ' ', 'hinweistext2': '', 510 | 'textschluessel3': ' ', 'hinweistext3': '', 511 | 'foo': ' '} 512 | if len(satz) > 1: 513 | data['textschluessel2'] = '%02d' % int(satz[1][0]) 514 | data['hinweistext2'] = _clip(30, satz[1][1]) 515 | ret.append(self.generate_satz('T', data)) 516 | return '\n'.join(ret) 517 | 518 | def generate_textsaetze(self, lieferung): 519 | """Generate as many T records as needed.""" 520 | schluesselliste = [] 521 | if lieferung.avisieren_unter: 522 | # 52 = Sendung vor Zustellung telefonisch avisieren 523 | # 01 = Sendung bitte avisieren unter Tel.-Nr.: ...(Zusatztext) 524 | schluesselliste.append(('01', _clip(30, lieferung.avisieren_unter))) 525 | if lieferung.fixtermin: 526 | # 15 = Fixtermin! Nicht früher oder später - am: ... (Zusatztext)* 527 | datum = lieferung.fixtermin.strftime('%d%m%Y') 528 | zeit = lieferung.fixtermin.strftime('%H:%M') 529 | if zeit == '00:00': 530 | zeit = ' ' 531 | timestamp = '%8s %5s' % (datum, zeit) 532 | schluesselliste.append(('15', _clip(30, timestamp))) 533 | if lieferung.hebebuehne: 534 | # 51 = Zustellung unbedingt mit Hebebühnen-LKW 535 | schluesselliste.append(('51', _clip(30, 'Zustellung mit Hebebühne'))) 536 | if lieferung.auftragsnummer_kunde.strip(): 537 | # 71 = Kundenauftragsnummer (Nummer im Zusatztext) 538 | schluesselliste.append(('71', _clip(30, lieferung.auftragsnummer_kunde))) 539 | # 70 = Lieferscheinnummer (Nummer im Zusatztext) 540 | schluesselliste.append(('70', _clip(30, lieferung.lieferscheinnummer))) 541 | # 73 = Freie weitere Kundenreferenz (Referenz im Zusatztext) 542 | # schluesselliste.append(('73', _clip(30, 'http://hudora.de/track/XXX'))) 543 | 544 | # 14 = Termindienst! Auslieferung spätestens am (nicht Eingangstag): (Zusatztext)* 545 | # 16 = Termingut! Unbedingt zustellen in KW: ... (Zusatztext) 546 | # 61 = Empfindliche Ware - vorsichtig behandeln 547 | return self.generate_textsatz_t(lieferung, schluesselliste) 548 | 549 | def generate_zusatztextsatz_j(self, lieferung): 550 | """Generates bodero record J - additional text info.""" 551 | data = {'zusatztext1': _clip(62, u'AuftragsNr: %s / KundenNr: %s' % 552 | (lieferung.auftragsnummer, lieferung.kundennummer)), 553 | 'zusatztext2': _clip(62, u'huLOG Code: %s' % lieferung.code), 554 | 'foo': ' '} 555 | return self.generate_satz('J', data) 556 | 557 | def generate_summensatz_l(self): 558 | """Generates bodero record L.""" 559 | # Was ist ein Packstück? => NVEs 560 | # Empfangskosten? 561 | # Was bei Einwegpaletten? 562 | # Was bei CHEP / Düsseldorfer Paletten = speditereigenebehaelter 563 | # clearing kennzeichen? 564 | data = {'sendungen': len(self.lieferungen), 565 | 'bruttogewicht': int(sum([lieferung.gewicht for lieferung in self.lieferungen]) / 1000), 566 | 'kostensteuerplichtig': 0, 567 | 'kostensteuerfrei': 0, 568 | 'kostenzoll': 0, 569 | 'eust': 0, 570 | 'gitterboxen': 0, 571 | 'europaletten': sum([len(lieferung.packstuecke) for lieferung in self.lieferungen]), 572 | 'packstuecke': sum([len(lieferung.packstuecke) for lieferung in self.lieferungen]), 573 | 'sonstigeladehilfsmittel': 0, 574 | 'foo': '', 575 | } 576 | self.satznummer = 999 577 | return self.generate_satz('L', data) 578 | 579 | def generate_lieferungssaetze(self, lieferung): 580 | """Generates all bodero records for a shipment.""" 581 | self.satznummer += 1 582 | out = [] 583 | out.append(self.generate_versendersatz_b(lieferung)) 584 | out.append(self.generate_versendersatz_c(lieferung)) 585 | out.append(self.generate_empfaengersatz_d(lieferung)) 586 | out.append(self.generate_empfaengersatz_e(lieferung)) 587 | out.append(self.generate_sendungspossatz_f(lieferung)) 588 | for packstueck in lieferung.packstuecke: 589 | out.append(self.generate_packstuecksatz(lieferung, packstueck)) 590 | out.append(self.generate_sendungsinfosatz_i(lieferung)) 591 | out.append(self.generate_textsaetze(lieferung)) 592 | out.append(self.generate_zusatztextsatz_j(lieferung)) 593 | return u'\n'.join(out) 594 | 595 | def generate_dataexport(self): 596 | """Return complete BODERO data.""" 597 | if not self.generated_output: 598 | out = [] 599 | out.append(self.generate_kopfsatz_a(self.verladung)) 600 | for lieferung in self.lieferungen: 601 | out.append(self.generate_lieferungssaetze(lieferung)) 602 | out.append(self.generate_summensatz_l()) 603 | self.generated_output = (u"@@PHBORD128 0128003500107 HUDORA1 MAEULER\n" 604 | + u'\n'.join(out) + u'\n@@PT\n') 605 | return self.generated_output 606 | 607 | 608 | def ship(verladung, empfangspartner='11515', basedir='/usr/local/maeuler/current/In/BORD/'): 609 | """Creates a BORDERO object and writes it to a file. 610 | 611 | The file lies in directory basedir containing the borderonr and a timestamp in its name. 612 | Returns the BORDERO object w/ its filename attached. 613 | """ 614 | bordero = Bordero(empfangspartner) 615 | bordero.verladung = verladung 616 | bordero.borderonr = verladung.borderonr 617 | for lieferung in verladung.lieferungen: 618 | bordero.add_lieferung(lieferung) 619 | data = bordero.generate_dataexport() # generate first to assure bordero.borderonr is set 620 | # we first create a temporary file and later rename it to it's final name 621 | basefilename = time.strftime('%Y%m%dT%H%M%S') + ('_%05d.txt' % (bordero.borderonr)) 622 | tmpfilename = os.path.join(basedir, '._' + basefilename + '_tmp') 623 | filename = os.path.join(basedir, basefilename) 624 | outfile = open(tmpfilename, 'w') 625 | outfile.write(data.encode('latin-1', 'ignore')) 626 | outfile.close() 627 | os.rename(tmpfilename, filename) 628 | for lieferung in verladung.lieferungen: 629 | lieferung.ship() 630 | lieferung.log(code=201, message="Verladung abgeschlossen / DFü an Spedition (Mäuler)") 631 | lieferung.save() 632 | verladung.ship() 633 | bordero.filename = filename 634 | return bordero 635 | 636 | 637 | def ship_lieferungen(lieferungen, empfangspartner='11515'): 638 | """Should be called when 'lieferungen' just left the building.""" 639 | bordero = Bordero(empfangspartner) 640 | for lieferung in lieferungen: 641 | bordero.add_lieferung(lieferung) 642 | data = bordero.generate_dataexport() # generate first to assure bordero.borderonr is set 643 | # we first create a temporary file and later rename it to it's finaal name 644 | filename = time.strftime('%Y%m%dT%H%M%S') + ('_%05d.txt' % (bordero.borderonr)) 645 | outfile = open(os.path.join('/usr/local/maeuler/current/In/BORD/', '._' + filename + '_tmp'), 'w') 646 | outfile.write(data) 647 | outfile.close() 648 | os.rename(os.path.join('/usr/local/maeuler/current/In/BORD/', '._' + filename + '_tmp'), 649 | os.path.join('/usr/local/maeuler/current/In/BORD/', filename)) 650 | for lieferung in lieferungen: 651 | lieferung.ship() 652 | lieferung.log(code=201, message="DFü an Mäuler") 653 | lieferung.save() 654 | return bordero 655 | -------------------------------------------------------------------------------- /pyshipping/fortras/entl.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hudora/pyShipping/089c502db5d79182dbd69f0b95c475e0eddda355/pyshipping/fortras/entl.py -------------------------------------------------------------------------------- /pyshipping/fortras/fakt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | fakt.py 5 | 6 | Created by Christian Klein on 2011-03-31. 7 | Copyright (c) 2011 HUDORA GmbH. All rights reserved. 8 | """ 9 | 10 | import csv 11 | import datetime 12 | import decimal 13 | import StringIO 14 | 15 | 16 | FIELDNAME_MAPPING = { 17 | 'Firma': 'firma', 18 | 'T-Datum': 'datum', 19 | 'Frachtbrief': 'frachtbrief', 20 | 'Versender-Ref.': 'lieferung_id', 21 | 'Abs.-Name': 'absender_name1', 22 | 'Abs.-Str.': 'absender_strasse', 23 | 'Abs.-Land': 'absender_land', 24 | 'Abs.-Plz': 'absender_plz', 25 | 'Abs.-Ort': 'absender_ort', 26 | 'Emp.-Name': 'empfaenger_name1', 27 | 'Emp.-Str.': 'empfaenger_strasse', 28 | 'Emp.-Land': 'empfaenger_land', 29 | 'Emp.-Plz': 'empfaenger_plz', 30 | 'Emp.-Ort': 'empfaenger_ort', 31 | 'Zeichen+Nr.': 'zeichennr', 32 | 'Inhalt': 'inhalt', 33 | 'T-Gewicht': 'transportgewicht', 34 | 'F-Gewicht': 'frachtpflgewicht', 35 | 'KM': 'kilometer', 36 | 'VPE': 'einheiten', 37 | 'EURO': 'euro', 38 | 'GIBO': 'gibo', 39 | 'Fracht': 'fracht', 40 | 'Maut': 'maut', 41 | 'Summe': 'summe'} 42 | 43 | 44 | def convert_to_decimal(value, factor=None): 45 | """Convert a pseudo decimal value to Decimal""" 46 | 47 | value = decimal.Decimal(value.replace(',', '.')) 48 | if factor: 49 | value *= factor 50 | return value 51 | 52 | 53 | def convert_record(record): 54 | """Convert fields in record to Python data types""" 55 | 56 | if 'datum' in record: 57 | record['datum'] = datetime.datetime.strptime(record['datum'], '%d.%m.%Y') 58 | 59 | if 'kilometer' in record: 60 | record['kilometer'] = convert_to_decimal(record['kilometer']) 61 | 62 | for key in 'fracht', 'frachteinheiten', 'maut', 'kosten': 63 | if key in record: 64 | record[key] = convert_to_decimal(record[key], factor=100) 65 | 66 | for key in 'transportgewicht', 'frachtpflgewicht': 67 | if key in record: 68 | record[key] = convert_to_decimal(record[key], factor=1000) 69 | 70 | if None in record: 71 | del record[None] 72 | 73 | return record 74 | 75 | 76 | def parse_fakt(data): 77 | """Parse BORDERO FAKT data""" 78 | 79 | if isinstance(data, basestring): 80 | data = StringIO.StringIO(data) 81 | 82 | header = data.readline() 83 | if not header.strip() == '@@PHFAKT128 FROMAT:CSV DELIMITER:;': 84 | raise ValueError('Unknown header: %r' % header) 85 | 86 | reader = csv.DictReader(data, delimiter=';') 87 | fieldnames = reader.fieldnames 88 | for index, field in enumerate(fieldnames): 89 | if field in FIELDNAME_MAPPING: 90 | del fieldnames[index] 91 | fieldnames.insert(index, FIELDNAME_MAPPING[field]) 92 | reader.fieldnames = fieldnames 93 | 94 | # rows = dict((row['zeichennr'], convert_record(row)) for row in reader) 95 | rows = [convert_record(row) for row in reader] 96 | return rows 97 | 98 | 99 | if __name__ == "__main__": 100 | import sys 101 | import pprint 102 | pprint.pprint(parse_fakt(open(sys.argv[1]).read())) 103 | -------------------------------------------------------------------------------- /pyshipping/fortras/fortras_stat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | stat.py - parse Fortras STAT messages. 5 | 6 | Created by Maximillian Dornseif on 2006-11-19. 7 | You may consider this BSD licensed. 8 | """ 9 | 10 | import re 11 | import datetime 12 | import logging 13 | 14 | 15 | class Statusmeldung(object): 16 | """Parses and represents an STAT message.""" 17 | q_record_re = (r'Q(?P.{10})(?P.)(?P.{16})' 18 | + r'(?P.{16})(?P[0-9 ]{3})(?P[0-9 ]{8})' 19 | + r'(?P