fix: 自动结算写入对齐 USE_TZ;lxs 打手注册同步开通管事
order_deal 不再 make_aware 入库以免 MySQL 报错;龙先生打手注册成功时创建管事且邀请人与打手相同。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,9 +5,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@@ -15,9 +16,25 @@ from django.utils import timezone
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _db_datetime(dt):
|
||||||
|
"""写入 DateTimeField 用:与 USE_TZ 对齐,避免 MySQL 拒收 aware datetime。"""
|
||||||
|
if dt is None:
|
||||||
|
return None
|
||||||
|
if settings.USE_TZ:
|
||||||
|
if timezone.is_naive(dt):
|
||||||
|
return timezone.make_aware(dt, timezone.get_current_timezone())
|
||||||
|
return dt
|
||||||
|
if timezone.is_aware(dt):
|
||||||
|
return timezone.make_naive(dt, timezone.get_current_timezone())
|
||||||
|
return dt
|
||||||
|
|
||||||
|
|
||||||
|
def _default_stats_since():
|
||||||
|
return _db_datetime(datetime(2020, 1, 1))
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_deal_config(club_id: str):
|
def get_or_create_deal_config(club_id: str):
|
||||||
from jituan.models import ClubOrderDealConfig
|
from jituan.models import ClubOrderDealConfig
|
||||||
from datetime import datetime
|
|
||||||
cid = (club_id or '').strip()
|
cid = (club_id or '').strip()
|
||||||
if not cid:
|
if not cid:
|
||||||
return None
|
return None
|
||||||
@@ -27,7 +44,7 @@ def get_or_create_deal_config(club_id: str):
|
|||||||
row = ClubOrderDealConfig(
|
row = ClubOrderDealConfig(
|
||||||
club_id=cid,
|
club_id=cid,
|
||||||
stats_enabled=True,
|
stats_enabled=True,
|
||||||
stats_since=timezone.make_aware(datetime(2020, 1, 1)),
|
stats_since=_default_stats_since(),
|
||||||
)
|
)
|
||||||
row.save()
|
row.save()
|
||||||
return row
|
return row
|
||||||
@@ -54,12 +71,8 @@ def order_auto_settle_payload(order) -> dict:
|
|||||||
expire_at = getattr(order, 'AutoExpireAt', None)
|
expire_at = getattr(order, 'AutoExpireAt', None)
|
||||||
if not expire_at:
|
if not expire_at:
|
||||||
return {}
|
return {}
|
||||||
now = timezone.now()
|
now = _db_datetime(timezone.now())
|
||||||
# 与项目 USE_TZ 对齐,避免剩余秒数偏差
|
expire_at = _db_datetime(expire_at)
|
||||||
if timezone.is_aware(expire_at) and timezone.is_naive(now):
|
|
||||||
now = timezone.make_aware(now, timezone.get_current_timezone())
|
|
||||||
elif timezone.is_naive(expire_at) and timezone.is_aware(now):
|
|
||||||
expire_at = timezone.make_aware(expire_at, timezone.get_current_timezone())
|
|
||||||
remain = int((expire_at - now).total_seconds())
|
remain = int((expire_at - now).total_seconds())
|
||||||
try:
|
try:
|
||||||
expire_ts = int(expire_at.timestamp())
|
expire_ts = int(expire_at.timestamp())
|
||||||
@@ -104,7 +117,7 @@ def save_deal_config(club_id: str, data: dict, updated_by: str = '') -> dict:
|
|||||||
|
|
||||||
was_auto = bool(row.auto_settle_enabled)
|
was_auto = bool(row.auto_settle_enabled)
|
||||||
was_stats = bool(row.stats_enabled)
|
was_stats = bool(row.stats_enabled)
|
||||||
now = timezone.now()
|
now = _db_datetime(timezone.now())
|
||||||
|
|
||||||
if 'hours_platform' in data and data['hours_platform'] is not None:
|
if 'hours_platform' in data and data['hours_platform'] is not None:
|
||||||
row.hours_platform = max(1, min(int(data['hours_platform']), 720))
|
row.hours_platform = max(1, min(int(data['hours_platform']), 720))
|
||||||
@@ -125,8 +138,7 @@ def save_deal_config(club_id: str, data: dict, updated_by: str = '') -> dict:
|
|||||||
row.stats_enabled = enabled
|
row.stats_enabled = enabled
|
||||||
if enabled and not was_stats:
|
if enabled and not was_stats:
|
||||||
# 开启时默认从较早起点计,便于旧单刷新也能出成交率
|
# 开启时默认从较早起点计,便于旧单刷新也能出成交率
|
||||||
from datetime import datetime
|
row.stats_since = _default_stats_since()
|
||||||
row.stats_since = timezone.make_aware(datetime(2020, 1, 1))
|
|
||||||
if not enabled:
|
if not enabled:
|
||||||
# 停止写入;保留 since 供只读理解历史起点;再次开启会刷新 since
|
# 停止写入;保留 since 供只读理解历史起点;再次开启会刷新 since
|
||||||
pass
|
pass
|
||||||
@@ -135,8 +147,11 @@ def save_deal_config(club_id: str, data: dict, updated_by: str = '') -> dict:
|
|||||||
|
|
||||||
# 若刚打开统计且 since 空
|
# 若刚打开统计且 since 空
|
||||||
if row.stats_enabled and not row.stats_since:
|
if row.stats_enabled and not row.stats_since:
|
||||||
from datetime import datetime
|
row.stats_since = _default_stats_since()
|
||||||
row.stats_since = timezone.make_aware(datetime(2020, 1, 1))
|
|
||||||
|
# 历史脏数据:aware datetime 在 USE_TZ=False 下无法落库,保存前统一清洗
|
||||||
|
row.auto_settle_enabled_at = _db_datetime(row.auto_settle_enabled_at)
|
||||||
|
row.stats_since = _db_datetime(row.stats_since)
|
||||||
|
|
||||||
row.updated_by = (updated_by or '')[:32]
|
row.updated_by = (updated_by or '')[:32]
|
||||||
row.save()
|
row.save()
|
||||||
@@ -166,18 +181,19 @@ def apply_enter_status_8(instance):
|
|||||||
订单进入结算中(8):写 SettlementTime;若俱乐部自动结算已开且本次开启后进入则打 eligible。
|
订单进入结算中(8):写 SettlementTime;若俱乐部自动结算已开且本次开启后进入则打 eligible。
|
||||||
由 signal 调用。instance 为即将 save 的 Order。
|
由 signal 调用。instance 为即将 save 的 Order。
|
||||||
"""
|
"""
|
||||||
now = timezone.now()
|
now = _db_datetime(timezone.now())
|
||||||
instance.SettlementTime = now
|
instance.SettlementTime = now
|
||||||
instance.PendingDispatch = False
|
instance.PendingDispatch = False
|
||||||
instance.AutoTaskID = ''
|
instance.AutoTaskID = ''
|
||||||
|
|
||||||
club_id = (getattr(instance, 'ClubID', None) or '').strip()
|
club_id = (getattr(instance, 'ClubID', None) or '').strip()
|
||||||
cfg = get_or_create_deal_config(club_id) if club_id else None
|
cfg = get_or_create_deal_config(club_id) if club_id else None
|
||||||
|
enabled_at = _db_datetime(getattr(cfg, 'auto_settle_enabled_at', None)) if cfg else None
|
||||||
if (
|
if (
|
||||||
cfg
|
cfg
|
||||||
and cfg.auto_settle_enabled
|
and cfg.auto_settle_enabled
|
||||||
and cfg.auto_settle_enabled_at
|
and enabled_at
|
||||||
and now >= cfg.auto_settle_enabled_at
|
and now >= enabled_at
|
||||||
):
|
):
|
||||||
hours = int(cfg.hours_merchant if instance.Platform == 2 else cfg.hours_platform) or 48
|
hours = int(cfg.hours_merchant if instance.Platform == 2 else cfg.hours_platform) or 48
|
||||||
instance.AutoSettleEligible = True
|
instance.AutoSettleEligible = True
|
||||||
@@ -215,15 +231,18 @@ def mark_order_completed_unified(order, *, source='auto_settle', operator=''):
|
|||||||
# 三重闸:资格位 + 到期时间 + 俱乐部开关仍开(关开关后立刻停,不靠等下一轮扫)
|
# 三重闸:资格位 + 到期时间 + 俱乐部开关仍开(关开关后立刻停,不靠等下一轮扫)
|
||||||
if not locked.AutoSettleEligible:
|
if not locked.AutoSettleEligible:
|
||||||
return False, '非自动结算资格单'
|
return False, '非自动结算资格单'
|
||||||
if locked.AutoExpireAt and locked.AutoExpireAt > timezone.now():
|
now = _db_datetime(timezone.now())
|
||||||
|
expire_at = _db_datetime(locked.AutoExpireAt)
|
||||||
|
if expire_at and expire_at > now:
|
||||||
return False, '未到自动结算时间'
|
return False, '未到自动结算时间'
|
||||||
club_id = (getattr(locked, 'ClubID', None) or '').strip()
|
club_id = (getattr(locked, 'ClubID', None) or '').strip()
|
||||||
cfg = get_or_create_deal_config(club_id) if club_id else None
|
cfg = get_or_create_deal_config(club_id) if club_id else None
|
||||||
if not cfg or not cfg.auto_settle_enabled:
|
if not cfg or not cfg.auto_settle_enabled:
|
||||||
return False, '俱乐部已关闭自动结算'
|
return False, '俱乐部已关闭自动结算'
|
||||||
# 只认「本次打开之后」进结算中的单;开启前的旧 8 状态单不会带 eligible
|
# 只认「本次打开之后」进结算中的单;开启前的旧 8 状态单不会带 eligible
|
||||||
if cfg.auto_settle_enabled_at and getattr(locked, 'SettlementTime', None):
|
enabled_at = _db_datetime(cfg.auto_settle_enabled_at)
|
||||||
if locked.SettlementTime < cfg.auto_settle_enabled_at:
|
settle_at = _db_datetime(getattr(locked, 'SettlementTime', None))
|
||||||
|
if enabled_at and settle_at and settle_at < enabled_at:
|
||||||
return False, '订单进入结算中早于本次开启,不自动结算'
|
return False, '订单进入结算中早于本次开启,不自动结算'
|
||||||
|
|
||||||
dashou_id = locked.PlayerID
|
dashou_id = locked.PlayerID
|
||||||
@@ -323,7 +342,7 @@ def scan_due_auto_settle(limit: int = 100) -> dict:
|
|||||||
if not clubs:
|
if not clubs:
|
||||||
return {'scanned': 0, 'settled': 0, 'failed': 0, 'clubs': 0}
|
return {'scanned': 0, 'settled': 0, 'failed': 0, 'clubs': 0}
|
||||||
|
|
||||||
now = timezone.now()
|
now = _db_datetime(timezone.now())
|
||||||
qs = (
|
qs = (
|
||||||
Order.objects.filter(
|
Order.objects.filter(
|
||||||
ClubID__in=list(clubs),
|
ClubID__in=list(clubs),
|
||||||
|
|||||||
46
users/services/lxs_dashou_guanshi.py
Normal file
46
users/services/lxs_dashou_guanshi.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"""龙先生(lxs):打手注册成功时同步开通管事。
|
||||||
|
|
||||||
|
管事邀请人 = 打手邀请人(邀请该用户的管事 UID)。
|
||||||
|
其它俱乐部不处理。
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from utils.invitationcode_utils import CreateInvitationCode
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
LXS_CLUB_ID = 'lxs'
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_lxs_guanshi_on_dashou_register(user, inviter_uid, club_id):
|
||||||
|
"""
|
||||||
|
仅 club_id=lxs:若尚无管事身份则创建。
|
||||||
|
返回新建或已有的 UserGuanshi;非 lxs / 参数不全则返回 None。
|
||||||
|
"""
|
||||||
|
from users.models import UserGuanshi
|
||||||
|
|
||||||
|
if (club_id or '').strip() != LXS_CLUB_ID:
|
||||||
|
return None
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
|
||||||
|
existing = UserGuanshi.query.filter(user=user).first()
|
||||||
|
if existing:
|
||||||
|
return existing
|
||||||
|
|
||||||
|
inviter = (inviter_uid or '').strip()[:7] or None
|
||||||
|
yaoqingma = CreateInvitationCode(str(user.UserUID))
|
||||||
|
profile = UserGuanshi.query.create(
|
||||||
|
user=user,
|
||||||
|
yaoqingma=yaoqingma,
|
||||||
|
yaoqingren=inviter,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
'lxs 打手注册同步开通管事 uid=%s yaoqingren=%s yaoqingma=%s',
|
||||||
|
getattr(user, 'UserUID', ''),
|
||||||
|
inviter,
|
||||||
|
yaoqingma,
|
||||||
|
)
|
||||||
|
return profile
|
||||||
@@ -580,7 +580,11 @@ class DashouZhuceView(APIView):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# 5.3 创建管事扩展表
|
# 5.3 龙先生:同步开通管事,邀请人与打手邀请人相同
|
||||||
|
from users.services.lxs_dashou_guanshi import ensure_lxs_guanshi_on_dashou_register
|
||||||
|
ensure_lxs_guanshi_on_dashou_register(
|
||||||
|
current_user, guanshi_yonghuid, club_id,
|
||||||
|
)
|
||||||
|
|
||||||
# 5.4 更新主表的用户类型 (如果需要,可以设为'dashou',或保持原逻辑)
|
# 5.4 更新主表的用户类型 (如果需要,可以设为'dashou',或保持原逻辑)
|
||||||
# current_user.UserType = 'dashou' # 根据你的业务逻辑决定
|
# current_user.UserType = 'dashou' # 根据你的业务逻辑决定
|
||||||
@@ -613,10 +617,14 @@ class DashouZhuceView(APIView):
|
|||||||
|
|
||||||
# 6. 注册成功,返回信息 (新注册用户没有会员记录,clumber返回空列表)
|
# 6. 注册成功,返回信息 (新注册用户没有会员记录,clumber返回空列表)
|
||||||
fanhui_data = self.zhuangbeiFanhuiShuju(dashou_profile, current_user, is_new=True)
|
fanhui_data = self.zhuangbeiFanhuiShuju(dashou_profile, current_user, is_new=True)
|
||||||
|
if (club_id or '').strip() == 'lxs':
|
||||||
|
ok_msg = '注册成功!已开通打手、管事身份。'
|
||||||
|
else:
|
||||||
|
ok_msg = '注册成功!'
|
||||||
return Response({
|
return Response({
|
||||||
'code': 200,
|
'code': 200,
|
||||||
'message': '注册成功!已开通打手、管事身份。',
|
'message': ok_msg,
|
||||||
'msg': '注册成功!已开通打手、管事身份。',
|
'msg': ok_msg,
|
||||||
'data': fanhui_data
|
'data': fanhui_data
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -921,7 +929,11 @@ class WechatLoginAndDashouRegisterView(APIView):
|
|||||||
|
|
||||||
# 创建商家扩展表
|
# 创建商家扩展表
|
||||||
|
|
||||||
# 创建管事扩展表
|
# 龙先生:同步开通管事,邀请人与打手邀请人相同
|
||||||
|
from users.services.lxs_dashou_guanshi import ensure_lxs_guanshi_on_dashou_register
|
||||||
|
ensure_lxs_guanshi_on_dashou_register(
|
||||||
|
user_main, guanshi_yonghuid, club_id,
|
||||||
|
)
|
||||||
|
|
||||||
# 更新原管事的邀请数量
|
# 更新原管事的邀请数量
|
||||||
locked_guanshi = UserGuanshi.objects.select_for_update().get(pk=guanshi_profile.pk)
|
locked_guanshi = UserGuanshi.objects.select_for_update().get(pk=guanshi_profile.pk)
|
||||||
@@ -946,9 +958,13 @@ class WechatLoginAndDashouRegisterView(APIView):
|
|||||||
# 6. 准备返回数据
|
# 6. 准备返回数据
|
||||||
response_data = self.zhuangbeiFanhuiShuju(user_main, dashou_profile, token)
|
response_data = self.zhuangbeiFanhuiShuju(user_main, dashou_profile, token)
|
||||||
|
|
||||||
|
if (club_id or '').strip() == 'lxs':
|
||||||
|
ok_msg = '登录并注册成功!已开通打手、管事等身份。'
|
||||||
|
else:
|
||||||
|
ok_msg = '登录并注册成功!'
|
||||||
return Response({
|
return Response({
|
||||||
'code': 0,
|
'code': 0,
|
||||||
'msg': '登录并注册成功!已开通打手、管事等身份。',
|
'msg': ok_msg,
|
||||||
'data': response_data
|
'data': response_data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user