+ Celery
+ Update CDEK status in background
This commit is contained in:
parent
e5e93ab6d5
commit
9d7e45cd65
|
|
@ -25,5 +25,7 @@ chmod-socket = 664
|
|||
# clear environment on exit
|
||||
vacuum = true
|
||||
|
||||
smart-attach-daemon = /tmp/celery-main.pid /var/www/phzhik-poizonstore/run_celery.sh
|
||||
|
||||
env = LANG=C.UTF-8
|
||||
enable-threads = true
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import http
|
||||
import os
|
||||
from contextlib import suppress
|
||||
from time import sleep
|
||||
from typing import Optional
|
||||
from urllib.parse import urljoin
|
||||
|
|
@ -13,6 +14,71 @@ 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'
|
||||
|
|
@ -118,6 +184,22 @@ class CDEKClient:
|
|||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
from .celery import app as celery_app
|
||||
|
||||
__all__ = ('celery_app',)
|
||||
22
poizonstore/celery.py
Normal file
22
poizonstore/celery.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
from celery import Celery
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'poizonstore.settings')
|
||||
|
||||
app = Celery('poizonstore')
|
||||
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||
app.autodiscover_tasks()
|
||||
|
||||
app.conf.beat_schedule = {
|
||||
'update-cdek-status-every-hour': {
|
||||
'task': 'store.tasks.schedule_cdek_status_update',
|
||||
'schedule': timedelta(hours=1),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@app.task()
|
||||
def debug_task():
|
||||
print(f'Task complete')
|
||||
|
|
@ -215,3 +215,12 @@ if not DEBUG:
|
|||
# We recommend adjusting this value in production.
|
||||
profiles_sample_rate=1.0,
|
||||
)
|
||||
|
||||
# Celery
|
||||
BROKER_URL = 'redis://localhost:6379/1'
|
||||
CELERY_RESULT_BACKEND = BROKER_URL
|
||||
CELERY_BROKER_URL = BROKER_URL
|
||||
CELERY_ACCEPT_CONTENT = ['application/json']
|
||||
CELERY_TASK_SERIALIZER = 'json'
|
||||
CELERY_RESULT_SERIALIZER = 'json'
|
||||
CELERY_TIMEZONE = TIME_ZONE
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ djoser==2.2.0
|
|||
drf-extra-fields==3.5.0
|
||||
Pillow==9.5.0
|
||||
|
||||
# Tasks
|
||||
celery==5.3.6
|
||||
redis==5.0.1
|
||||
flower==2.0.1
|
||||
|
||||
# Misc
|
||||
tqdm==4.65.0
|
||||
django-debug-toolbar==4.1.0
|
||||
|
|
|
|||
20
run_celery.sh
Executable file
20
run_celery.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
|
||||
PROJECT_NAME="poizonstore"
|
||||
|
||||
# Run Celery worker
|
||||
echo 'Starting Celery worker'
|
||||
celery -A $PROJECT_NAME worker -l INFO --pidfile=/tmp/celery.pid &
|
||||
|
||||
# Wait for worker to start
|
||||
until timeout -t 10 celery -A project inspect ping; do
|
||||
>&2 echo "Celery workers not available"
|
||||
done
|
||||
|
||||
# Run flower for Celery management
|
||||
echo 'Starting Celery flower'
|
||||
celery -A $PROJECT_NAME flower --pidfile=/tmp/celery-flower.pid &
|
||||
|
||||
# Run celery beat for periodic tasks
|
||||
echo 'Starting Celery beat'
|
||||
celery -A $PROJECT_NAME beat -l INFO --pidfile=/tmp/celery-beat.pid &
|
||||
3
stop_celery.sh
Executable file
3
stop_celery.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
killall celery
|
||||
|
|
@ -355,6 +355,7 @@ class Checklist(models.Model):
|
|||
COMPLETED = "completed"
|
||||
|
||||
PDF_AVAILABLE_STATUSES = (RUSSIA, SPLIT_WAITING, SPLIT_PAID, CDEK, COMPLETED)
|
||||
CDEK_READY_STATUSES = (RUSSIA, SPLIT_PAID, CDEK)
|
||||
|
||||
CHOICES = (
|
||||
(DRAFT, 'Черновик'),
|
||||
|
|
|
|||
46
store/tasks.py
Normal file
46
store/tasks.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
from celery import shared_task
|
||||
from django.db.models import Q
|
||||
|
||||
from external_api.cdek import client as cdek_client, CDEKStatus
|
||||
from .models import Checklist
|
||||
|
||||
|
||||
@shared_task
|
||||
def check_cdek_status(order_id):
|
||||
obj = Checklist.objects.filter(id=order_id).first()
|
||||
if obj is None or obj.cdek_tracking is None or obj.status == Checklist.Status.COMPLETED:
|
||||
return
|
||||
|
||||
# Get CDEK statuses
|
||||
statuses = cdek_client.get_order_statuses(obj.cdek_tracking)
|
||||
if not statuses:
|
||||
return
|
||||
|
||||
new_status = obj.status
|
||||
if CDEKStatus.DELIVERED in statuses:
|
||||
new_status = Checklist.Status.COMPLETED
|
||||
elif CDEKStatus.READY_FOR_SHIPMENT_IN_SENDER_CITY in statuses:
|
||||
new_status = Checklist.Status.CDEK
|
||||
|
||||
# Update status
|
||||
if obj.status != new_status:
|
||||
print(f'Order [{obj.id}] status: {obj.status} -> {new_status}')
|
||||
obj.status = new_status
|
||||
obj.save()
|
||||
return new_status
|
||||
|
||||
|
||||
@shared_task
|
||||
def schedule_cdek_status_update():
|
||||
qs = Checklist.objects.filter(
|
||||
Q(cdek_tracking__isnull=False) & Q(status__in=Checklist.Status.CDEK_READY_STATUSES)
|
||||
)
|
||||
|
||||
order_count = len(qs)
|
||||
print(f'Scheduled to update {order_count} orders')
|
||||
|
||||
# Spawn a sub-task for every order
|
||||
for obj in qs:
|
||||
check_cdek_status.delay(order_id=obj.id)
|
||||
|
||||
return order_count
|
||||
Loading…
Reference in New Issue
Block a user