Add tracking/shipment info

master
informatic 2020-04-24 10:50:14 +02:00
parent 1f9f7f0c6c
commit fccf493f8e
8 changed files with 125 additions and 5 deletions

View File

@ -173,6 +173,11 @@ class FaceshieldRequestAdmin(ModelView):
model = self.get_one(request.args.get('id'))
return self.render('label.html', models=[model])
@flask_admin.expose('/shipmentinfo')
def tracking(self):
model = self.get_one(request.args.get('id'))
return self.render('admin/tracking.html', models=[model])
@flask_admin.expose('/import-dpd', methods=['POST', 'GET'])
def dpd_import(self):
if request.method == 'POST':

View File

@ -101,18 +101,37 @@ class FaceshieldRequest(db.Model):
return self.faceshield_full_delivered // 150
return 1
@cache.memoize(timeout=10 * 60)
def fetch_shipping_info(self):
if self.shipping_provider == 'kurjerzy':
from shipping.kurjerzy import Kurjerzy
k = Kurjerzy(current_app)
k.authenticate()
return k.shipment_info(self.shipping_id)
return [self.cache_shipment_info(k.shipment_info, i) for i in self.shipping_id.split(',')]
elif self.shipping_provider == 'xbs':
from shipping.dpd import DPD
d = DPD(current_app)
return [self.cache_shipment_info(d.shipment_info, i) for i in self.shipping_id.split(',')]
return None
def cache_shipment_info(self, fetch, id):
key = 'shipment_info_%d_%s' % (self.id, id)
res = cache.get(key)
if not res:
try:
res = fetch(id)
except Exception as exc:
res = {'error': str(exc)}
if res.get('tracking', {}).get('delivered'):
cache.set(key, res, timeout=60 * 60 * 24 * 7)
else:
cache.set(key, res, timeout=60 * 10)
return res
def refresh_shipping_info(self):
cache.delete_memoized(self.fetch_shipping_info, self)
for i in self.shipping_id.split(','):
cache.delete('shipment_info_%d_%s' % (self.id, id))
@property
def shipping_info(self):

View File

@ -1,5 +1,6 @@
alembic==1.4.2
Babel==2.8.0
beautifulsoup4==4.9.0
blinker==1.4
cairocffi==1.1.0
CairoSVG==2.4.2
@ -46,6 +47,7 @@ requests==2.23.0
requests-oauthlib==1.3.0
six==1.14.0
sortedcontainers==2.1.0
soupsieve==2.0
SQLAlchemy==1.3.15
tinycss2==1.0.2
unicodecsv==0.14.1

61
shipping/dpd.py Normal file
View File

@ -0,0 +1,61 @@
import requests
import bs4
import re
class DPD:
def __init__(self, app):
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0',
})
self.regexes = {
'booked': r'Zarejestrowano dane przesyłki, przesyłka jeszcze nienadana',
'collected': r'Przesyłka odebrana przez Kuriera',
'inDelivery': r'Wydanie przesyłki do doręczenia',
'delivered': r'Przesyłka doręczona Odbiorca: .*',
}
def authenticate(self):
pass
def shipment_info(self, id):
resp = self.session.post('https://tracktrace.dpd.com.pl/findPackage', data={
'q': id,
'typ': '1',
})
resp.raise_for_status()
soup = bs4.BeautifulSoup(resp.text, features="html5lib")
statuses = []
tracking = {
'statuses': statuses,
'booked': None,
'collected': None,
'inDelivery': None,
'delivered': None,
}
for row in soup.find(class_='table-track').find('tbody').findAll('tr'):
cols = [item.get_text(' ', strip=True).replace('\xa0', ' ') for item in row.findAll('td')]
status = {
'timestamp': '%s %s' % (cols[0], cols[1]),
'description': cols[2],
'location': cols[3],
}
for k, r in self.regexes.items():
if tracking[k] is None and re.match(r, status['description']):
tracking[k] = status['timestamp']
statuses.append(status)
return {
'numberParcel': id,
'tracking': tracking,
}
if __name__ == '__main__':
d = DPD(None)
d.authenticate()
import pprint
pprint.pprint(d.shipment_info('xxxxxxxxxx'))

View File

@ -133,7 +133,7 @@ class Kurjerzy:
model.shipping_id = result['data']['createShipment']['shipment']['id']
def shipment_info(self, id):
result = self.send_query(r'query($id: Int) { shipments(ids:[$id]) { id, labelRef, labelSource, status, tracking { statuses } } }', {
result = self.send_query(r'query($id: Int) { shipments(ids:[$id]) { labelSource, numberParcel, status, tracking { booked, collected, inDelivery, delivered, returned, statuses } } }', {
'id': id,
})

View File

@ -0,0 +1,26 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h3>Tracking info for #{{ models[0].id }}</h3>
</div>
<div class="modal-body">
{% if models[0].shipping_info %}
{% for shipment in models[0].shipping_info %}
{% if shipment.error is defined %}
<h4 class="text-danger">Tracking error occured: {{ shipment.error }}</h4>
{% else %}
<h4>{{ shipment.numberParcel }}</h4>
<table class="table table-hover">
{% for status in shipment.tracking.statuses %}
<tr><td>{{ status.timestamp }}</td><td>{{ status.location }}</td><td>{{ status.description }}</td></tr>
{% endfor %}
</table>
{% endif %}
{% endfor %}
{% else %}
<h4>No shipping information</h4>
{% endif %}
<script src="/admin/static/admin/js/bs3_modal.js?v=1.0.0"></script>
</div>
</div>

View File

@ -4,6 +4,11 @@
<a href="{{ url_for('map.index', id=row.id) }}" class="icon" title="Show on map"><span class="glyphicon glyphicon-map-marker"></span></a>
<a href="{{ url_for('faceshieldrequest.label', id=row.id) }}" class="icon" title="Render package label"><span class="glyphicon glyphicon-print"></span></a>
<a class="icon" role="button" data-toggle="popover" data-html="true" data-content="<b>Extras:</b><br />{{ row.extra or '' }}<hr><b>Remarks:</b><br />{{ row.remarks or '' }}"><span class="glyphicon glyphicon-comment"></span></a>
{% if row.shipping_id %}
<a class="icon" data-target="#fa_modal_window" title="View tracking" href="{{ url_for('faceshieldrequest.tracking', id=row.id) }}" data-toggle="modal">
<span class="fa fa-eye glyphicon glyphicon-plane"></span>
</a>
{% endif %}
{% endblock %}
{% block head_css %}{{ super() }}

View File

@ -16,10 +16,12 @@ height: 100%;
{% endblock %}
{% block content %}
{% for model in models %}
{% for shipment in model.shipping_info %}
<div class="container shipping">
<div class="inner">
<img src="{{ model.shipping_info['labelSource'] }}" alt="label {{ model.id }}" />
<img src="{{ shipment['labelSource'] }}" alt="label {{ model.id }}" />
</div>
</div>
{% endfor %}
{% endfor %}
{% endblock %}