+ 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
|
# clear environment on exit
|
||||||
vacuum = true
|
vacuum = true
|
||||||
|
|
||||||
|
smart-attach-daemon = /tmp/celery-main.pid /var/www/phzhik-poizonstore/run_celery.sh
|
||||||
|
|
||||||
env = LANG=C.UTF-8
|
env = LANG=C.UTF-8
|
||||||
enable-threads = true
|
enable-threads = true
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import http
|
import http
|
||||||
import os
|
import os
|
||||||
|
from contextlib import suppress
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
@ -13,6 +14,71 @@ from store.utils import is_migration_running
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'poizonstore.settings')
|
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:
|
class CDEKClient:
|
||||||
AUTH_ENDPOINT = 'oauth/token'
|
AUTH_ENDPOINT = 'oauth/token'
|
||||||
ORDER_INFO_ENDPOINT = 'orders'
|
ORDER_INFO_ENDPOINT = 'orders'
|
||||||
|
|
@ -118,6 +184,22 @@ class CDEKClient:
|
||||||
r = self.request('GET', url)
|
r = self.request('GET', url)
|
||||||
return ContentFile(r.content) if r and r.content else None
|
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)
|
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.
|
# We recommend adjusting this value in production.
|
||||||
profiles_sample_rate=1.0,
|
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
|
drf-extra-fields==3.5.0
|
||||||
Pillow==9.5.0
|
Pillow==9.5.0
|
||||||
|
|
||||||
|
# Tasks
|
||||||
|
celery==5.3.6
|
||||||
|
redis==5.0.1
|
||||||
|
flower==2.0.1
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
tqdm==4.65.0
|
tqdm==4.65.0
|
||||||
django-debug-toolbar==4.1.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"
|
COMPLETED = "completed"
|
||||||
|
|
||||||
PDF_AVAILABLE_STATUSES = (RUSSIA, SPLIT_WAITING, SPLIT_PAID, CDEK, COMPLETED)
|
PDF_AVAILABLE_STATUSES = (RUSSIA, SPLIT_WAITING, SPLIT_PAID, CDEK, COMPLETED)
|
||||||
|
CDEK_READY_STATUSES = (RUSSIA, SPLIT_PAID, CDEK)
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(DRAFT, 'Черновик'),
|
(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