kwork-poizonstore/external_api/cdek.py

212 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import http
import os
from contextlib import suppress
from time import sleep
from typing import Optional
from urllib.parse import urljoin
import requests
from django.conf import settings
from django.core.files.base import ContentFile
from store.utils import is_migration_running
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'poizonstore.settings')
class CDEKStatus:
# Принят
ACCEPTED = "ACCEPTED"
# Создан
CREATED = "CREATED"
# Принят на склад отправителя
RECEIVED_AT_SHIPMENT_WAREHOUSE = "RECEIVED_AT_SHIPMENT_WAREHOUSE"
# Выдан на отправку в г. отправителе
READY_FOR_SHIPMENT_IN_SENDER_CITY = "READY_FOR_SHIPMENT_IN_SENDER_CITY"
# Возвращен на склад отправителя
RETURNED_TO_SENDER_CITY_WAREHOUSE = "RETURNED_TO_SENDER_CITY_WAREHOUSE"
# Сдан перевозчику в г. отправителе
TAKEN_BY_TRANSPORTER_FROM_SENDER_CITY = "TAKEN_BY_TRANSPORTER_FROM_SENDER_CITY"
# Отправлен в г. транзит
SENT_TO_TRANSIT_CITY = "SENT_TO_TRANSIT_CITY"
# Встречен в г. транзите
ACCEPTED_IN_TRANSIT_CITY = "ACCEPTED_IN_TRANSIT_CITY"
# Принят на склад транзита
ACCEPTED_AT_TRANSIT_WAREHOUSE = "ACCEPTED_AT_TRANSIT_WAREHOUSE"
# Возвращен на склад транзита
RETURNED_TO_TRANSIT_WAREHOUSE = "RETURNED_TO_TRANSIT_WAREHOUSE"
# Выдан на отправку в г. транзите
READY_FOR_SHIPMENT_IN_TRANSIT_CITY = "READY_FOR_SHIPMENT_IN_TRANSIT_CITY"
# Сдан перевозчику в г. транзите
TAKEN_BY_TRANSPORTER_FROM_TRANSIT_CITY = "TAKEN_BY_TRANSPORTER_FROM_TRANSIT_CITY"
# Отправлен в г. отправитель
SENT_TO_SENDER_CITY = "SENT_TO_SENDER_CITY"
# Отправлен в г. получатель
SENT_TO_RECIPIENT_CITY = "SENT_TO_RECIPIENT_CITY"
# Встречен в г. отправителе
ACCEPTED_IN_SENDER_CITY = "ACCEPTED_IN_SENDER_CITY"
# Встречен в г. получателе
ACCEPTED_IN_RECIPIENT_CITY = "ACCEPTED_IN_RECIPIENT_CITY"
# Принят на склад доставки
ACCEPTED_AT_RECIPIENT_CITY_WAREHOUSE = "ACCEPTED_AT_RECIPIENT_CITY_WAREHOUSE"
# Принят на склад до востребования
ACCEPTED_AT_PICK_UP_POINT = "ACCEPTED_AT_PICK_UP_POINT"
# Выдан на доставку
TAKEN_BY_COURIER = "TAKEN_BY_COURIER"
# Возвращен на склад доставки
RETURNED_TO_RECIPIENT_CITY_WAREHOUSE = "RETURNED_TO_RECIPIENT_CITY_WAREHOUSE"
# Вручен
DELIVERED = "DELIVERED"
# Не вручен
NOT_DELIVERED = "NOT_DELIVERED"
# Некорректный заказ
INVALID = "INVALID"
# Таможенное оформление в стране отправления
IN_CUSTOMS_INTERNATIONAL = "IN_CUSTOMS_INTERNATIONAL"
# Отправлено в страну назначения
SHIPPED_TO_DESTINATION = "SHIPPED_TO_DESTINATION"
# Передано транзитному перевозчику
PASSED_TO_TRANSIT_CARRIER = "PASSED_TO_TRANSIT_CARRIER"
# Таможенное оформление в стране назначения
IN_CUSTOMS_LOCAL = "IN_CUSTOMS_LOCAL"
# Таможенное оформление завершено
CUSTOMS_COMPLETE = "CUSTOMS_COMPLETE"
# Заложен в постамат
POSTOMAT_POSTED = "POSTOMAT_POSTED"
# Изъят из постамата курьером
POSTOMAT_SEIZED = "POSTOMAT_SEIZED"
# Изъят из постамата клиентом
POSTOMAT_RECEIVED = "POSTOMAT_RECEIVED"
class CDEKClient:
AUTH_ENDPOINT = 'oauth/token'
ORDER_INFO_ENDPOINT = 'orders'
CALCULATOR_TARIFF_ENDPOINT = 'calculator/tariff'
CALCULATOR_TARIFF_LIST_ENDPOINT = 'calculator/tarifflist'
BARCODE_ENDPOINT = 'print/barcodes'
MAX_RETRIES = 2
def __init__(self, client_id, client_secret, grant_type='client_credentials'):
self.api_url = 'https://api.cdek.ru/v2/'
self.client_id = client_id
self.client_secret = client_secret
self.grant_type = grant_type
self.session = requests.Session()
def request(self, method, url, *args, **kwargs):
joined_url = urljoin(self.api_url, url)
request = requests.Request(method, joined_url, *args, **kwargs)
retries = 0
while retries < self.MAX_RETRIES:
retries += 1
prepared = self.session.prepare_request(request)
try:
r = self.session.send(prepared, timeout=settings.EXTERNAL_API_TIMEOUT_SEC)
except:
continue
# TODO: handle/log errors
if r.status_code == http.HTTPStatus.UNAUTHORIZED:
self.authorize()
continue
return r
def authorize(self):
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': self.grant_type
}
r = self.request('POST', self.AUTH_ENDPOINT, params=params)
if r:
data = r.json()
token = data['access_token']
self.session.headers.update({'Authorization': f'Bearer {token}'})
def get_order_info(self, im_number):
params = {
'im_number': str(im_number)
}
return self.request('GET', self.ORDER_INFO_ENDPOINT, params=params)
def create_order(self, order_data):
return self.request('POST', self.ORDER_INFO_ENDPOINT, json=order_data)
def edit_order(self, order_data):
return self.request('PATCH', self.ORDER_INFO_ENDPOINT, json=order_data)
def calculate_tariff(self, data):
return self.request('POST', self.CALCULATOR_TARIFF_ENDPOINT, json=data)
def calculate_tarifflist(self, data):
return self.request('POST', self.CALCULATOR_TARIFF_LIST_ENDPOINT, json=data)
def generate_barcode(self, cdek_number, format="A6") -> Optional[str]:
request_data = {
"orders": [{"cdek_number": cdek_number}],
"copy_count": 1,
"format": format
}
r = self.request('POST', self.BARCODE_ENDPOINT, json=request_data)
if not r:
return None
resp_data = r.json()
if 'entity' not in resp_data:
return None
barcode_uuid = resp_data['entity']['uuid']
return barcode_uuid
def get_barcode_url(self, uuid) -> Optional[str]:
if not uuid:
return None
r = self.request('GET', f'{self.BARCODE_ENDPOINT}/{uuid}')
if not r:
return None
resp_data = r.json()
if 'entity' not in resp_data:
return None
url = resp_data['entity'].get('url')
return url
def get_barcode_file(self, cdek_number):
uuid = self.generate_barcode(cdek_number)
sleep(2) # Sometimes url are not yet created, so be prepared for this
url = self.get_barcode_url(uuid)
if not url:
return None
r = self.request('GET', url)
return ContentFile(r.content) if r and r.content else None
def get_order_statuses(self, cdek_number):
params = {
'cdek_number': str(cdek_number)
}
r = self.request('GET', self.ORDER_INFO_ENDPOINT, params=params)
if not r:
return []
with suppress(KeyError):
statuses = r.json()['entity']['statuses']
statuses = [s.get('code') for s in statuses]
return statuses
return []
client = CDEKClient(settings.CDEK_CLIENT_ID, settings.CDEK_CLIENT_SECRET)
if not is_migration_running():
client.authorize()