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
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.db.models import F
|
||||
from django.utils import timezone
|
||||
@@ -15,9 +16,25 @@ from django.utils import timezone
|
||||
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):
|
||||
from jituan.models import ClubOrderDealConfig
|
||||
from datetime import datetime
|
||||
cid = (club_id or '').strip()
|
||||
if not cid:
|
||||
return None
|
||||
@@ -27,7 +44,7 @@ def get_or_create_deal_config(club_id: str):
|
||||
row = ClubOrderDealConfig(
|
||||
club_id=cid,
|
||||
stats_enabled=True,
|
||||
stats_since=timezone.make_aware(datetime(2020, 1, 1)),
|
||||
stats_since=_default_stats_since(),
|
||||
)
|
||||
row.save()
|
||||
return row
|
||||
@@ -54,12 +71,8 @@ def order_auto_settle_payload(order) -> dict:
|
||||
expire_at = getattr(order, 'AutoExpireAt', None)
|
||||
if not expire_at:
|
||||
return {}
|
||||
now = timezone.now()
|
||||
# 与项目 USE_TZ 对齐,避免剩余秒数偏差
|
||||
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())
|
||||
now = _db_datetime(timezone.now())
|
||||
expire_at = _db_datetime(expire_at)
|
||||
remain = int((expire_at - now).total_seconds())
|
||||
try:
|
||||
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_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:
|
||||
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
|
||||
if enabled and not was_stats:
|
||||
# 开启时默认从较早起点计,便于旧单刷新也能出成交率
|
||||
from datetime import datetime
|
||||
row.stats_since = timezone.make_aware(datetime(2020, 1, 1))
|
||||
row.stats_since = _default_stats_since()
|
||||
if not enabled:
|
||||
# 停止写入;保留 since 供只读理解历史起点;再次开启会刷新 since
|
||||
pass
|
||||
@@ -135,8 +147,11 @@ def save_deal_config(club_id: str, data: dict, updated_by: str = '') -> dict:
|
||||
|
||||
# 若刚打开统计且 since 空
|
||||
if row.stats_enabled and not row.stats_since:
|
||||
from datetime import datetime
|
||||
row.stats_since = timezone.make_aware(datetime(2020, 1, 1))
|
||||
row.stats_since = _default_stats_since()
|
||||
|
||||
# 历史脏数据: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.save()
|
||||
@@ -166,18 +181,19 @@ def apply_enter_status_8(instance):
|
||||
订单进入结算中(8):写 SettlementTime;若俱乐部自动结算已开且本次开启后进入则打 eligible。
|
||||
由 signal 调用。instance 为即将 save 的 Order。
|
||||
"""
|
||||
now = timezone.now()
|
||||
now = _db_datetime(timezone.now())
|
||||
instance.SettlementTime = now
|
||||
instance.PendingDispatch = False
|
||||
instance.AutoTaskID = ''
|
||||
|
||||
club_id = (getattr(instance, 'ClubID', None) or '').strip()
|
||||
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 (
|
||||
cfg
|
||||
and cfg.auto_settle_enabled
|
||||
and cfg.auto_settle_enabled_at
|
||||
and now >= cfg.auto_settle_enabled_at
|
||||
and enabled_at
|
||||
and now >= enabled_at
|
||||
):
|
||||
hours = int(cfg.hours_merchant if instance.Platform == 2 else cfg.hours_platform) or 48
|
||||
instance.AutoSettleEligible = True
|
||||
@@ -215,15 +231,18 @@ def mark_order_completed_unified(order, *, source='auto_settle', operator=''):
|
||||
# 三重闸:资格位 + 到期时间 + 俱乐部开关仍开(关开关后立刻停,不靠等下一轮扫)
|
||||
if not locked.AutoSettleEligible:
|
||||
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, '未到自动结算时间'
|
||||
club_id = (getattr(locked, 'ClubID', None) or '').strip()
|
||||
cfg = get_or_create_deal_config(club_id) if club_id else None
|
||||
if not cfg or not cfg.auto_settle_enabled:
|
||||
return False, '俱乐部已关闭自动结算'
|
||||
# 只认「本次打开之后」进结算中的单;开启前的旧 8 状态单不会带 eligible
|
||||
if cfg.auto_settle_enabled_at and getattr(locked, 'SettlementTime', None):
|
||||
if locked.SettlementTime < cfg.auto_settle_enabled_at:
|
||||
enabled_at = _db_datetime(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, '订单进入结算中早于本次开启,不自动结算'
|
||||
|
||||
dashou_id = locked.PlayerID
|
||||
@@ -323,7 +342,7 @@ def scan_due_auto_settle(limit: int = 100) -> dict:
|
||||
if not clubs:
|
||||
return {'scanned': 0, 'settled': 0, 'failed': 0, 'clubs': 0}
|
||||
|
||||
now = timezone.now()
|
||||
now = _db_datetime(timezone.now())
|
||||
qs = (
|
||||
Order.objects.filter(
|
||||
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
|
||||
|
||||
|
||||
# 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',或保持原逻辑)
|
||||
# current_user.UserType = 'dashou' # 根据你的业务逻辑决定
|
||||
@@ -613,10 +617,14 @@ class DashouZhuceView(APIView):
|
||||
|
||||
# 6. 注册成功,返回信息 (新注册用户没有会员记录,clumber返回空列表)
|
||||
fanhui_data = self.zhuangbeiFanhuiShuju(dashou_profile, current_user, is_new=True)
|
||||
if (club_id or '').strip() == 'lxs':
|
||||
ok_msg = '注册成功!已开通打手、管事身份。'
|
||||
else:
|
||||
ok_msg = '注册成功!'
|
||||
return Response({
|
||||
'code': 200,
|
||||
'message': '注册成功!已开通打手、管事身份。',
|
||||
'msg': '注册成功!已开通打手、管事身份。',
|
||||
'message': ok_msg,
|
||||
'msg': ok_msg,
|
||||
'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)
|
||||
@@ -946,9 +958,13 @@ class WechatLoginAndDashouRegisterView(APIView):
|
||||
# 6. 准备返回数据
|
||||
response_data = self.zhuangbeiFanhuiShuju(user_main, dashou_profile, token)
|
||||
|
||||
if (club_id or '').strip() == 'lxs':
|
||||
ok_msg = '登录并注册成功!已开通打手、管事等身份。'
|
||||
else:
|
||||
ok_msg = '登录并注册成功!'
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': '登录并注册成功!已开通打手、管事等身份。',
|
||||
'msg': ok_msg,
|
||||
'data': response_data
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user