Add tracking/shipment info
parent
1f9f7f0c6c
commit
fccf493f8e
|
@ -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':
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
|
@ -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,
|
||||
})
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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() }}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
Loading…
Reference in New Issue