Files
2026-05-28 10:32:33 +03:30

230 lines
8.8 KiB
Python

import logging
from azbankgateways import (
bankfactories,
models as bank_models,
default_settings as settings,
)
from azbankgateways.models.enum import PaymentStatus
from .models import OrderModel
from account.models import PushSubscription
import ghasedak_sms
from product.models import ProductImageModel
from celery import shared_task
from django.db import transaction
logger = logging.getLogger(__name__)
@shared_task
def udpate_bank_status():
factory = bankfactories.BankFactory()
# ۱. بروزرسانی رکوردهای منقضی در یک تراکنش جداگانه
try:
with transaction.atomic():
bank_models.Bank.objects.update_expire_records()
except Exception as e:
logger.error(f"Error in update_expire_records: {e}")
# ۲. پردازش رکوردهایی که از بانک برگشته‌اند
for item in bank_models.Bank.objects.filter_return_from_bank():
try:
with transaction.atomic():
bank = factory.create(
bank_type=item.bank_type, identifier=item.bank_choose_identifier
)
bank.verify(item.tracking_code)
# استفاده از select_for_update برای جلوگیری از Race Condition
bank_record = bank_models.Bank.objects.select_related('order').select_for_update().get(tracking_code=item.tracking_code)
if bank_record.is_success and bank_record.order:
bank_record.order.cart.clear_cart()
bank_record.order.is_paid = True
bank_record.order.save(update_fields=['is_paid'])
elif bank_record.order:
bank_record.order.rollback_stock()
except Exception as e:
logger.error(f"Failed to verify bank record {item.tracking_code}: {e}")
failed_statuses = [
PaymentStatus.CANCEL_BY_USER,
PaymentStatus.EXPIRE_GATEWAY_TOKEN,
PaymentStatus.EXPIRE_VERIFY_PAYMENT,
PaymentStatus.ERROR,
]
# 1. ابتدا رکوردهای بانکی را بدون select_related پیدا کنید
# از select_for_update اینجا استفاده نکنید چون به Order وصل است
failed_records = bank_models.Bank.objects.filter(
status__in=failed_statuses,
order__isnull=False,
order__is_paid=False,
order__is_stock_rolled_back=False,
)
logger.info(f"Found {failed_records.count()} failed records for rollback.")
for bank_record in failed_records:
try:
# استفاده از تراکنش اتمیک برای هر رکورد
with transaction.atomic():
# 2. حالا خودِ Order مربوطه را با select_for_update قفل کنید
# این کار مانع از تداخل با بقیه تسک‌ها می‌شود
order = bank_record.order
# بررسی مجدد شرط برای اطمینان در لحظه قفل شدن
if order and not order.is_paid and not order.is_stock_rolled_back:
order.rollback_stock()
logger.info(f"Successfully rolled back stock for bank record {bank_record.id}")
else:
logger.info(f"Order {order.id} already processed or paid, skipping.")
except Exception as e:
logger.error(f"Failed to rollback stock for bank record {bank_record.id}: {str(e)}")
return "update bank record is done"
@shared_task
def send_change_status_notif(instance_pk, new_status):
instance = OrderModel.objects.get(pk=instance_pk)
user_subs = PushSubscription.objects.filter(user=instance.user)
for user_sub in user_subs:
try:
user_sub.send_notif(f'سفارش شما به {new_status} تغییر کرد', f'سفارش شما به {new_status} تغییر کرد', ProductImageModel.objects.all().first().image.url)
except Exception as e:
logger.error('Error sending status notification: ' + str(e))
@shared_task
def send_change_status_sms(instance_pk, new_status):
instance = OrderModel.objects.get(pk=instance_pk)
sms_api = ghasedak_sms.Ghasedak(api_key="8f7396f1e3c39e3a4621009c558d955336eea6d21cf257dd74ae262d6f22a458XdoDjH6egJsiZsy8")
response = sms_api.send_single_sms(
ghasedak_sms.SendSingleSmsInput(
message=f'سفارش شما به {new_status} تغییر کرد',
receptor=instance.user.phone,
line_number='30005006004095',
client_reference_id=str(instance.user.pk)
)
)
if response['statusCode'] == 200:
return 'done log later'
else:
return f'error: {response}'
@shared_task
def generate_daily_shop_reports():
"""Generate daily shop reports for the previous day.
This task aggregates ShopOrderModel entries from yesterday
and creates/updates ShopDailyReport records for each shop.
Scheduled to run daily at midnight.
"""
from django.utils import timezone
from datetime import timedelta
from django.db.models import Sum
from .models import ShopOrderModel, ShopDailyReport
target_date = (timezone.now() - timedelta(days=1)).date()
logging.info(f'Generating shop reports for {target_date}')
shop_orders = ShopOrderModel.objects.filter(created_at__date=target_date)
if not shop_orders.exists():
logging.warning(f'No shop orders found for {target_date}')
return f'No shop orders for {target_date}'
shops = shop_orders.values('shop').distinct()
reports_created = 0
reports_updated = 0
for s in shops:
shop_id = s['shop']
aggr = shop_orders.filter(shop_id=shop_id).aggregate(
total_sales=Sum('subtotal'),
total_commission=Sum('commission_amount'),
total_payable=Sum('payable_amount')
)
total_sales = aggr['total_sales'] or 0
total_commission = aggr['total_commission'] or 0
total_payable = aggr['total_payable'] or 0
report, created = ShopDailyReport.objects.update_or_create(
shop_id=shop_id,
date=target_date,
defaults={
'total_sales': total_sales,
'total_commission': total_commission,
'total_payable': total_payable,
}
)
# Link all shop orders for this shop and date to the daily report
shop_orders.filter(shop_id=shop_id).update(daily_report=report)
if created:
reports_created += 1
else:
reports_updated += 1
logging.info(f"Shop {shop_id}: sales={total_sales}, commission={total_commission}, payable={total_payable}")
result = f'Generated reports for {target_date}: {reports_created} created, {reports_updated} updated'
logging.info(result)
return result
@shared_task
def send_shop_order_invoice_telegram_task(shop_order_id, chat_id, bot_token):
"""Send shop order invoice PDF to Telegram chat.
Args:
shop_order_id: ID of the ShopOrderModel
chat_id: Telegram chat ID to send invoice to
bot_token: Telegram bot token for authentication
Returns:
Success or error message
"""
import telebot
from .invoice_generator import generate_shop_order_invoice
from .models import ShopOrderModel
try:
# Get the shop order
shop_order = ShopOrderModel.objects.get(pk=shop_order_id)
# Generate invoice PDF (should return a file-like object)
pdf_buffer = generate_shop_order_invoice(shop_order_id)
if hasattr(pdf_buffer, 'seek'):
pdf_buffer.seek(0)
caption = f'فاکتور سفارش #{shop_order_id}\n{shop_order.shop.shop_name}'
# Initialize bot and send document
bot = telebot.TeleBot(bot_token)
bot.send_document(
chat_id=chat_id,
document=pdf_buffer,
caption=caption,
visible_file_name=f'invoice_shop_order_{shop_order_id}.pdf'
)
logging.info(f'Successfully sent shop order invoice {shop_order_id} to Telegram chat {chat_id}')
return f'Invoice sent successfully to chat {chat_id}'
except ShopOrderModel.DoesNotExist:
error_msg = f'ShopOrderModel with id {shop_order_id} does not exist'
logging.error(error_msg)
return error_msg
except telebot.apihelper.ApiException as e:
error_msg = f'Telegram API error sending invoice {shop_order_id}: {str(e)}'
logging.error(error_msg)
return error_msg
except Exception as e:
error_msg = f'Error sending invoice {shop_order_id} to Telegram: {str(e)}'
logging.error(error_msg)
return error_msg