From e64697b64acf090d454332ff58f71e012f5e6e54 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 19 Jun 2026 19:19:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=BB=E5=BA=95=E8=A7=A3=E5=86=B3=E4=BA=86?= =?UTF-8?q?=20create=5Ftime=20=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C?= =?UTF-8?q?=E5=B0=86=E6=89=80=E6=9C=89=E5=AD=97=E6=AE=B5=E9=83=BD=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E4=BA=86=20CreateTime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/migrations/0001_initial.py | 18 +- ...16da_idx_abnormal_us_createt_38f96c_idx.py | 18 + backend/models.py | 648 +++--- backend/view.py | 138 +- config/migrations/0001_initial.py | 40 +- config/models.py | 40 +- config/tasks.py | 2 +- config/views.py | 24 +- gvsdsdk/commission/models.py | 704 +++--- gvsdsdk/fluent.py | 1848 +++++++-------- .../migrations/0002_alter_user_useruuid.py | 20 - ...002_position_roleposition_roleshop_shop.py | 90 - gvsdsdk/model_base.py | 1080 ++++----- migrate_to_gvsdsdk_user.py | 520 ++--- orders/migrations/0001_initial.py | 52 +- ...ion_commissionrate_escortorder_and_more.py | 52 +- orders/models.py | 2054 ++++++++--------- orders/views.py | 16 +- products/migrations/0001_initial.py | 28 +- products/models.py | 726 +++--- products/views.py | 56 +- rank/migrations/0001_initial.py | 24 +- ...dashou_biao_dashou__502414_idx_and_more.py | 23 + rank/models.py | 446 ++-- rank/views.py | 22 +- refactor_replace.py | 244 +- shop/migrations/0001_initial.py | 24 +- shop/models.py | 448 ++-- shop/utils.py | 8 +- shop/views.py | 26 +- users/migrations/0001_initial.py | 46 +- users/models.py | 48 +- users/tixian_shenhe_services.py | 24 +- users/tixian_shenhe_views.py | 4 +- users/views.py | 114 +- 35 files changed, 4803 insertions(+), 4872 deletions(-) create mode 100644 backend/migrations/0002_rename_abnormal_us_create__ee16da_idx_abnormal_us_createt_38f96c_idx.py delete mode 100644 gvsdsdk/migrations/0002_alter_user_useruuid.py delete mode 100644 gvsdsdk/migrations/0002_position_roleposition_roleshop_shop.py create mode 100644 rank/migrations/0003_rename_dashou_biao_dashou__36e1c3_idx_dashou_biao_dashou__502414_idx_and_more.py diff --git a/backend/migrations/0001_initial.py b/backend/migrations/0001_initial.py index 15e83fd..d556bc9 100644 --- a/backend/migrations/0001_initial.py +++ b/backend/migrations/0001_initial.py @@ -28,7 +28,7 @@ class Migration(migrations.Migration): ('request_headers', models.TextField(blank=True, default='', verbose_name='请求头(JSON格式)')), ('attempt_type', models.CharField(blank=True, default='', max_length=50, verbose_name='攻击类型')), ('detail', models.TextField(blank=True, default='', verbose_name='详细描述')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ], options={ 'verbose_name': '异常用户日志', @@ -105,8 +105,8 @@ class Migration(migrations.Migration): ('perm_code', models.CharField(db_index=True, max_length=100, unique=True, verbose_name='权限编码')), ('perm_name', models.CharField(max_length=100, verbose_name='权限名称')), ('description', models.CharField(blank=True, default='', max_length=200, verbose_name='权限描述')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '权限', @@ -140,8 +140,8 @@ class Migration(migrations.Migration): ('role_code', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='角色编码')), ('role_name', models.CharField(max_length=50, verbose_name='角色名称')), ('description', models.CharField(blank=True, default='', max_length=200, verbose_name='角色描述')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '角色', @@ -170,8 +170,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), ('account_id', models.CharField(db_index=True, max_length=11, verbose_name='账号ID')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.role', verbose_name='角色')), ], options={ @@ -185,7 +185,7 @@ class Migration(migrations.Migration): name='RolePermission', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.permission', verbose_name='权限')), ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.role', verbose_name='角色')), ], @@ -290,7 +290,7 @@ class Migration(migrations.Migration): ), migrations.AddIndex( model_name='abnormaluserlog', - index=models.Index(fields=['create_time'], name='abnormal_us_create__ee16da_idx'), + index=models.Index(fields=['CreateTime'], name='abnormal_us_create__ee16da_idx'), ), migrations.AlterUniqueTogether( name='userrole', diff --git a/backend/migrations/0002_rename_abnormal_us_create__ee16da_idx_abnormal_us_createt_38f96c_idx.py b/backend/migrations/0002_rename_abnormal_us_create__ee16da_idx_abnormal_us_createt_38f96c_idx.py new file mode 100644 index 0000000..02cb9ee --- /dev/null +++ b/backend/migrations/0002_rename_abnormal_us_create__ee16da_idx_abnormal_us_createt_38f96c_idx.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.27 on 2026-06-19 19:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('backend', '0001_initial'), + ] + + operations = [ + migrations.RenameIndex( + model_name='abnormaluserlog', + new_name='abnormal_us_CreateT_38f96c_idx', + old_name='abnormal_us_create__ee16da_idx', + ), + ] diff --git a/backend/models.py b/backend/models.py index fb1b377..ed1b591 100644 --- a/backend/models.py +++ b/backend/models.py @@ -1,324 +1,324 @@ -from django.db import models -from gvsdsdk.model_base import QModel - - -# ==================== 1. 角色表 ==================== -class Role(QModel): - """角色表""" - role_id = models.AutoField(primary_key=True, verbose_name='角色ID') - role_code = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='角色编码') - role_name = models.CharField(max_length=50, verbose_name='角色名称') - description = models.CharField(max_length=200, blank=True, default='', verbose_name='角色描述') - - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'role' - verbose_name = '角色' - verbose_name_plural = '角色' - managed = True - indexes = [ - models.Index(fields=['role_code']), - ] - - def __str__(self): - return self.role_name - - -# ==================== 2. 权限表 ==================== -class Permission(QModel): - """权限表""" - perm_id = models.AutoField(primary_key=True, verbose_name='权限ID') - perm_code = models.CharField(max_length=100, unique=True, db_index=True, verbose_name='权限编码') - perm_name = models.CharField(max_length=100, verbose_name='权限名称') - description = models.CharField(max_length=200, blank=True, default='', verbose_name='权限描述') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'permission' - verbose_name = '权限' - verbose_name_plural = '权限' - managed = True - indexes = [ - models.Index(fields=['perm_code']), - ] - - def __str__(self): - return f"{self.perm_name} ({self.perm_code})" - - -# ==================== 3. 角色-权限关联表 ==================== -class RolePermission(QModel): - """角色权限关联表""" - id = models.AutoField(primary_key=True) - role = models.ForeignKey(Role, on_delete=models.CASCADE, db_index=True, verbose_name='角色') - permission = models.ForeignKey(Permission, on_delete=models.CASCADE, db_index=True, verbose_name='权限') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'role_permission' - verbose_name = '角色权限关联' - verbose_name_plural = '角色权限关联' - managed = True - unique_together = [['role', 'permission']] - - def __str__(self): - return f"{self.role.role_name} -> {self.permission.perm_name}" - - -# ==================== 4. 用户-角色关联表 ==================== -class UserRole(QModel): - """用户角色关联表""" - id = models.AutoField(primary_key=True) - account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID') - role = models.ForeignKey(Role, on_delete=models.CASCADE, db_index=True, verbose_name='角色') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'user_role' - verbose_name = '用户角色关联' - verbose_name_plural = '用户角色关联' - managed = True - unique_together = [['account_id', 'role']] - - def __str__(self): - return f"{self.account_id} -> {self.role.role_name}" - - -# ==================== 5. 异常用户日志 ==================== -class AbnormalUserLog(QModel): - """记录恶意尝试越权操作的用户信息""" - account_id = models.CharField(max_length=20, db_index=True, verbose_name='账号ID(phone)') - ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP地址') - real_ip = models.GenericIPAddressField(null=True, blank=True, verbose_name='真实IP(穿透代理后)') - x_forwarded_for = models.CharField(max_length=255, blank=True, default='', verbose_name='X-Forwarded-For') - user_agent = models.CharField(max_length=500, blank=True, default='', verbose_name='User-Agent') - request_method = models.CharField(max_length=10, blank=True, default='', verbose_name='请求方法') - request_path = models.CharField(max_length=500, blank=True, default='', verbose_name='请求路径') - request_params = models.TextField(blank=True, default='', verbose_name='请求参数(GET/POST)') - request_body = models.TextField(blank=True, default='', verbose_name='请求体(原始)') - request_headers = models.TextField(blank=True, default='', verbose_name='请求头(JSON格式)') - attempt_type = models.CharField(max_length=50, blank=True, default='', verbose_name='攻击类型') - detail = models.TextField(blank=True, default='', verbose_name='详细描述') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'abnormal_user_log' - verbose_name = '异常用户日志' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['account_id']), - models.Index(fields=['ip_address']), - models.Index(fields=['create_time']), - ] - - def __str__(self): - return f"{self.account_id} @ {self.ip_address} - {self.create_time}" - - -# ==================== 6. 商家每日统计 ==================== -class MerchantDailyStats(QModel): - """商家每日统计表(原 ShangjiaRiTongji)""" - MerchantID = models.CharField( - max_length=7, db_index=True, db_column='yonghuid', verbose_name='商家用户ID') - Date = models.DateField( - db_index=True, db_column='riqi', verbose_name='日期') - - # 派发统计 - AssignedOrderCount = models.IntegerField( - default=0, db_column='paifa_dingdan_shu', verbose_name='派发订单总数') - AssignedAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='paifa_jine', verbose_name='派发订单总金额') - - # 结算统计 - SettledOrderCount = models.IntegerField( - default=0, db_column='jiesuan_dingdan_shu', verbose_name='结算订单总数') - SettledAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='jiesuan_jine', verbose_name='结算总金额') - - # 退款统计 - RefundOrderCount = models.IntegerField( - default=0, db_column='tuikuan_dingdan_shu', verbose_name='退款订单总数') - RefundAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='tuikuan_jine', verbose_name='退款总金额') - - class Meta: - db_table = 'shangjia_ri_tongji' - verbose_name = '商家每日统计' - unique_together = [['MerchantID', 'Date']] - indexes = [ - models.Index(fields=['Date', '-AssignedAmount']), - models.Index(fields=['Date', '-SettledAmount']), - models.Index(fields=['Date', '-AssignedOrderCount']), - ] - - def __str__(self): - return f"商家{self.MerchantID} {self.Date}" - - -# ==================== 7. 打手每日统计 ==================== -class PlayerDailyStats(QModel): - """打手每日统计表(原 DashouRiTongji)""" - PlayerID = models.CharField( - max_length=7, db_index=True, db_column='yonghuid', verbose_name='打手用户ID') - Date = models.DateField( - db_index=True, db_column='riqi', verbose_name='日期') - - # 接单统计 - AcceptedOrderTotal = models.IntegerField( - default=0, db_column='jiedan_zongliang', verbose_name='接单总量') - CompletedOrderTotal = models.IntegerField( - default=0, db_column='chengjiao_zongliang', verbose_name='成交单总量') - - # 金额统计 - AcceptedAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='jiedan_zonge', verbose_name='接单总金额') - CompletedAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='chengjiao_zonge', verbose_name='成交总金额') - - # 退款统计 - RefundCount = models.IntegerField( - default=0, db_column='tuikuan_liang', verbose_name='退款订单量') - RefundAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='tuikuan_jine', verbose_name='退款总金额') - - class Meta: - db_table = 'dashou_ri_tongji' - verbose_name = '打手每日统计' - unique_together = [['PlayerID', 'Date']] - indexes = [ - models.Index(fields=['Date', '-AcceptedOrderTotal']), - models.Index(fields=['Date', '-CompletedOrderTotal']), - models.Index(fields=['Date', '-AcceptedAmount']), - models.Index(fields=['Date', '-CompletedAmount']), - ] - - def __str__(self): - return f"打手{self.PlayerID} {self.Date}" - - -# ==================== 8. 组长每日统计 ==================== -class LeaderDailyStats(QModel): - """组长每日统计表(原 ZuzhangRiTongji)""" - LeaderID = models.CharField( - max_length=7, db_index=True, db_column='yonghuid', verbose_name='组长用户ID') - Date = models.DateField( - db_index=True, db_column='riqi', verbose_name='日期') - - InvitedManagerCount = models.IntegerField( - default=0, db_column='yaoqing_guanshi_shu', verbose_name='邀请管事总数') - TotalIncome = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='shouru_zonge', verbose_name='收入总额(分佣)') - CommissionAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='fenyong_jine', verbose_name='分佣金额') - - class Meta: - db_table = 'zuzhang_ri_tongji' - verbose_name = '组长每日统计' - unique_together = [['LeaderID', 'Date']] - indexes = [ - models.Index(fields=['Date', '-InvitedManagerCount']), - models.Index(fields=['Date', '-TotalIncome']), - ] - - def __str__(self): - return f"组长{self.LeaderID} {self.Date}" - - -# ==================== 9. 管事每日统计 ==================== -class ManagerDailyStats(QModel): - """管事每日统计表(原 GuanshiRiTongji)""" - ManagerID = models.CharField( - max_length=7, db_index=True, db_column='yonghuid', verbose_name='管事用户ID') - Date = models.DateField( - db_index=True, db_column='riqi', verbose_name='日期') - - InvitedPlayerCount = models.IntegerField( - default=0, db_column='yaoqing_dashou_shu', verbose_name='邀请打手总数') - RechargedPlayerCount = models.IntegerField( - default=0, db_column='chongzhi_dashou_shu', verbose_name='充值打手数量') - TotalIncome = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='shouru_zonge', verbose_name='收入总额') - - class Meta: - db_table = 'guanshi_ri_tongji' - verbose_name = '管事每日统计' - unique_together = [['ManagerID', 'Date']] - indexes = [ - models.Index(fields=['Date', '-InvitedPlayerCount']), - models.Index(fields=['Date', '-RechargedPlayerCount']), - models.Index(fields=['Date', '-TotalIncome']), - ] - - def __str__(self): - return f"管事{self.ManagerID} {self.Date}" - - -# ==================== 10. 管事续费每日统计 ==================== -class ManagerRenewalDailyStats(QModel): - """管事续费每日统计表(原 GuanshiXufeiRiTongji)""" - ManagerID = models.CharField( - max_length=7, db_index=True, db_column='yonghuid', verbose_name='管事用户ID') - Date = models.DateField( - db_index=True, db_column='riqi', verbose_name='日期') - - RenewalTotal = models.IntegerField( - default=0, db_column='xufei_zongshu', verbose_name='今日续费总数') - RenewalRevenue = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='xufei_shouyi', verbose_name='续费收益金额') - - class Meta: - db_table = 'guanshi_xufei_ri_tongji' - verbose_name = '管事续费每日统计' - unique_together = [['ManagerID', 'Date']] - indexes = [ - models.Index(fields=['Date', '-RenewalTotal']), - models.Index(fields=['Date', '-RenewalRevenue']), - ] - - def __str__(self): - return f"管事续费{self.ManagerID} {self.Date}" - - -# ==================== 11. 提现每日统计 ==================== -class WithdrawalDailyStats(QModel): - """每日提现统计表(原 TixianRiTongji),按身份统计当天提现总额""" - Date = models.DateField( - db_index=True, db_column='riqi', verbose_name='日期') - WithdrawalType = models.IntegerField( - choices=[(1, '打手'), (2, '管事'), (3, '组长'), - (4, '考核官'), (5, '打手押金'), (6, '商家余额')], - db_column='leixing', verbose_name='用户类型') - total_amount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, verbose_name='当天提现总额(元)') - - class Meta: - db_table = 'tixian_ri_tongji' - verbose_name = '每日提现统计' - unique_together = [['Date', 'WithdrawalType']] - indexes = [models.Index(fields=['Date', 'WithdrawalType'])] - - def __str__(self): - return f"{self.Date} - {self.get_WithdrawalType_display()} 提现总额: {self.total_amount}" - - -# ==================== 兼容别名(供逐步迁移) ==================== -ShangjiaRiTongji = MerchantDailyStats -DashouRiTongji = PlayerDailyStats -ZuzhangRiTongji = LeaderDailyStats -GuanshiRiTongji = ManagerDailyStats -GuanshiXufeiRiTongji = ManagerRenewalDailyStats -TixianRiTongji = WithdrawalDailyStats +from django.db import models +from gvsdsdk.model_base import QModel + + +# ==================== 1. 角色表 ==================== +class Role(QModel): + """角色表""" + role_id = models.AutoField(primary_key=True, verbose_name='角色ID') + role_code = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='角色编码') + role_name = models.CharField(max_length=50, verbose_name='角色名称') + description = models.CharField(max_length=200, blank=True, default='', verbose_name='角色描述') + + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'role' + verbose_name = '角色' + verbose_name_plural = '角色' + managed = True + indexes = [ + models.Index(fields=['role_code']), + ] + + def __str__(self): + return self.role_name + + +# ==================== 2. 权限表 ==================== +class Permission(QModel): + """权限表""" + perm_id = models.AutoField(primary_key=True, verbose_name='权限ID') + perm_code = models.CharField(max_length=100, unique=True, db_index=True, verbose_name='权限编码') + perm_name = models.CharField(max_length=100, verbose_name='权限名称') + description = models.CharField(max_length=200, blank=True, default='', verbose_name='权限描述') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'permission' + verbose_name = '权限' + verbose_name_plural = '权限' + managed = True + indexes = [ + models.Index(fields=['perm_code']), + ] + + def __str__(self): + return f"{self.perm_name} ({self.perm_code})" + + +# ==================== 3. 角色-权限关联表 ==================== +class RolePermission(QModel): + """角色权限关联表""" + id = models.AutoField(primary_key=True) + role = models.ForeignKey(Role, on_delete=models.CASCADE, db_index=True, verbose_name='角色') + permission = models.ForeignKey(Permission, on_delete=models.CASCADE, db_index=True, verbose_name='权限') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'role_permission' + verbose_name = '角色权限关联' + verbose_name_plural = '角色权限关联' + managed = True + unique_together = [['role', 'permission']] + + def __str__(self): + return f"{self.role.role_name} -> {self.permission.perm_name}" + + +# ==================== 4. 用户-角色关联表 ==================== +class UserRole(QModel): + """用户角色关联表""" + id = models.AutoField(primary_key=True) + account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID') + role = models.ForeignKey(Role, on_delete=models.CASCADE, db_index=True, verbose_name='角色') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'user_role' + verbose_name = '用户角色关联' + verbose_name_plural = '用户角色关联' + managed = True + unique_together = [['account_id', 'role']] + + def __str__(self): + return f"{self.account_id} -> {self.role.role_name}" + + +# ==================== 5. 异常用户日志 ==================== +class AbnormalUserLog(QModel): + """记录恶意尝试越权操作的用户信息""" + account_id = models.CharField(max_length=20, db_index=True, verbose_name='账号ID(phone)') + ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP地址') + real_ip = models.GenericIPAddressField(null=True, blank=True, verbose_name='真实IP(穿透代理后)') + x_forwarded_for = models.CharField(max_length=255, blank=True, default='', verbose_name='X-Forwarded-For') + user_agent = models.CharField(max_length=500, blank=True, default='', verbose_name='User-Agent') + request_method = models.CharField(max_length=10, blank=True, default='', verbose_name='请求方法') + request_path = models.CharField(max_length=500, blank=True, default='', verbose_name='请求路径') + request_params = models.TextField(blank=True, default='', verbose_name='请求参数(GET/POST)') + request_body = models.TextField(blank=True, default='', verbose_name='请求体(原始)') + request_headers = models.TextField(blank=True, default='', verbose_name='请求头(JSON格式)') + attempt_type = models.CharField(max_length=50, blank=True, default='', verbose_name='攻击类型') + detail = models.TextField(blank=True, default='', verbose_name='详细描述') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'abnormal_user_log' + verbose_name = '异常用户日志' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['account_id']), + models.Index(fields=['ip_address']), + models.Index(fields=['CreateTime']), + ] + + def __str__(self): + return f"{self.account_id} @ {self.ip_address} - {self.CreateTime}" + + +# ==================== 6. 商家每日统计 ==================== +class MerchantDailyStats(QModel): + """商家每日统计表(原 ShangjiaRiTongji)""" + MerchantID = models.CharField( + max_length=7, db_index=True, db_column='yonghuid', verbose_name='商家用户ID') + Date = models.DateField( + db_index=True, db_column='riqi', verbose_name='日期') + + # 派发统计 + AssignedOrderCount = models.IntegerField( + default=0, db_column='paifa_dingdan_shu', verbose_name='派发订单总数') + AssignedAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='paifa_jine', verbose_name='派发订单总金额') + + # 结算统计 + SettledOrderCount = models.IntegerField( + default=0, db_column='jiesuan_dingdan_shu', verbose_name='结算订单总数') + SettledAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='jiesuan_jine', verbose_name='结算总金额') + + # 退款统计 + RefundOrderCount = models.IntegerField( + default=0, db_column='tuikuan_dingdan_shu', verbose_name='退款订单总数') + RefundAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='tuikuan_jine', verbose_name='退款总金额') + + class Meta: + db_table = 'shangjia_ri_tongji' + verbose_name = '商家每日统计' + unique_together = [['MerchantID', 'Date']] + indexes = [ + models.Index(fields=['Date', '-AssignedAmount']), + models.Index(fields=['Date', '-SettledAmount']), + models.Index(fields=['Date', '-AssignedOrderCount']), + ] + + def __str__(self): + return f"商家{self.MerchantID} {self.Date}" + + +# ==================== 7. 打手每日统计 ==================== +class PlayerDailyStats(QModel): + """打手每日统计表(原 DashouRiTongji)""" + PlayerID = models.CharField( + max_length=7, db_index=True, db_column='yonghuid', verbose_name='打手用户ID') + Date = models.DateField( + db_index=True, db_column='riqi', verbose_name='日期') + + # 接单统计 + AcceptedOrderTotal = models.IntegerField( + default=0, db_column='jiedan_zongliang', verbose_name='接单总量') + CompletedOrderTotal = models.IntegerField( + default=0, db_column='chengjiao_zongliang', verbose_name='成交单总量') + + # 金额统计 + AcceptedAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='jiedan_zonge', verbose_name='接单总金额') + CompletedAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='chengjiao_zonge', verbose_name='成交总金额') + + # 退款统计 + RefundCount = models.IntegerField( + default=0, db_column='tuikuan_liang', verbose_name='退款订单量') + RefundAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='tuikuan_jine', verbose_name='退款总金额') + + class Meta: + db_table = 'dashou_ri_tongji' + verbose_name = '打手每日统计' + unique_together = [['PlayerID', 'Date']] + indexes = [ + models.Index(fields=['Date', '-AcceptedOrderTotal']), + models.Index(fields=['Date', '-CompletedOrderTotal']), + models.Index(fields=['Date', '-AcceptedAmount']), + models.Index(fields=['Date', '-CompletedAmount']), + ] + + def __str__(self): + return f"打手{self.PlayerID} {self.Date}" + + +# ==================== 8. 组长每日统计 ==================== +class LeaderDailyStats(QModel): + """组长每日统计表(原 ZuzhangRiTongji)""" + LeaderID = models.CharField( + max_length=7, db_index=True, db_column='yonghuid', verbose_name='组长用户ID') + Date = models.DateField( + db_index=True, db_column='riqi', verbose_name='日期') + + InvitedManagerCount = models.IntegerField( + default=0, db_column='yaoqing_guanshi_shu', verbose_name='邀请管事总数') + TotalIncome = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='shouru_zonge', verbose_name='收入总额(分佣)') + CommissionAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='fenyong_jine', verbose_name='分佣金额') + + class Meta: + db_table = 'zuzhang_ri_tongji' + verbose_name = '组长每日统计' + unique_together = [['LeaderID', 'Date']] + indexes = [ + models.Index(fields=['Date', '-InvitedManagerCount']), + models.Index(fields=['Date', '-TotalIncome']), + ] + + def __str__(self): + return f"组长{self.LeaderID} {self.Date}" + + +# ==================== 9. 管事每日统计 ==================== +class ManagerDailyStats(QModel): + """管事每日统计表(原 GuanshiRiTongji)""" + ManagerID = models.CharField( + max_length=7, db_index=True, db_column='yonghuid', verbose_name='管事用户ID') + Date = models.DateField( + db_index=True, db_column='riqi', verbose_name='日期') + + InvitedPlayerCount = models.IntegerField( + default=0, db_column='yaoqing_dashou_shu', verbose_name='邀请打手总数') + RechargedPlayerCount = models.IntegerField( + default=0, db_column='chongzhi_dashou_shu', verbose_name='充值打手数量') + TotalIncome = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='shouru_zonge', verbose_name='收入总额') + + class Meta: + db_table = 'guanshi_ri_tongji' + verbose_name = '管事每日统计' + unique_together = [['ManagerID', 'Date']] + indexes = [ + models.Index(fields=['Date', '-InvitedPlayerCount']), + models.Index(fields=['Date', '-RechargedPlayerCount']), + models.Index(fields=['Date', '-TotalIncome']), + ] + + def __str__(self): + return f"管事{self.ManagerID} {self.Date}" + + +# ==================== 10. 管事续费每日统计 ==================== +class ManagerRenewalDailyStats(QModel): + """管事续费每日统计表(原 GuanshiXufeiRiTongji)""" + ManagerID = models.CharField( + max_length=7, db_index=True, db_column='yonghuid', verbose_name='管事用户ID') + Date = models.DateField( + db_index=True, db_column='riqi', verbose_name='日期') + + RenewalTotal = models.IntegerField( + default=0, db_column='xufei_zongshu', verbose_name='今日续费总数') + RenewalRevenue = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='xufei_shouyi', verbose_name='续费收益金额') + + class Meta: + db_table = 'guanshi_xufei_ri_tongji' + verbose_name = '管事续费每日统计' + unique_together = [['ManagerID', 'Date']] + indexes = [ + models.Index(fields=['Date', '-RenewalTotal']), + models.Index(fields=['Date', '-RenewalRevenue']), + ] + + def __str__(self): + return f"管事续费{self.ManagerID} {self.Date}" + + +# ==================== 11. 提现每日统计 ==================== +class WithdrawalDailyStats(QModel): + """每日提现统计表(原 TixianRiTongji),按身份统计当天提现总额""" + Date = models.DateField( + db_index=True, db_column='riqi', verbose_name='日期') + WithdrawalType = models.IntegerField( + choices=[(1, '打手'), (2, '管事'), (3, '组长'), + (4, '考核官'), (5, '打手押金'), (6, '商家余额')], + db_column='leixing', verbose_name='用户类型') + total_amount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, verbose_name='当天提现总额(元)') + + class Meta: + db_table = 'tixian_ri_tongji' + verbose_name = '每日提现统计' + unique_together = [['Date', 'WithdrawalType']] + indexes = [models.Index(fields=['Date', 'WithdrawalType'])] + + def __str__(self): + return f"{self.Date} - {self.get_WithdrawalType_display()} 提现总额: {self.total_amount}" + + +# ==================== 兼容别名(供逐步迁移) ==================== +ShangjiaRiTongji = MerchantDailyStats +DashouRiTongji = PlayerDailyStats +ZuzhangRiTongji = LeaderDailyStats +GuanshiRiTongji = ManagerDailyStats +GuanshiXufeiRiTongji = ManagerRenewalDailyStats +TixianRiTongji = WithdrawalDailyStats diff --git a/backend/view.py b/backend/view.py index 514f536..0c05aa3 100644 --- a/backend/view.py +++ b/backend/view.py @@ -370,8 +370,8 @@ class GetAdminUserListView(APIView): 'nicheng': user.KefuProfile.nicheng, 'roles': roles_data, 'status': user.KefuProfile.zhuangtai, - 'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if hasattr(user, 'UserCreateTime') else '', - 'update_time': user.update_time.strftime('%Y-%m-%d %H:%M:%S') if hasattr(user, 'update_time') else '', + 'CreateTime': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if hasattr(user, 'UserCreateTime') else '', + 'UpdateTime': user.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if hasattr(user, 'UpdateTime') else '', }) return Response({ @@ -452,7 +452,7 @@ class GetAdminRolesView(APIView): users = users.filter(phone__in=user_ids_with_role) # 排序 - users = users.order_by('-create_time') + users = users.order_by('-CreateTime') # 分页 paginator = Paginator(users, page_size) @@ -469,8 +469,8 @@ class GetAdminRolesView(APIView): 'nicheng': user.KefuProfile.nicheng, 'roles': roles_data, 'status': user.KefuProfile.zhuangtai, - 'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S'), - 'update_time': user.update_time.strftime('%Y-%m-%d %H:%M:%S'), + 'CreateTime': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S'), + 'UpdateTime': user.UpdateTime.strftime('%Y-%m-%d %H:%M:%S'), }) return Response({ @@ -737,7 +737,7 @@ class KefuGetDashouListView(APIView): # 7. 分页 offset = (page - 1) * page_size - dashou_list = dashou_qs.order_by('-create_time')[offset:offset + page_size] + dashou_list = dashou_qs.order_by('-CreateTime')[offset:offset + page_size] # 8. 构建返回数据 result = [] @@ -821,7 +821,7 @@ class KefuGetDashouDetailView(APIView): 'dianhua': '', 'wechat': '', 'phone': user_main.Phone or '', - 'create_time': user_main.UserCreateTime, + 'CreateTime': user_main.UserCreateTime, 'ip': user_main.IP or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, @@ -861,7 +861,7 @@ class KefuGetDashouDetailView(APIView): 'dianhua': dashou.dianhua or '', 'wechat': dashou.wechat or '', 'phone': user_main.Phone or '', - 'create_time': user_main.UserCreateTime, + 'CreateTime': user_main.UserCreateTime, 'ip': user_main.IP or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, @@ -880,7 +880,7 @@ class KefuGetDashouDetailView(APIView): # 6. 查询打手已拥有的会员(未过期) member_records = Huiyuangoumai.query.filter( yonghu_id=uid - ).order_by('-create_time') + ).order_by('-CreateTime') member_list = [] for record in member_records: # 检查是否过期 @@ -980,7 +980,7 @@ class KefuGetDashouDetailView(APIView): 'dianhua': '', 'wechat': '', 'phone': user_main.Phone or '', - 'create_time': user_main.UserCreateTime, + 'CreateTime': user_main.UserCreateTime, 'ip': user_main.IP or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, @@ -1016,7 +1016,7 @@ class KefuGetDashouDetailView(APIView): 'dianhua': dashou.dianhua or '', 'wechat': dashou.wechat or '', 'phone': user_main.Phone or '', - 'create_time': user_main.UserCreateTime, + 'CreateTime': user_main.UserCreateTime, 'ip': user_main.IP or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, @@ -1035,7 +1035,7 @@ class KefuGetDashouDetailView(APIView): # 5. 查询打手会员信息(仅未过期) member_records = Huiyuangoumai.query.filter( yonghu_id=uid - ).order_by('-create_time') + ).order_by('-CreateTime') member_list = [] for record in member_records: @@ -1473,7 +1473,7 @@ class KefuGetShangjiaListView(APIView): # 6. 分页 offset = (page - 1) * page_size - shangjia_list = shangjia_qs.order_by('-create_time')[offset:offset + page_size] + shangjia_list = shangjia_qs.order_by('-CreateTime')[offset:offset + page_size] # 7. 构建返回数据 result = [] @@ -1560,7 +1560,7 @@ class KefuGetShangjiaDetailView(APIView): 'jinriliushui': str(shangjia.jinriliushui), 'jinyuedingdan': shangjia.jinyuedingdan, 'jinyueliushui': str(shangjia.jinyueliushui), - 'create_time': shangjia.create_time.isoformat() if shangjia.create_time else None, + 'CreateTime': shangjia.CreateTime.isoformat() if shangjia.CreateTime else None, } return Response({'code': 0, 'data': data}) @@ -1850,7 +1850,7 @@ class GetProductListView(APIView): else: qs = qs.order_by(f'-{sort_field}') else: - qs = qs.order_by('-create_time') # 默认按创建时间倒序 + qs = qs.order_by('-CreateTime') # 默认按创建时间倒序 # 分页 total = qs.count() @@ -2052,7 +2052,7 @@ class GetProductListView(APIView): else: qs = qs.order_by(f'-{sort_field}') else: - qs = qs.order_by('-create_time') # 默认按创建时间倒序 + qs = qs.order_by('-CreateTime') # 默认按创建时间倒序 # 分页 total = qs.count() @@ -2465,7 +2465,7 @@ class GetProductDetailView(APIView): 'tupian_url': product.tupian_url, 'kaioi_ewai_PlayerCommission': product.kaioi_ewai_dashou_fencheng, 'ewai_PlayerCommission': str(product.ewai_dashou_fencheng) if product.ewai_dashou_fencheng is not None else '0', - 'create_time': product.create_time.isoformat() if product.create_time else None, + 'CreateTime': product.CreateTime.isoformat() if product.CreateTime else None, 'shenhezhuangtai': product.shenhezhuangtai, } @@ -2685,7 +2685,7 @@ class GetMemberListView(APIView): if '3300a' not in permissions: return Response({'code': 403, 'msg': '无权限查看会员列表'}) - members = Huiyuan.query.all().order_by('-create_time') + members = Huiyuan.query.all().order_by('-CreateTime') member_list = [] for m in members: member_list.append({ @@ -2696,8 +2696,8 @@ class GetMemberListView(APIView): 'guanshifc': str(m.guanshifc), 'zuzhangfc': str(m.zuzhangfc), 'goumai_cishu': m.goumai_cishu, - 'create_time': m.create_time.isoformat() if m.create_time else None, - 'update_time': m.update_time.isoformat() if m.update_time else None, + 'CreateTime': m.CreateTime.isoformat() if m.CreateTime else None, + 'UpdateTime': m.UpdateTime.isoformat() if m.UpdateTime else None, }) return Response({'code': 0, 'data': {'list': member_list}}) @@ -3034,7 +3034,7 @@ class GetGuanliDetailView(APIView): 'jinrichongzhi': guanshi.jinrichongzhi, 'jinyuechongzhi': guanshi.jinyuechongzhi, 'chongzhifenrun': str(guanshi.chongzhifenrun), - 'create_time': guanshi.create_time.isoformat() if guanshi.create_time else None, + 'CreateTime': guanshi.CreateTime.isoformat() if guanshi.CreateTime else None, } # 余额提现相关 @@ -3726,7 +3726,7 @@ class GetZuzhangDetailView(APIView): 'jinri_fenyong': str(zuzhang.jinri_fenyong), 'jinyue_fenyong': str(zuzhang.jinyue_fenyong), 'fenyong_zonge': str(zuzhang.fenyong_zonge), - 'create_time': zuzhang.create_time.isoformat() if zuzhang.create_time else None, + 'CreateTime': zuzhang.CreateTime.isoformat() if zuzhang.CreateTime else None, } # 余额提现相关 @@ -5567,8 +5567,8 @@ class ProductListView(APIView): 'yongjin': p.yongjin, 'kaioi_ewai_PlayerCommission': p.kaioi_ewai_dashou_fencheng, 'ewai_PlayerCommission': p.ewai_dashou_fencheng, - 'create_time': p.create_time.isoformat() if p.create_time else '', - 'update_time': p.update_time.isoformat() if p.update_time else '', + 'CreateTime': p.CreateTime.isoformat() if p.CreateTime else '', + 'UpdateTime': p.UpdateTime.isoformat() if p.UpdateTime else '', 'paixu': p.paixu, 'dianpu_id': p.dianpu_id, 'dianpu_name': p.dianpu.dianpu_mingcheng if p.dianpu else '', @@ -5873,7 +5873,7 @@ class FaKuanLieBiaoView(APIView): 'zhengju_tupian': zhengju_map.get(r.id, []), 'shensu_tupian': shensu_map.get(r.id, []), 'creat_time': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '', - 'update_time': r.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if r.UpdateTime else '', + 'UpdateTime': r.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if r.UpdateTime else '', }) return Response({ @@ -6278,7 +6278,7 @@ class HqbkxxView(APIView): bankuai_list.append({ 'bankuai_id': bk.bankuai_id, 'mingcheng': bk.mingcheng, - 'create_time': bk.create_time.strftime('%Y-%m-%d %H:%M:%S') if bk.create_time else '' + 'CreateTime': bk.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if bk.CreateTime else '' }) # 6. 查询所有商品类型 @@ -6514,8 +6514,8 @@ class CaiwuView(APIView): # 今日充值(会员充值,leixing=1, zhuangtai=3) today_chongzhi = Czjilu.query.filter( - create_time__gte=today_start, - create_time__lt=today_end, + CreateTime__gte=today_start, + CreateTime__lt=today_end, leixing=1, zhuangtai=3 ) @@ -6524,8 +6524,8 @@ class CaiwuView(APIView): # 今日新增会员:从购买记录表统计今天创建的用户(去重) new_huiyuan_user_ids = Huiyuangoumai.query.filter( - create_time__gte=today_start, - create_time__lt=today_end + CreateTime__gte=today_start, + CreateTime__lt=today_end ).values_list('yonghu_id', flat=True).distinct() today_new_huiyuan = len(new_huiyuan_user_ids) @@ -6533,8 +6533,8 @@ class CaiwuView(APIView): today_new_huiyuan_amount = 0.0 if new_huiyuan_user_ids: amount_sum = Czjilu.query.filter( - create_time__gte=today_start, - create_time__lt=today_end, + CreateTime__gte=today_start, + CreateTime__lt=today_end, UserUID__in=new_huiyuan_user_ids, leixing=1, zhuangtai=3 @@ -6685,7 +6685,7 @@ class HybkjtsjView(APIView): end_date = date(year + 1, 1, 1) else: end_date = date(year, month + 1, 1) - trunc_func = TruncDate('create_time') + trunc_func = TruncDate('CreateTime') if not summary_date: summary_date = now.strftime('%Y-%m-%d') summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date() @@ -6694,7 +6694,7 @@ class HybkjtsjView(APIView): return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400) start_date = date(year, 1, 1) end_date = date(year + 1, 1, 1) - trunc_func = TruncMonth('create_time') + trunc_func = TruncMonth('CreateTime') if not summary_date: summary_date = now.strftime('%Y-%m') summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date() @@ -6702,7 +6702,7 @@ class HybkjtsjView(APIView): year = year or now.year start_date = date(year, 1, 1) end_date = date(year + 1, 1, 1) - trunc_func = TruncYear('create_time') + trunc_func = TruncYear('CreateTime') if not summary_date: summary_date = str(year) summary_q = date(year, 1, 1) @@ -6718,15 +6718,15 @@ class HybkjtsjView(APIView): 'huiyuan_id': huiyuan_id, # 会员ID筛选 'leixing': 1, 'zhuangtai': 3, - 'create_time__gte': start_dt, - 'create_time__lt': end_dt + 'CreateTime__gte': start_dt, + 'CreateTime__lt': end_dt } # ---------- 分红过滤 ---------- fenhong_filter = { 'huiyuan_id': huiyuan_id, # 分红表会员ID筛选 - 'create_time__gte': start_dt, - 'create_time__lt': end_dt + 'CreateTime__gte': start_dt, + 'CreateTime__lt': end_dt } # 充值分组查询 @@ -6787,12 +6787,12 @@ class HybkjtsjView(APIView): # ---------- 汇总数据 ---------- summary_chongzhi_filter = chongzhi_filter.copy() if granularity == 'day': - summary_chongzhi_filter['create_time__date'] = summary_q + summary_chongzhi_filter['CreateTime__date'] = summary_q elif granularity == 'month': - summary_chongzhi_filter['create_time__year'] = summary_q.year - summary_chongzhi_filter['create_time__month'] = summary_q.month + summary_chongzhi_filter['CreateTime__year'] = summary_q.year + summary_chongzhi_filter['CreateTime__month'] = summary_q.month else: - summary_chongzhi_filter['create_time__year'] = summary_q.year + summary_chongzhi_filter['CreateTime__year'] = summary_q.year summary_chongzhi = Czjilu.query.filter(**summary_chongzhi_filter).aggregate( total_count=Count('id'), @@ -6806,12 +6806,12 @@ class HybkjtsjView(APIView): if include_guanshi or include_zuzhang: summary_fenhong_filter = fenhong_filter.copy() if granularity == 'day': - summary_fenhong_filter['create_time__date'] = summary_q + summary_fenhong_filter['CreateTime__date'] = summary_q elif granularity == 'month': - summary_fenhong_filter['create_time__year'] = summary_q.year - summary_fenhong_filter['create_time__month'] = summary_q.month + summary_fenhong_filter['CreateTime__year'] = summary_q.year + summary_fenhong_filter['CreateTime__month'] = summary_q.month else: - summary_fenhong_filter['create_time__year'] = summary_q.year + summary_fenhong_filter['CreateTime__year'] = summary_q.year fenhong_agg = {} if include_guanshi: @@ -7261,7 +7261,7 @@ class CwqtczhqView(APIView): return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400) start_date = date(year, month, 1) end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1) - trunc_func = TruncDate('create_time') + trunc_func = TruncDate('CreateTime') # 默认汇总日期 if not summary_date: summary_date = now.strftime('%Y-%m-%d') @@ -7271,7 +7271,7 @@ class CwqtczhqView(APIView): return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400) start_date = date(year, 1, 1) end_date = date(year + 1, 1, 1) - trunc_func = TruncMonth('create_time') + trunc_func = TruncMonth('CreateTime') if not summary_date: summary_date = now.strftime('%Y-%m') summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date() @@ -7279,7 +7279,7 @@ class CwqtczhqView(APIView): year = year or now.year start_date = date(year, 1, 1) end_date = date(year + 1, 1, 1) - trunc_func = TruncYear('create_time') + trunc_func = TruncYear('CreateTime') if not summary_date: summary_date = str(year) summary_q = date(year, 1, 1) @@ -7301,8 +7301,8 @@ class CwqtczhqView(APIView): cz_types = [t for t in types if t in [2, 3, 4, 5]] if cz_types: cz_qs = Czjilu.query.filter( - create_time__gte=start_dt, - create_time__lt=end_dt, + CreateTime__gte=start_dt, + CreateTime__lt=end_dt, leixing__in=cz_types, zhuangtai=3 # 只统计支付成功的 ).annotate( @@ -7382,12 +7382,12 @@ class CwqtczhqView(APIView): 'zhuangtai': 3, } if granularity == 'day': - filter_kwargs['create_time__date'] = summary_q + filter_kwargs['CreateTime__date'] = summary_q elif granularity == 'month': - filter_kwargs['create_time__year'] = summary_q.year - filter_kwargs['create_time__month'] = summary_q.month + filter_kwargs['CreateTime__year'] = summary_q.year + filter_kwargs['CreateTime__month'] = summary_q.month else: - filter_kwargs['create_time__year'] = summary_q.year + filter_kwargs['CreateTime__year'] = summary_q.year agg = Czjilu.query.filter(**filter_kwargs).aggregate(count=Count('id'), amount=Sum('jine')) count = agg['count'] or 0 amount = float(agg['amount'] or 0) @@ -7496,7 +7496,7 @@ class CwqtczhqView(APIView): return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400) start_date = date(year, month, 1) end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1) - trunc_func = TruncDate('create_time') + trunc_func = TruncDate('CreateTime') # 默认汇总日期 if not summary_date: summary_date = now.strftime('%Y-%m-%d') @@ -7506,7 +7506,7 @@ class CwqtczhqView(APIView): return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400) start_date = date(year, 1, 1) end_date = date(year + 1, 1, 1) - trunc_func = TruncMonth('create_time') + trunc_func = TruncMonth('CreateTime') if not summary_date: summary_date = now.strftime('%Y-%m') summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date() @@ -7514,7 +7514,7 @@ class CwqtczhqView(APIView): year = year or now.year start_date = date(year, 1, 1) end_date = date(year + 1, 1, 1) - trunc_func = TruncYear('create_time') + trunc_func = TruncYear('CreateTime') if not summary_date: summary_date = str(year) summary_q = date(year, 1, 1) @@ -7536,8 +7536,8 @@ class CwqtczhqView(APIView): cz_types = [t for t in types if t in [2, 3, 4, 5]] if cz_types: cz_qs = Czjilu.query.filter( - create_time__gte=start_dt, - create_time__lt=end_dt, + CreateTime__gte=start_dt, + CreateTime__lt=end_dt, leixing__in=cz_types, zhuangtai=3 # 只统计支付成功的 ).annotate( @@ -7639,12 +7639,12 @@ class CwqtczhqView(APIView): 'zhuangtai': 3, } if granularity == 'day': - filter_kwargs['create_time__date'] = summary_q + filter_kwargs['CreateTime__date'] = summary_q elif granularity == 'month': - filter_kwargs['create_time__year'] = summary_q.year - filter_kwargs['create_time__month'] = summary_q.month + filter_kwargs['CreateTime__year'] = summary_q.year + filter_kwargs['CreateTime__month'] = summary_q.month else: - filter_kwargs['create_time__year'] = summary_q.year + filter_kwargs['CreateTime__year'] = summary_q.year agg = Czjilu.query.filter(**filter_kwargs).aggregate(count=Count('id'), amount=Sum('jine')) count = agg['count'] or 0 amount = float(agg['amount'] or 0) @@ -7760,7 +7760,7 @@ class KhpzhqView(APIView): 'bankuai_id': ch.bankuai_id, 'tubiao_url': ch.tubiao_url or '', 'texiao_miaoshu': ch.texiao_miaoshu or '', - 'create_time': ch.create_time.strftime('%Y-%m-%d %H:%M:%S') if ch.create_time else '', + 'CreateTime': ch.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if ch.CreateTime else '', 'kaohe_guize': ch.kaohe_guize or '', 'kaioi_jinpai': ch.kaioi_jinpai, 'feiyong': feiyong_dict, # {1:100.0, 2:200.0} @@ -8016,7 +8016,7 @@ class KhgglView(APIView): total = queryset.count() start = (page - 1) * page_size end = start + page_size - shenheguan_list = queryset.order_by('-create_time')[start:end] + shenheguan_list = queryset.order_by('-CreateTime')[start:end] # ---------- 预计算每个板块的打手数量 ---------- bankuai_dashou_count_map = {} @@ -8074,8 +8074,8 @@ class KhgglView(APIView): 'tongguo_zongshu': sg.tongguo_zongshu, 'yue': float(sg.yue), 'zonge': float(sg.zonge), - 'create_time': sg.create_time.strftime('%Y-%m-%d %H:%M:%S') if sg.create_time else '', - 'update_time': sg.update_time.strftime('%Y-%m-%d %H:%M:%S') if sg.update_time else '', + 'CreateTime': sg.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if sg.CreateTime else '', + 'UpdateTime': sg.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if sg.UpdateTime else '', 'bankuai': bankuai_info, 'nicheng': dashou_nick, 'is_renzheng': sg.is_renzheng, diff --git a/config/migrations/0001_initial.py b/config/migrations/0001_initial.py index c091182..309e787 100644 --- a/config/migrations/0001_initial.py +++ b/config/migrations/0001_initial.py @@ -17,8 +17,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('leixing', models.IntegerField(blank=True, null=True, verbose_name='公告类型')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更改时间')), ('jieshao', models.TextField(blank=True, null=True, verbose_name='公告介绍')), ], options={ @@ -33,8 +33,8 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('tupian_url', models.CharField(blank=True, max_length=500, null=True, verbose_name='轮播图片URL')), ('leixing', models.IntegerField(blank=True, null=True, verbose_name='图片类型:1:轮播2:个人中心背景')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更改时间')), ], options={ 'verbose_name': '轮播', @@ -91,8 +91,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('peizhiid', models.IntegerField(blank=True, null=True, verbose_name='群类型:1.打手 2.管事')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更改时间')), ('neirong', models.CharField(blank=True, max_length=200, null=True, verbose_name='群介绍')), ('qunid', models.CharField(blank=True, max_length=100, null=True, verbose_name='群id')), ('jieshao', models.CharField(blank=True, max_length=200, null=True, verbose_name='介绍')), @@ -110,8 +110,8 @@ class Migration(migrations.Migration): ('zongzc', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='总支出')), ('jrzc', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='今日支出')), ('jrls', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='今日流水')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更改时间')), ], options={ 'verbose_name': '收支记录', @@ -125,8 +125,8 @@ class Migration(migrations.Migration): ('id', models.AutoField(primary_key=True, serialize=False)), ('leixing', models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '组长'), (4, '考核官'), (5, '打手押金'), (6, '商家余额')], unique=True, verbose_name='用户类型')), ('default_quota', models.DecimalField(decimal_places=2, default=20.0, max_digits=10, verbose_name='默认每日提现限额(元)')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '默认提现限额', @@ -140,8 +140,8 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('tupian', models.CharField(blank=True, max_length=500, null=True, verbose_name='配置图片URL')), ('leixing', models.PositiveIntegerField(blank=True, null=True, verbose_name='提现类型1:打手规则图片2:默认头像图片')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更改时间')), ('fangwen_cishu', models.PositiveIntegerField(default=0, verbose_name='访问次数')), ], options={ @@ -176,8 +176,8 @@ class Migration(migrations.Migration): ('yongjin_qiangdan', models.BooleanField(default=False, verbose_name='是否开启佣金抢单')), ('yongjin_jiage', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='佣金价格')), ('chenghao_id', models.IntegerField(blank=True, null=True, verbose_name='关联称号ID')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '商家订单模板', @@ -200,8 +200,8 @@ class Migration(migrations.Migration): ('shiyongzhe_ip', models.CharField(blank=True, max_length=50, null=True, verbose_name='使用者IP')), ('youxiao_shijian', models.DateTimeField(blank=True, null=True, verbose_name='有效截止时间')), ('lianjie_token', models.CharField(blank=True, db_index=True, max_length=32, null=True, unique=True, verbose_name='链接Token')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ('previous_lianjie_id', models.PositiveIntegerField(blank=True, db_index=True, null=True, verbose_name='上一个链接ID')), ('previous_dingdan_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='上一个订单ID')), ], @@ -244,8 +244,8 @@ class Migration(migrations.Migration): ('day', models.PositiveSmallIntegerField(verbose_name='日期')), ('total_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='出款总额')), ('total_count', models.PositiveIntegerField(default=0, verbose_name='出款总笔数')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '每日出款统计', @@ -264,8 +264,8 @@ class Migration(migrations.Migration): ('day', models.PositiveSmallIntegerField(verbose_name='日期')), ('total_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='总收入金额')), ('total_count', models.PositiveIntegerField(default=0, verbose_name='总收入笔数')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '每日收入统计', diff --git a/config/models.py b/config/models.py index 4f12736..b818654 100644 --- a/config/models.py +++ b/config/models.py @@ -6,8 +6,8 @@ class Lunbo(QModel): ImageURL = models.CharField(max_length=500, null=True, blank=True, verbose_name='轮播图片URL') ImageType = models.IntegerField(null=True, blank=True, verbose_name='图片类型:1:轮播2:个人中心背景') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更改时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更改时间') class Meta: db_table = 'lunbo' verbose_name = '轮播' @@ -17,8 +17,8 @@ class Lunbo(QModel): class Gonggao(QModel): NoticeType = models.IntegerField(null=True, blank=True, verbose_name='公告类型') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更改时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更改时间') Content = models.TextField(null=True, blank=True, verbose_name='公告介绍') class Meta: db_table = 'gonggao' @@ -28,8 +28,8 @@ class Gonggao(QModel): class Qunpeizhi(QModel): GroupType = models.IntegerField(null=True, blank=True, verbose_name='群类型:1.打手 2.管事') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更改时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更改时间') GroupContent = models.CharField(max_length=200, null=True, blank=True, verbose_name='群介绍') GroupID = models.CharField(max_length=100, null=True, blank=True, verbose_name='群id') Description = models.CharField(max_length=200, null=True, blank=True, verbose_name='介绍') @@ -39,8 +39,8 @@ class Qunpeizhi(QModel): class Tupianpeizhi(QModel): ImageURL = models.CharField(max_length=500, null=True, blank=True, verbose_name='配置图片URL') ImageType = models.PositiveIntegerField(null=True, blank=True, verbose_name='提现类型1:打手规则图片2:默认头像图片') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更改时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更改时间') AccessCount = models.PositiveIntegerField(default=0, verbose_name='访问次数') class Meta: db_table = 'tupianpeizhi' @@ -55,8 +55,8 @@ class Szjilu(QModel): TotalExpense = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='总支出') DailyExpense = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日支出') DailyFlow = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日流水') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更改时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更改时间') class Meta: db_table = 'szjilu' verbose_name = '收支记录' @@ -93,8 +93,8 @@ class ShangjiaMoban(QModel): # 时间字段 - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'shangjia_moban' @@ -152,8 +152,8 @@ class ShangjiaLianjie(QModel): ) # 时间字段 - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') # 上一个链接的ID(可选,记录订单被更换前的链接) PreviousLinkID = models.PositiveIntegerField( @@ -206,8 +206,8 @@ class TixianQuotaDefault(QModel): default=20.00, verbose_name='默认每日提现限额(元)' ) - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'tixian_quota_default' @@ -287,8 +287,8 @@ class DailyIncomeStat(QModel): total_count = models.PositiveIntegerField(default=0, verbose_name='总收入笔数') # 时间戳 - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'daily_income_stat' @@ -318,8 +318,8 @@ class DailyPayoutStat(QModel): total_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='出款总额') total_count = models.PositiveIntegerField(default=0, verbose_name='出款总笔数') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'daily_payout_stat' diff --git a/config/tasks.py b/config/tasks.py index 40f271d..f3faf65 100644 --- a/config/tasks.py +++ b/config/tasks.py @@ -53,7 +53,7 @@ def daily_sz_reset_task(): # 清零今日字段 szjilu.DailyExpense = Decimal('0.00') szjilu.DailyFlow = Decimal('0.00') - szjilu.save(update_fields=['DailyExpense', 'DailyFlow', 'update_time']) + szjilu.save(update_fields=['DailyExpense', 'DailyFlow', 'UpdateTime']) logger.info(f"收支记录清零完成,今日支出原值: {old_jrzc},今日流水原值: {old_jrls}") diff --git a/config/views.py b/config/views.py index b66e9f1..4de1bf5 100644 --- a/config/views.py +++ b/config/views.py @@ -1038,7 +1038,7 @@ class GetAdminZhanghaoView(APIView): LEFT JOIN xiugaijilu xjl ON um.phone = xjl.xiugaiid WHERE um.UserType = 'admin' GROUP BY um.id, um.phone, um.yonghuid, um.UserType - ORDER BY um.create_time DESC + ORDER BY um.CreateTime DESC """ cursor.execute(sql) @@ -1194,12 +1194,12 @@ class GetXiugaiJiluView(APIView): xjl.huiyuants, -- IntegerField xjl.huiyuan_id, -- CharField xjl.qitashuoming, -- TextField - xjl.create_time, -- DateTimeField + xjl.CreateTime, -- DateTimeField um.avatar -- CharField FROM xiugaijilu xjl LEFT JOIN user_main um ON xjl.yonghuid = um.yonghuid WHERE xjl.xiugaiid = %s - ORDER BY xjl.create_time DESC + ORDER BY xjl.CreateTime DESC LIMIT %s OFFSET %s """ @@ -1390,7 +1390,7 @@ class ShangjiaMobanListView(APIView): 'labelId': moban.TitleID, 'labelName': label_name, 'texiaoJson': texiao_json, - 'createTime': moban.create_time.strftime('%Y-%m-%d %H:%M:%S') if moban.create_time else '' + 'createTime': moban.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if moban.CreateTime else '' }) return Response({'code': 200, 'msg': '搜索成功', 'data': {'list': formatted, 'total': len(formatted), 'hasMore': False}}) @@ -1423,7 +1423,7 @@ class ShangjiaMobanListView(APIView): 'labelId': moban.TitleID, 'labelName': label_name, 'texiaoJson': texiao_json, - 'createTime': moban.create_time.strftime('%Y-%m-%d %H:%M:%S') if moban.create_time else '' + 'createTime': moban.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if moban.CreateTime else '' }) has_more = current_page.has_next() @@ -1561,7 +1561,7 @@ class ShangjiaTianjiaMobanView(APIView): 'jieshao': moban.TemplateDesc, 'jiage': str(moban.Price), 'fabushuliang': moban.PublishedCount, - 'createTime': moban.create_time.strftime('%Y-%m-%d %H:%M:%S'), + 'createTime': moban.CreateTime.strftime('%Y-%m-%d %H:%M:%S'), 'commissionEnabled': moban.CommissionGrabEnabled, 'commissionValue': str(moban.CommissionPrice) if moban.CommissionPrice is not None else None, 'labelId': moban.TitleID @@ -1730,7 +1730,7 @@ class ShangjiaGengxinMobanView(APIView): 'commissionEnabled': moban.CommissionGrabEnabled, 'commissionValue': str(moban.CommissionPrice) if moban.CommissionPrice else None, 'labelId': moban.TitleID, - 'updateTime': moban.update_time.strftime('%Y-%m-%d %H:%M:%S') + 'updateTime': moban.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') } }) @@ -1947,7 +1947,7 @@ class ShangjiaLianjieListView(APIView): UserID=yonghu_id, TemplateID=moban_id, is_used=(used == 1) - ).order_by('-create_time') + ).order_by('-CreateTime') paginator = Paginator(links, page_size) if page < 1 or page > paginator.num_pages: @@ -1963,7 +1963,7 @@ class ShangjiaLianjieListView(APIView): 'is_used': link.is_used, 'dingdan_id': link.OrderID, 'ExpireTime': link.ExpireTime.strftime('%Y-%m-%d %H:%M:%S') if link.ExpireTime else '', - 'create_time': link.create_time.strftime('%Y-%m-%d %H:%M:%S') + 'CreateTime': link.CreateTime.strftime('%Y-%m-%d %H:%M:%S') }) return Response({ @@ -2581,7 +2581,7 @@ class KehuTianxieDingdanView(APIView): # 14. 返回成功响应 response_data = { #'dingdan_id': dingdan_obj.dingdan_id, - #'create_time': dingdan_obj.create_time.strftime('%Y-%m-%d %H:%M:%S'), + #'CreateTime': dingdan_obj.CreateTime.strftime('%Y-%m-%d %H:%M:%S'), #'shangpin_mingcheng': dingdan_obj.jieshao[:50] if dingdan_obj.jieshao else '', # 截取前50字符 #'jiage': str(dingdan_obj.jine) if dingdan_obj.jine else '0.00', 'shangjia_mingcheng': shangjia_mingcheng, @@ -2714,8 +2714,8 @@ class GuanZhuAListView(APIView): # 获取所有图片的相对URL images = list(qs.values_list('ImageURL', flat=True)) - # 获取最新更新时间(取所有记录的update_time最大值) - last_update = qs.aggregate(update_time__max=Max('update_time'))['update_time__max'] + # 获取最新更新时间(取所有记录的UpdateTime最大值) + last_update = qs.aggregate(UpdateTime__max=Max('UpdateTime'))['UpdateTime__max'] last_update_str = last_update.strftime('%Y-%m-%d %H:%M:%S') if last_update else None # 原子性增加访问次数 diff --git a/gvsdsdk/commission/models.py b/gvsdsdk/commission/models.py index bbffd85..e6a1233 100644 --- a/gvsdsdk/commission/models.py +++ b/gvsdsdk/commission/models.py @@ -1,352 +1,352 @@ -"""模块:分佣 SAAS 数据模型 -SAAS 级分佣引擎——事件驱动、规则外置、参与方可配置。 -子服务只管说"发生了什么",SAAS 负责"怎么分"。""" - -import uuid -import datetime -from django.db import models -from gvsdsdk.model_base import QModel -from gvsdsdk.models import UUIDField - - -class CommissionEvent(QModel): - """分佣事件表——子服务触发的分佣事件 - - 子服务在业务发生时(订单完成、会员购买、充值到账等) - 调用 CommissionEngine.Dispatch() 写入此表。 - """ - - EVT_ORDER_COMPLETE = 'order_complete' - EVT_MEMBER_PURCHASE = 'member_purchase' - EVT_RECHARGE = 'recharge' - EVT_PENALTY = 'penalty' - EVT_REGISTER = 'register' - EVT_CUSTOM = 'custom' - - EVT_TYPE_CHOICES = [ - (EVT_ORDER_COMPLETE, '订单完成'), - (EVT_MEMBER_PURCHASE, '会员购买'), - (EVT_RECHARGE, '充值到账'), - (EVT_PENALTY, '罚款缴纳'), - (EVT_REGISTER, '用户注册'), - (EVT_CUSTOM, '自定义事件'), - ] - - EVT_PENDING = 0 - EVT_PROCESSING = 1 - EVT_SETTLED = 2 - EVT_FAILED = 3 - - EVT_STATUS_CHOICES = [ - (EVT_PENDING, '待处理'), - (EVT_PROCESSING, '处理中'), - (EVT_SETTLED, '已结算'), - (EVT_FAILED, '结算失败'), - ] - - EventUUID = UUIDField(primary_key=True) - TenantUUID = UUIDField() - EventType = models.CharField(max_length=32, choices=EVT_TYPE_CHOICES) - EventKey = models.CharField(max_length=128) - TotalAmount = models.DecimalField(max_digits=18, decimal_places=4, default=0) - SourceUserUUID = UUIDField(null=True, blank=True) - EventPayload = models.JSONField(default=dict) - Participants = models.JSONField(default=dict) - EventStatus = models.SmallIntegerField(default=EVT_PENDING, choices=EVT_STATUS_CHOICES) - EventTime = models.DateTimeField(default=datetime.datetime.utcnow) - ProcessTime = models.DateTimeField(null=True, blank=True) - CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) - - class Meta: - db_table = 'CommissionEvent' - app_label = 'gvsdsdk' - verbose_name = '分佣事件' - verbose_name_plural = '分佣事件' - unique_together = [('TenantUUID', 'EventKey')] - managed = False - indexes = [ - models.Index(fields=['TenantUUID', 'EventType', 'EventStatus'], name='idx_cmevt_tenant_type_status'), - models.Index(fields=['EventKey'], name='idx_cmevt_event_key'), - models.Index(fields=['CreateTime'], name='idx_cmevt_create_time'), - ] - - def __str__(self): - return f'{self.EventType}:{self.EventKey}' - - @property - def IsSettled(self): - return self.EventStatus == self.EVT_SETTLED - - def MarkProcessing(self): - self.EventStatus = self.EVT_PROCESSING - self.save(update_fields=['EventStatus']) - - def MarkSettled(self): - self.EventStatus = self.EVT_SETTLED - self.ProcessTime = datetime.datetime.utcnow() - self.save(update_fields=['EventStatus', 'ProcessTime']) - - def MarkFailed(self): - self.EventStatus = self.EVT_FAILED - self.ProcessTime = datetime.datetime.utcnow() - self.save(update_fields=['EventStatus', 'ProcessTime']) - - -class RateTable(QModel): - """费率表——可复用的费率配置 - - 每个租户可创建多张费率表,不同事件类型绑定不同的表。 - 例如:打手分成费率表、提现手续费率表、罚款分红费率表。 - """ - - RateTableUUID = UUIDField(primary_key=True) - TenantUUID = UUIDField() - TableCode = models.CharField(max_length=64) - TableName = models.CharField(max_length=128) - TableDescription = models.CharField(max_length=512, default='') - TableStatus = models.SmallIntegerField(default=1) - CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) - UpdateTime = models.DateTimeField(null=True, blank=True) - - class Meta: - db_table = 'CommissionRateTable' - app_label = 'gvsdsdk' - verbose_name = '费率表' - verbose_name_plural = '费率表' - unique_together = [('TenantUUID', 'TableCode')] - managed = False - indexes = [ - models.Index(fields=['TenantUUID'], name='idx_cmrate_tenant'), - ] - - def __str__(self): - return f'{self.TableCode}:{self.TableName}' - - def GetRate(self, key): - try: - entry = self.Entries.get(RateKey=key, EntryStatus=1) - return entry.RateValue - except RateEntry.DoesNotExist: - return None - - def GetRates(self): - return { - e.RateKey: e.RateValue - for e in self.Entries.filter(EntryStatus=1) - } - - -class RateEntry(QModel): - """费率条目——费率表中的一行""" - - EntryUUID = UUIDField(primary_key=True) - RateTable = models.ForeignKey( - RateTable, on_delete=models.CASCADE, - related_name='Entries', - ) - RateKey = models.CharField(max_length=64) - RateLabel = models.CharField(max_length=128, default='') - RateValue = models.DecimalField(max_digits=10, decimal_places=6) - EntryStatus = models.SmallIntegerField(default=1) - CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) - - class Meta: - db_table = 'CommissionRateEntry' - app_label = 'gvsdsdk' - verbose_name = '费率条目' - verbose_name_plural = '费率条目' - unique_together = [('RateTable', 'RateKey')] - managed = False - indexes = [ - models.Index(fields=['RateTable', 'RateKey'], name='idx_cmrentry_table_key'), - ] - - def __str__(self): - return f'{self.RateKey}={self.RateValue}' - - -class CommissionRule(QModel): - """分佣规则表——定义"怎么分" - - 每条规则绑定一种事件类型,定义参与者如何分账。 - 支持比例分账、固定金额、阶梯分账、剩余归平台四种 SplitType。 - """ - - SPLT_PERCENTAGE = 'percentage' - SPLT_FIXED = 'fixed' - SPLT_TIERED = 'tiered' - SPLT_REMAINDER = 'remainder' - - SPLT_TYPE_CHOICES = [ - (SPLT_PERCENTAGE, '比例分账'), - (SPLT_FIXED, '固定金额'), - (SPLT_TIERED, '阶梯分账'), - (SPLT_REMAINDER, '剩余归平台'), - ] - - RuleUUID = UUIDField(primary_key=True) - TenantUUID = UUIDField() - EventType = models.CharField(max_length=32, choices=CommissionEvent.EVT_TYPE_CHOICES) - RuleName = models.CharField(max_length=128) - RuleDescription = models.CharField(max_length=512, default='') - ParticipantRules = models.JSONField(default=list) - RateTable = models.ForeignKey( - RateTable, on_delete=models.SET_NULL, - null=True, blank=True, related_name='rules', - ) - MaxInviteLevel = models.SmallIntegerField(default=2) - RulePriority = models.IntegerField(default=0) - RuleStatus = models.SmallIntegerField(default=1) - CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) - UpdateTime = models.DateTimeField(null=True, blank=True) - - class Meta: - db_table = 'CommissionRule' - app_label = 'gvsdsdk' - verbose_name = '分佣规则' - verbose_name_plural = '分佣规则' - managed = False - indexes = [ - models.Index(fields=['TenantUUID', 'EventType', 'RuleStatus'], name='idx_cmrule_tenant_type_status'), - models.Index(fields=['RulePriority'], name='idx_cmrule_priority'), - ] - - def __str__(self): - return f'{self.RuleName}({self.EventType})' - - def FindParticipantRule(self, role): - for pr in self.ParticipantRules: - if pr.get('role') == role: - return pr - return None - - -class CommissionRecord(QModel): - """分佣记录表——不可变分账流水 - - 每条记录代表一次分账结果:谁、从哪个事件、按什么规则、分了多少。 - 生成后即冻结,规则变更不影响历史记录。 - """ - - REC_PENDING = 0 - REC_SETTLED = 1 - REC_WITHDRAWN = 2 - REC_CANCELLED = 3 - - REC_STATUS_CHOICES = [ - (REC_PENDING, '待结算'), - (REC_SETTLED, '已结算'), - (REC_WITHDRAWN, '已提现'), - (REC_CANCELLED, '已取消'), - ] - - RecordUUID = UUIDField(primary_key=True) - TenantUUID = UUIDField() - Event = models.ForeignKey( - CommissionEvent, on_delete=models.CASCADE, - related_name='records', - ) - Rule = models.ForeignKey( - CommissionRule, on_delete=models.SET_NULL, - null=True, related_name='records', - ) - ParticipantID = models.CharField(max_length=128) - ParticipantRole = models.CharField(max_length=64) - SplitType = models.CharField(max_length=16) - Amount = models.DecimalField(max_digits=18, decimal_places=4) - RuleSnapshot = models.JSONField(default=dict) - Status = models.SmallIntegerField(default=REC_PENDING, choices=REC_STATUS_CHOICES) - TierIndex = models.IntegerField(default=0) - SettleTime = models.DateTimeField(null=True, blank=True) - CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) - - class Meta: - db_table = 'CommissionRecord' - app_label = 'gvsdsdk' - verbose_name = '分佣记录' - verbose_name_plural = '分佣记录' - ordering = ['-CreateTime'] - managed = False - indexes = [ - models.Index(fields=['TenantUUID', 'ParticipantID', 'Status'], name='idx_cmrec_tenant_part_status'), - models.Index(fields=['Event'], name='idx_cmrec_event'), - models.Index(fields=['CreateTime'], name='idx_cmrec_create_time'), - ] - - def __str__(self): - return f'{self.ParticipantRole}:{self.ParticipantID}={self.Amount}' - - @property - def IsSettled(self): - return self.Status >= self.REC_SETTLED - - def Settle(self): - self.Status = self.REC_SETTLED - self.SettleTime = datetime.datetime.utcnow() - self.save(update_fields=['Status', 'SettleTime']) - - def Cancel(self): - self.Status = self.REC_CANCELLED - self.save(update_fields=['Status']) - - -class PreSettlement(QModel): - """预结算表——跨平台订单待确认款项 - - 当订单涉及外部平台时,打手分成先挂预结算, - 对方确认后转为正式 CommissionRecord。 - """ - - PST_PENDING = 0 - PST_CONFIRMED = 1 - PST_REJECTED = 2 - - PST_STATUS_CHOICES = [ - (PST_PENDING, '待确认'), - (PST_CONFIRMED, '已确认'), - (PST_REJECTED, '已拒绝'), - ] - - SettlementUUID = UUIDField(primary_key=True) - TenantUUID = UUIDField() - EventKey = models.CharField(max_length=128) - ExternalPlatform = models.CharField(max_length=64, default='') - ExternalOrderID = models.CharField(max_length=128, default='') - ParticipantID = models.CharField(max_length=128) - ParticipantRole = models.CharField(max_length=64, default='worker') - Amount = models.DecimalField(max_digits=18, decimal_places=4) - OrderAmount = models.DecimalField(max_digits=18, decimal_places=4, default=0) - Status = models.SmallIntegerField(default=PST_PENDING, choices=PST_STATUS_CHOICES) - ConfirmedBy = models.CharField(max_length=128, default='') - ConfirmDeadline = models.DateTimeField(null=True, blank=True) - ConfirmedAt = models.DateTimeField(null=True, blank=True) - CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) - UpdateTime = models.DateTimeField(null=True, blank=True) - - class Meta: - db_table = 'CommissionPreSettlement' - app_label = 'gvsdsdk' - verbose_name = '预结算' - verbose_name_plural = '预结算' - unique_together = [('EventKey', 'ParticipantID')] - managed = False - indexes = [ - models.Index(fields=['TenantUUID', 'Status'], name='idx_cmpst_tenant_status'), - models.Index(fields=['EventKey'], name='idx_cmpst_event_key'), - models.Index(fields=['ExternalOrderID'], name='idx_cmpst_ext_order'), - ] - - def __str__(self): - return f'预结算:{self.EventKey}->{self.ParticipantID}={self.Amount}' - - def Confirm(self, confirmed_by=''): - self.Status = self.PST_CONFIRMED - self.ConfirmedBy = confirmed_by - self.ConfirmedAt = datetime.datetime.utcnow() - self.save(update_fields=['Status', 'ConfirmedBy', 'ConfirmedAt']) - - def Reject(self, confirmed_by=''): - self.Status = self.PST_REJECTED - self.ConfirmedBy = confirmed_by - self.ConfirmedAt = datetime.datetime.utcnow() - self.save(update_fields=['Status', 'ConfirmedBy', 'ConfirmedAt']) +"""模块:分佣 SAAS 数据模型 +SAAS 级分佣引擎——事件驱动、规则外置、参与方可配置。 +子服务只管说"发生了什么",SAAS 负责"怎么分"。""" + +import uuid +import datetime +from django.db import models +from gvsdsdk.model_base import QModel +from gvsdsdk.models import UUIDField + + +class CommissionEvent(QModel): + """分佣事件表——子服务触发的分佣事件 + + 子服务在业务发生时(订单完成、会员购买、充值到账等) + 调用 CommissionEngine.Dispatch() 写入此表。 + """ + + EVT_ORDER_COMPLETE = 'order_complete' + EVT_MEMBER_PURCHASE = 'member_purchase' + EVT_RECHARGE = 'recharge' + EVT_PENALTY = 'penalty' + EVT_REGISTER = 'register' + EVT_CUSTOM = 'custom' + + EVT_TYPE_CHOICES = [ + (EVT_ORDER_COMPLETE, '订单完成'), + (EVT_MEMBER_PURCHASE, '会员购买'), + (EVT_RECHARGE, '充值到账'), + (EVT_PENALTY, '罚款缴纳'), + (EVT_REGISTER, '用户注册'), + (EVT_CUSTOM, '自定义事件'), + ] + + EVT_PENDING = 0 + EVT_PROCESSING = 1 + EVT_SETTLED = 2 + EVT_FAILED = 3 + + EVT_STATUS_CHOICES = [ + (EVT_PENDING, '待处理'), + (EVT_PROCESSING, '处理中'), + (EVT_SETTLED, '已结算'), + (EVT_FAILED, '结算失败'), + ] + + EventUUID = UUIDField(primary_key=True) + TenantUUID = UUIDField() + EventType = models.CharField(max_length=32, choices=EVT_TYPE_CHOICES) + EventKey = models.CharField(max_length=128) + TotalAmount = models.DecimalField(max_digits=18, decimal_places=4, default=0) + SourceUserUUID = UUIDField(null=True, blank=True) + EventPayload = models.JSONField(default=dict) + Participants = models.JSONField(default=dict) + EventStatus = models.SmallIntegerField(default=EVT_PENDING, choices=EVT_STATUS_CHOICES) + EventTime = models.DateTimeField(default=datetime.datetime.utcnow) + ProcessTime = models.DateTimeField(null=True, blank=True) + CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) + + class Meta: + db_table = 'CommissionEvent' + app_label = 'gvsdsdk' + verbose_name = '分佣事件' + verbose_name_plural = '分佣事件' + unique_together = [('TenantUUID', 'EventKey')] + managed = False + indexes = [ + models.Index(fields=['TenantUUID', 'EventType', 'EventStatus'], name='idx_cmevt_tenant_type_status'), + models.Index(fields=['EventKey'], name='idx_cmevt_event_key'), + models.Index(fields=['CreateTime'], name='idx_cmevt_CreateTime'), + ] + + def __str__(self): + return f'{self.EventType}:{self.EventKey}' + + @property + def IsSettled(self): + return self.EventStatus == self.EVT_SETTLED + + def MarkProcessing(self): + self.EventStatus = self.EVT_PROCESSING + self.save(update_fields=['EventStatus']) + + def MarkSettled(self): + self.EventStatus = self.EVT_SETTLED + self.ProcessTime = datetime.datetime.utcnow() + self.save(update_fields=['EventStatus', 'ProcessTime']) + + def MarkFailed(self): + self.EventStatus = self.EVT_FAILED + self.ProcessTime = datetime.datetime.utcnow() + self.save(update_fields=['EventStatus', 'ProcessTime']) + + +class RateTable(QModel): + """费率表——可复用的费率配置 + + 每个租户可创建多张费率表,不同事件类型绑定不同的表。 + 例如:打手分成费率表、提现手续费率表、罚款分红费率表。 + """ + + RateTableUUID = UUIDField(primary_key=True) + TenantUUID = UUIDField() + TableCode = models.CharField(max_length=64) + TableName = models.CharField(max_length=128) + TableDescription = models.CharField(max_length=512, default='') + TableStatus = models.SmallIntegerField(default=1) + CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) + UpdateTime = models.DateTimeField(null=True, blank=True) + + class Meta: + db_table = 'CommissionRateTable' + app_label = 'gvsdsdk' + verbose_name = '费率表' + verbose_name_plural = '费率表' + unique_together = [('TenantUUID', 'TableCode')] + managed = False + indexes = [ + models.Index(fields=['TenantUUID'], name='idx_cmrate_tenant'), + ] + + def __str__(self): + return f'{self.TableCode}:{self.TableName}' + + def GetRate(self, key): + try: + entry = self.Entries.get(RateKey=key, EntryStatus=1) + return entry.RateValue + except RateEntry.DoesNotExist: + return None + + def GetRates(self): + return { + e.RateKey: e.RateValue + for e in self.Entries.filter(EntryStatus=1) + } + + +class RateEntry(QModel): + """费率条目——费率表中的一行""" + + EntryUUID = UUIDField(primary_key=True) + RateTable = models.ForeignKey( + RateTable, on_delete=models.CASCADE, + related_name='Entries', + ) + RateKey = models.CharField(max_length=64) + RateLabel = models.CharField(max_length=128, default='') + RateValue = models.DecimalField(max_digits=10, decimal_places=6) + EntryStatus = models.SmallIntegerField(default=1) + CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) + + class Meta: + db_table = 'CommissionRateEntry' + app_label = 'gvsdsdk' + verbose_name = '费率条目' + verbose_name_plural = '费率条目' + unique_together = [('RateTable', 'RateKey')] + managed = False + indexes = [ + models.Index(fields=['RateTable', 'RateKey'], name='idx_cmrentry_table_key'), + ] + + def __str__(self): + return f'{self.RateKey}={self.RateValue}' + + +class CommissionRule(QModel): + """分佣规则表——定义"怎么分" + + 每条规则绑定一种事件类型,定义参与者如何分账。 + 支持比例分账、固定金额、阶梯分账、剩余归平台四种 SplitType。 + """ + + SPLT_PERCENTAGE = 'percentage' + SPLT_FIXED = 'fixed' + SPLT_TIERED = 'tiered' + SPLT_REMAINDER = 'remainder' + + SPLT_TYPE_CHOICES = [ + (SPLT_PERCENTAGE, '比例分账'), + (SPLT_FIXED, '固定金额'), + (SPLT_TIERED, '阶梯分账'), + (SPLT_REMAINDER, '剩余归平台'), + ] + + RuleUUID = UUIDField(primary_key=True) + TenantUUID = UUIDField() + EventType = models.CharField(max_length=32, choices=CommissionEvent.EVT_TYPE_CHOICES) + RuleName = models.CharField(max_length=128) + RuleDescription = models.CharField(max_length=512, default='') + ParticipantRules = models.JSONField(default=list) + RateTable = models.ForeignKey( + RateTable, on_delete=models.SET_NULL, + null=True, blank=True, related_name='rules', + ) + MaxInviteLevel = models.SmallIntegerField(default=2) + RulePriority = models.IntegerField(default=0) + RuleStatus = models.SmallIntegerField(default=1) + CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) + UpdateTime = models.DateTimeField(null=True, blank=True) + + class Meta: + db_table = 'CommissionRule' + app_label = 'gvsdsdk' + verbose_name = '分佣规则' + verbose_name_plural = '分佣规则' + managed = False + indexes = [ + models.Index(fields=['TenantUUID', 'EventType', 'RuleStatus'], name='idx_cmrule_tenant_type_status'), + models.Index(fields=['RulePriority'], name='idx_cmrule_priority'), + ] + + def __str__(self): + return f'{self.RuleName}({self.EventType})' + + def FindParticipantRule(self, role): + for pr in self.ParticipantRules: + if pr.get('role') == role: + return pr + return None + + +class CommissionRecord(QModel): + """分佣记录表——不可变分账流水 + + 每条记录代表一次分账结果:谁、从哪个事件、按什么规则、分了多少。 + 生成后即冻结,规则变更不影响历史记录。 + """ + + REC_PENDING = 0 + REC_SETTLED = 1 + REC_WITHDRAWN = 2 + REC_CANCELLED = 3 + + REC_STATUS_CHOICES = [ + (REC_PENDING, '待结算'), + (REC_SETTLED, '已结算'), + (REC_WITHDRAWN, '已提现'), + (REC_CANCELLED, '已取消'), + ] + + RecordUUID = UUIDField(primary_key=True) + TenantUUID = UUIDField() + Event = models.ForeignKey( + CommissionEvent, on_delete=models.CASCADE, + related_name='records', + ) + Rule = models.ForeignKey( + CommissionRule, on_delete=models.SET_NULL, + null=True, related_name='records', + ) + ParticipantID = models.CharField(max_length=128) + ParticipantRole = models.CharField(max_length=64) + SplitType = models.CharField(max_length=16) + Amount = models.DecimalField(max_digits=18, decimal_places=4) + RuleSnapshot = models.JSONField(default=dict) + Status = models.SmallIntegerField(default=REC_PENDING, choices=REC_STATUS_CHOICES) + TierIndex = models.IntegerField(default=0) + SettleTime = models.DateTimeField(null=True, blank=True) + CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) + + class Meta: + db_table = 'CommissionRecord' + app_label = 'gvsdsdk' + verbose_name = '分佣记录' + verbose_name_plural = '分佣记录' + ordering = ['-CreateTime'] + managed = False + indexes = [ + models.Index(fields=['TenantUUID', 'ParticipantID', 'Status'], name='idx_cmrec_tenant_part_status'), + models.Index(fields=['Event'], name='idx_cmrec_event'), + models.Index(fields=['CreateTime'], name='idx_cmrec_CreateTime'), + ] + + def __str__(self): + return f'{self.ParticipantRole}:{self.ParticipantID}={self.Amount}' + + @property + def IsSettled(self): + return self.Status >= self.REC_SETTLED + + def Settle(self): + self.Status = self.REC_SETTLED + self.SettleTime = datetime.datetime.utcnow() + self.save(update_fields=['Status', 'SettleTime']) + + def Cancel(self): + self.Status = self.REC_CANCELLED + self.save(update_fields=['Status']) + + +class PreSettlement(QModel): + """预结算表——跨平台订单待确认款项 + + 当订单涉及外部平台时,打手分成先挂预结算, + 对方确认后转为正式 CommissionRecord。 + """ + + PST_PENDING = 0 + PST_CONFIRMED = 1 + PST_REJECTED = 2 + + PST_STATUS_CHOICES = [ + (PST_PENDING, '待确认'), + (PST_CONFIRMED, '已确认'), + (PST_REJECTED, '已拒绝'), + ] + + SettlementUUID = UUIDField(primary_key=True) + TenantUUID = UUIDField() + EventKey = models.CharField(max_length=128) + ExternalPlatform = models.CharField(max_length=64, default='') + ExternalOrderID = models.CharField(max_length=128, default='') + ParticipantID = models.CharField(max_length=128) + ParticipantRole = models.CharField(max_length=64, default='worker') + Amount = models.DecimalField(max_digits=18, decimal_places=4) + OrderAmount = models.DecimalField(max_digits=18, decimal_places=4, default=0) + Status = models.SmallIntegerField(default=PST_PENDING, choices=PST_STATUS_CHOICES) + ConfirmedBy = models.CharField(max_length=128, default='') + ConfirmDeadline = models.DateTimeField(null=True, blank=True) + ConfirmedAt = models.DateTimeField(null=True, blank=True) + CreateTime = models.DateTimeField(default=datetime.datetime.utcnow) + UpdateTime = models.DateTimeField(null=True, blank=True) + + class Meta: + db_table = 'CommissionPreSettlement' + app_label = 'gvsdsdk' + verbose_name = '预结算' + verbose_name_plural = '预结算' + unique_together = [('EventKey', 'ParticipantID')] + managed = False + indexes = [ + models.Index(fields=['TenantUUID', 'Status'], name='idx_cmpst_tenant_status'), + models.Index(fields=['EventKey'], name='idx_cmpst_event_key'), + models.Index(fields=['ExternalOrderID'], name='idx_cmpst_ext_order'), + ] + + def __str__(self): + return f'预结算:{self.EventKey}->{self.ParticipantID}={self.Amount}' + + def Confirm(self, confirmed_by=''): + self.Status = self.PST_CONFIRMED + self.ConfirmedBy = confirmed_by + self.ConfirmedAt = datetime.datetime.utcnow() + self.save(update_fields=['Status', 'ConfirmedBy', 'ConfirmedAt']) + + def Reject(self, confirmed_by=''): + self.Status = self.PST_REJECTED + self.ConfirmedBy = confirmed_by + self.ConfirmedAt = datetime.datetime.utcnow() + self.save(update_fields=['Status', 'ConfirmedBy', 'ConfirmedAt']) diff --git a/gvsdsdk/fluent.py b/gvsdsdk/fluent.py index 100b3d3..46c4ea0 100644 --- a/gvsdsdk/fluent.py +++ b/gvsdsdk/fluent.py @@ -1,924 +1,924 @@ -"""FluentQuery 兼容层查询构建器 - -提供 SQLAlchemy 风格的链式 OOP 查询接口,包装 Django QuerySet。 - -核心类: -- ``FluentQuery`` — 链式查询构建器,由 ``Model.query`` 返回 -- ``Session`` — 简易会话管理(add/commit/rollback) -- ``_CompatFunc`` — 兼容层聚合函数(func.sum / func.count 等) -- ``db`` — 全局数据库操作入口(db.session / db.or_ / db.and_) - -典型用法:: - - from gvsdsdk.fluent import db, func, FQ - - # 链式查询 - orders = Dingdan.query.filter(zhuangtai=8).order_by('-create_time').to_list() - - # 字段表达式 - Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3])) - - # 聚合 - Dingdan.query.aggregate(total=Sum('jine')) - - # values / annotate - Dingdan.query.values('zhuangtai').annotate(cnt=Count('id')) - - # 分页 - Dingdan.query.paginate(page=1, per_page=20) - - # 兼容层聚合函数 - func.sum(Dingdan.jine) - func.count(Dingdan.id) - func.year(Dingdan.create_time) -""" -from __future__ import annotations - -import logging -import threading -from typing import Any, Dict, Generic, Iterator, List, Optional, Sequence, Tuple, Type, TypeVar, Union - -from django.db import connection as default_db_connection, models, transaction -from django.db.models import Q, F, Count, Sum, Avg, Min, Max, Subquery, OuterRef, Case, When, Value - -logger = logging.getLogger(__name__) - -_local = threading.local() - -#: 模型类型变量,用于 FluentQuery[_M] 泛型参数 -_M = TypeVar('_M', bound=models.Model) - - -def _get_field_expression_cls(): - """延迟导入 FieldExpression,避免 fluent ↔ model_base 循环依赖。""" - from gvsdsdk.model_base import FieldExpression - return FieldExpression - - -# --------------------------------------------------------------------------- -# 兼容层聚合函数 -# --------------------------------------------------------------------------- - -class _AggregateExpr: - """聚合表达式包装,关联模型类和 Django Aggregate 对象。""" - - def __init__(self, model: Optional[Type[models.Model]], aggregate: models.Aggregate) -> None: - self.model = model - self.aggregate = aggregate - - -class _ExtractExpr: - """日期提取表达式(year/month/day),用于 ``func.year(field) == 2024`` 等。""" - - def __init__(self, field_name: str, lookup_type: str) -> None: - self.field_name = field_name - self.lookup_type = lookup_type - - def __eq__(self, other) -> Q: # type: ignore[override] - return Q(**{f'{self.field_name}__{self.lookup_type}': other}) - - def __and__(self, other) -> Q: - if isinstance(other, Q): - return Q(**{f'{self.field_name}__{self.lookup_type}': None}) & other - return self.__eq__(other) - - -class _CompatFunc: - """兼容层聚合函数集合,提供 SQLAlchemy 风格的 ``func.sum()`` / ``func.count()`` 等。 - - 用法:: - - from gvsdsdk.fluent import func - - func.sum(Model.jine) → Sum('jine') - func.count(Model.id) → Count('id') - func.max(Model.jine) → Max('jine') - func.min(Model.jine) → Min('jine') - func.avg(Model.jine) → Avg('jine') - func.year(Model.riqi) → _ExtractExpr('riqi', 'year') - func.month(Model.riqi) → _ExtractExpr('riqi', 'month') - func.day(Model.riqi) → _ExtractExpr('riqi', 'day') - """ - - def sum(self, field_expr) -> Union[_AggregateExpr, Sum]: - """求和聚合。""" - name = self._resolve_name(field_expr) - agg = Sum(name) - model = getattr(field_expr, 'model_class', None) - return _AggregateExpr(model, agg) if model else agg - - def count(self, field_expr=None) -> Union[_AggregateExpr, Count]: - """计数聚合,无参数时等价于 COUNT(*)。""" - if field_expr is None: - return Count('*') - name = self._resolve_name(field_expr) - agg = Count(name) - model = getattr(field_expr, 'model_class', None) - return _AggregateExpr(model, agg) if model else agg - - def max(self, field_expr) -> Union[_AggregateExpr, Max]: - """最大值聚合。""" - name = self._resolve_name(field_expr) - agg = Max(name) - model = getattr(field_expr, 'model_class', None) - return _AggregateExpr(model, agg) if model else agg - - def min(self, field_expr) -> Union[_AggregateExpr, Min]: - """最小值聚合。""" - name = self._resolve_name(field_expr) - agg = Min(name) - model = getattr(field_expr, 'model_class', None) - return _AggregateExpr(model, agg) if model else agg - - def avg(self, field_expr) -> Union[_AggregateExpr, Avg]: - """平均值聚合。""" - name = self._resolve_name(field_expr) - agg = Avg(name) - model = getattr(field_expr, 'model_class', None) - return _AggregateExpr(model, agg) if model else agg - - def year(self, field_expr) -> _ExtractExpr: - """提取年份,用于 ``func.year(Model.date) == 2024``。""" - return _ExtractExpr(self._resolve_name(field_expr), 'year') - - def month(self, field_expr) -> _ExtractExpr: - """提取月份。""" - return _ExtractExpr(self._resolve_name(field_expr), 'month') - - def day(self, field_expr) -> _ExtractExpr: - """提取日期。""" - return _ExtractExpr(self._resolve_name(field_expr), 'day') - - @staticmethod - def _resolve_name(field_expr) -> str: - """从 FieldExpression 或字符串解析 Django 字段名。""" - if hasattr(field_expr, '_django_name'): - return field_expr._django_name - if hasattr(field_expr, 'field_name'): - return field_expr.field_name - return str(field_expr) - - -func = _CompatFunc() - - -# --------------------------------------------------------------------------- -# DELETE 语句构建器 -# --------------------------------------------------------------------------- - -class _DeleteStmt: - """DELETE 语句构建器,支持 ``Model.query.filter(...).delete()`` 风格。""" - - def __init__(self, model_class: Type[models.Model], conditions: Optional[Q] = None) -> None: - self.model_class = model_class - self.conditions = conditions or Q() - - def where(self, *args: Q, **kwargs: Any) -> _DeleteStmt: - """添加 WHERE 条件。""" - new_conditions = Q() - for a in args: - if isinstance(a, Q): - new_conditions &= a - if kwargs: - new_conditions &= Q(**kwargs) - self.conditions = new_conditions - return self - - def execute(self) -> Tuple[int, Dict[str, int]]: - """执行删除,返回 (删除数, {表名: 删除数})。""" - if self.model_class and self.conditions: - return self.model_class.objects.filter(self.conditions).delete() - elif self.model_class: - return self.model_class.objects.all().delete() - return (0, {}) - - -# --------------------------------------------------------------------------- -# 会话与缓存 -# --------------------------------------------------------------------------- - -def _get_session() -> Session: - """获取当前线程的 Session 实例。""" - if not hasattr(_local, 'db_session'): - _local.db_session = Session() - return _local.db_session - - -def _get_cache() -> Dict: - """获取当前线程的查询缓存。""" - if not hasattr(_local, 'query_cache'): - _local.query_cache = {} - return _local.query_cache - - -def _invalidate_cache() -> None: - """清除当前线程的查询缓存。""" - if hasattr(_local, 'query_cache'): - _local.query_cache.clear() - - -# --------------------------------------------------------------------------- -# 关联字段访问器 -# --------------------------------------------------------------------------- - -class RelationAccessor: - """关联字段访问器,支持 ``Model.relation.field`` 跨表字段表达式。 - - 用法:: - - # 自动解析关联字段 - Dingdan.query.filter(Dingdan.shangjia.nicheng == '测试') - """ - - def __init__(self, model_class: Type[models.Model], prefix: str = '') -> None: - self._model = model_class - self._prefix = prefix - - def __getattr__(self, name: str): - if name.startswith('_'): - return object.__getattribute__(self, name) - try: - self._model._meta.get_field(name) - full_name = f'{self._prefix}__{name}' if self._prefix else name - return _get_field_expression_cls()(self._model, full_name) - except Exception: - for rel in self._model._meta.get_fields(include_hidden=True): - if hasattr(rel, 'related_model') and rel.related_model: - rel_name = '' - if hasattr(rel, 'get_accessor_name'): - rel_name = rel.get_accessor_name() - elif hasattr(rel, 'name'): - rel_name = rel.name - if rel_name and rel_name == name: - full_prefix = f'{self._prefix}__{rel_name}' if self._prefix else rel_name - return RelationAccessor(rel.related_model, full_prefix) - return _get_field_expression_cls()(self._model, name) - - def __repr__(self) -> str: - return f'RelationAccessor({self._model.__name__}, prefix={self._prefix!r})' - - -# --------------------------------------------------------------------------- -# Values 结果集 -# --------------------------------------------------------------------------- - -class _ValuesResult: - """包装 Django ValuesQuerySet,支持链式调用。 - - 底层使用 Django ``.values()`` 返回字典,保持与 Django 行为一致。 - 额外提供 ``.all()`` 返回字典列表,``.first()`` 返回字典或 None, - ``.scalar()`` 返回单字段场景的标量值。 - """ - - def __init__(self, qs: models.QuerySet, fields: List[str], original_fields: Tuple) -> None: - self._qs: models.QuerySet = qs - self._fields: List[str] = fields - self._original_fields: Tuple = original_fields - - @property - def query(self) -> Any: - """暴露底层 Django QuerySet 的 query 属性,支持 Subquery() 等场景。""" - return self._qs.query - - def filter(self, *args: Any, **kwargs: Any) -> _ValuesResult: - self._qs = self._qs.filter(*args, **kwargs) - return self - - def exclude(self, *args: Any, **kwargs: Any) -> _ValuesResult: - self._qs = self._qs.exclude(*args, **kwargs) - return self - - def order_by(self, *fields: str) -> _ValuesResult: - self._qs = self._qs.order_by(*fields) - return self - - def distinct(self) -> _ValuesResult: - self._qs = self._qs.distinct() - return self - - def annotate(self, **kwargs: Any) -> _ValuesResult: - self._qs = self._qs.annotate(**kwargs) - return self - - def first(self) -> Optional[Dict[str, Any]]: - """返回第一条记录(字典)或 None。""" - return self._qs.first() - - def all(self) -> List[Dict[str, Any]]: - """返回所有记录的字典列表。""" - return list(self._qs) - - def scalar(self) -> Any: - """返回单字段场景的标量值。""" - row = self._qs.first() - if row is None: - return None - if self._fields: - return row.get(self._fields[0]) - return row - - def count(self) -> int: - return self._qs.count() - - def exists(self) -> bool: - return self._qs.exists() - - def __getitem__(self, key: Union[int, slice]) -> Any: - if isinstance(key, slice): - self._qs = self._qs[key] - return self - else: - return self._qs[key] - - def __iter__(self) -> Iterator[Dict[str, Any]]: - return iter(self._qs) - - def __len__(self) -> int: - return self.count() - - def __bool__(self) -> bool: - return self._qs.exists() - - def __repr__(self) -> str: - return f'_ValuesResult(fields={self._fields}, count={self.count()})' - - -# --------------------------------------------------------------------------- -# FluentQuery 核心查询构建器 -# --------------------------------------------------------------------------- - -class FluentQuery(Generic[_M]): - """QModel 兼容层查询构建器,提供 SQLAlchemy 风格的链式 OOP 查询接口。 - - 由 ``Model.query`` 返回,支持链式调用:: - - # 基础查询 - Dingdan.query.filter(zhuangtai=8).all() - - # 链式过滤 - Dingdan.query.filter(jine__gt=100).order_by('-create_time').limit(10) - - # 字段表达式 - Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3])) - - # 聚合 - Dingdan.query.aggregate(total=Sum('jine')) - - # values / annotate - Dingdan.query.values('zhuangtai').annotate(cnt=Count('id')) - - # 分页 - Dingdan.query.paginate(page=1, per_page=20) - - 泛型参数 ``_M`` 携带模型类型,使 IDE 能推断链式调用返回的模型实例类型。 - """ - - def __init__(self, model_class: Type[_M]) -> None: - self._model: Type[_M] = model_class - self._qs: models.QuerySet = model_class.objects.all() - self._joined_models: List[Type[models.Model]] = [] - self._cache_enabled: bool = True - - @property - def query(self) -> Any: - """暴露底层 Django QuerySet 的 query 属性,支持 Subquery() 等场景。""" - return self._qs.query - - @property - def model(self) -> Type[_M]: - """返回关联的模型类。""" - return self._model - - def _clone(self) -> FluentQuery[_M]: - """克隆当前查询状态。""" - q = FluentQuery(self._model) - q._qs = self._qs - q._joined_models = list(self._joined_models) - q._cache_enabled = self._cache_enabled - return q - - def _cache_key(self, method: str) -> Tuple[Any, str, Any]: - """生成缓存键。""" - try: - query_str = str(self._qs.query) - except Exception: - query_str = id(self._qs) - return (self._model, method, query_str) - - def _remap_q(self, q: Q) -> Q: - """重映射 Q 对象中的字段名(用于 join 跨表查询)。""" - new_children: List[Any] = [] - for child in q.children: - if isinstance(child, Q): - new_children.append(self._remap_q(child)) - elif isinstance(child, tuple): - key, val = child - new_key = self._remap_key(key) - new_children.append((new_key, val)) - else: - new_children.append(child) - if new_children: - new_q = Q(*new_children) - new_q.connector = q.connector - new_q.negated = q.negated - return new_q - return q - - def _remap_key(self, key: str) -> str: - """重映射字段名(用于 join 跨表查询自动添加关联前缀)。""" - parts = key.split('__') - field_name = parts[0] - try: - self._model._meta.get_field(field_name) - return key - except Exception: - pass - for joined in self._joined_models: - try: - joined._meta.get_field(field_name) - for rel in self._model._meta.get_fields(include_hidden=True): - if hasattr(rel, 'related_model') and rel.related_model == joined: - acc = '' - if hasattr(rel, 'get_accessor_name'): - acc = rel.get_accessor_name() - elif hasattr(rel, 'name'): - acc = rel.name - if acc and acc != '+': - return acc + '__' + key - return joined._meta.model_name + '__' + key - except Exception: - pass - return key - - # ---- 过滤 ---- - - def filter(self, *args: Any, **kwargs: Any) -> FluentQuery[_M]: - """过滤查询,支持 Q 对象和关键字参数,返回 self 以支持链式调用。""" - converted_args: List[Any] = [] - for arg in args: - if isinstance(arg, Q) and self._joined_models: - converted_args.append(self._remap_q(arg)) - else: - converted_args.append(arg) - self._qs = self._qs.filter(*converted_args, **kwargs) - return self - - def exclude(self, *args: Any, **kwargs: Any) -> FluentQuery[_M]: - """排除查询,返回 self 以支持链式调用。""" - converted_args: List[Any] = [] - for arg in args: - if isinstance(arg, Q) and self._joined_models: - converted_args.append(self._remap_q(arg)) - else: - converted_args.append(arg) - self._qs = self._qs.exclude(*converted_args, **kwargs) - return self - - # ---- 排序 ---- - - def order_by(self, *args: Any) -> FluentQuery[_M]: - """排序,支持字符串和 FieldExpression,返回 self 以支持链式调用。""" - converted: List[str] = [] - for a in args: - if isinstance(a, str): - converted.append(a) - elif isinstance(a, _get_field_expression_cls()): - converted.append(str(a)) - else: - converted.append(str(a)) - self._qs = self._qs.order_by(*converted) - return self - - # ---- 获取记录 ---- - - def first(self) -> Optional[_M]: - """返回第一条记录或 None。""" - if self._cache_enabled: - cache = _get_cache() - key = self._cache_key('first') - if key in cache: - return cache[key] - obj = self._qs.first() - if obj is not None: - _get_session().track(obj) - if self._cache_enabled: - _get_cache()[self._cache_key('first')] = obj - return obj - - def last(self) -> Optional[_M]: - """返回最后一条记录或 None。""" - return self._qs.last() - - def get(self, *args: Any, **kwargs: Any) -> _M: - """获取唯一匹配记录,不存在或多个时抛异常。""" - if args: - obj = self._qs.filter(*args).get(**kwargs) - else: - obj = self._qs.get(**kwargs) - _get_session().track(obj) - return obj - - def all(self) -> FluentQuery[_M]: - """返回 self 以支持链式调用(与 Django QuerySet.all() 一致)。 - 如需获取列表,使用 ``.to_list()`` 或 ``list()``。""" - self._qs = self._qs.all() - return self - - def to_list(self) -> List[_M]: - """执行查询并返回模型实例列表。""" - if self._cache_enabled: - cache = _get_cache() - key = self._cache_key('to_list') - if key in cache: - return cache[key] - results = list(self._qs) - session = _get_session() - for obj in results: - session.track(obj) - if self._cache_enabled: - _get_cache()[self._cache_key('to_list')] = results - return results - - def count(self) -> int: - """返回匹配记录数。""" - if self._cache_enabled: - cache = _get_cache() - key = self._cache_key('count') - if key in cache: - return cache[key] - result = self._qs.count() - if self._cache_enabled: - _get_cache()[self._cache_key('count')] = result - return result - - def exists(self) -> bool: - """判断是否存在匹配记录。""" - return self._qs.exists() - - # ---- 创建/更新/删除 ---- - - def create(self, **kwargs: Any) -> _M: - """创建并保存新记录。""" - obj = self._model.objects.create(**kwargs) - _invalidate_cache() - return obj - - def get_or_create(self, defaults: Optional[Dict[str, Any]] = None, - **kwargs: Any) -> Tuple[_M, bool]: - """查询或创建记录,返回 ``(obj, created)`` 元组。""" - obj, created = self._model.objects.get_or_create(defaults=defaults, **kwargs) - if created: - _invalidate_cache() - _get_session().track(obj) - return obj, created - - def update_or_create(self, defaults: Optional[Dict[str, Any]] = None, - **kwargs: Any) -> Tuple[_M, bool]: - """更新或创建记录,返回 ``(obj, created)`` 元组。""" - obj, created = self._model.objects.update_or_create(defaults=defaults, **kwargs) - _invalidate_cache() - _get_session().track(obj) - return obj, created - - def bulk_create(self, objs: List[_M], batch_size: Optional[int] = None, - ignore_conflicts: bool = False) -> List[_M]: - """批量创建记录。""" - result = self._model.objects.bulk_create( - objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts - ) - _invalidate_cache() - return result - - def bulk_update(self, objs: List[_M], fields: List[str], - batch_size: Optional[int] = None) -> int: - """批量更新记录,返回受影响行数。""" - result = self._model.objects.bulk_update(objs, fields, batch_size=batch_size) - _invalidate_cache() - return result - - def update(self, **kwargs: Any) -> int: - """批量更新匹配记录,返回受影响行数。""" - result = self._qs.update(**kwargs) - _invalidate_cache() - return result - - def delete(self) -> Tuple[int, Dict[str, int]]: - """批量删除匹配记录,返回 ``(删除数, {表名: 删除数})``。""" - result = self._qs.delete() - _invalidate_cache() - return result - - # ---- 查询修饰 ---- - - def none(self) -> FluentQuery[_M]: - """返回空结果集,返回 self 以支持链式调用。""" - self._qs = self._qs.none() - return self - - def limit(self, n: int) -> FluentQuery[_M]: - """限制返回记录数,返回 self 以支持链式调用。""" - self._qs = self._qs[:n] - return self - - def offset(self, n: int) -> FluentQuery[_M]: - """跳过前 n 条记录,返回 self 以支持链式调用。""" - self._qs = self._qs[n:] - return self - - def join(self, *args: Type[models.Model]) -> FluentQuery[_M]: - """关联模型,用于跨表查询自动映射字段,返回 self 以支持链式调用。""" - for arg in args: - if isinstance(arg, type) and issubclass(arg, models.Model): - if arg not in self._joined_models: - self._joined_models.append(arg) - return self - - def select_related(self, *fields: str) -> FluentQuery[_M]: - """外键关联查询优化,返回 self 以支持链式调用。""" - self._qs = self._qs.select_related(*fields) - return self - - def prefetch_related(self, *fields: str) -> FluentQuery[_M]: - """多对多/反向关联查询优化,返回 self 以支持链式调用。""" - self._qs = self._qs.prefetch_related(*fields) - return self - - def only(self, *fields: str) -> FluentQuery[_M]: - """只加载指定字段,返回 self 以支持链式调用。""" - self._qs = self._qs.only(*fields) - return self - - def defer(self, *fields: str) -> FluentQuery[_M]: - """延迟加载指定字段,返回 self 以支持链式调用。""" - self._qs = self._qs.defer(*fields) - return self - - def select_for_update(self, nowait: bool = False, skip_locked: bool = False, - of: Tuple = (), no_key: bool = False) -> FluentQuery[_M]: - """行级锁,返回 self 以支持链式调用。""" - self._qs = self._qs.select_for_update( - nowait=nowait, skip_locked=skip_locked, of=of, no_key=no_key - ) - return self - - def distinct(self) -> FluentQuery[_M]: - """去重查询,返回 self 以支持链式调用。""" - self._qs = self._qs.distinct() - return self - - def using(self, alias: str) -> FluentQuery[_M]: - """指定数据库别名,返回 self 以支持链式调用。""" - self._qs = self._qs.using(alias) - return self - - def no_cache(self) -> FluentQuery[_M]: - """禁用查询缓存,返回 self 以支持链式调用。""" - self._cache_enabled = False - return self - - def params(self, **kwargs: Any) -> FluentQuery[_M]: - """参数占位(SQLAlchemy 兼容),返回 self 以支持链式调用。""" - return self - - # ---- 聚合与注解 ---- - - def annotate(self, **kwargs: Any) -> FluentQuery[_M]: - """注解查询,添加聚合/计算字段,返回 self 以支持链式调用。""" - self._qs = self._qs.annotate(**kwargs) - return self - - def aggregate(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: - """聚合查询,返回聚合结果字典。 - - 兼容 Django QuerySet.aggregate() 的两种调用方式: - aggregate(Max('field')) # 位置参数 - aggregate(field__max=Max('field')) # 关键字参数 - """ - return self._qs.aggregate(*args, **kwargs) - - def values(self, *fields: Union[str, _AggregateExpr, models.Expression]) -> _ValuesResult: - """返回指定字段的字典结果,支持链式调用。""" - if not fields: - return _ValuesResult(self._qs.values(), [], fields) - - annotations: Dict[str, models.Aggregate] = {} - converted: List[str] = [] - for i, f in enumerate(fields): - if isinstance(f, _AggregateExpr): - annotations[f'val{i}'] = f.aggregate - converted.append(f'val{i}') - elif isinstance(f, (models.Aggregate, models.Expression)): - annotations[f'val{i}'] = f - converted.append(f'val{i}') - elif isinstance(f, str): - converted.append(f) - else: - converted.append(str(f)) - - qs = self._qs - if annotations: - qs = qs.annotate(**annotations) - qs = qs.values(*converted) - return _ValuesResult(qs, converted, fields) - - def with_entities(self, *fields: Union[str, _AggregateExpr, models.Expression]) -> _ValuesResult: - """values() 的别名,SQLAlchemy 风格。""" - return self.values(*fields) - - def values_list(self, *fields: str, flat: bool = False) -> Any: - """返回指定字段的元组列表。""" - return self._qs.values_list(*fields, flat=flat) - - # ---- 分页 ---- - - def paginate(self, page: int = 1, per_page: int = 20, - error_out: bool = False) -> Any: - """分页查询,返回 Page 对象(含 items, total, pages 等属性)。""" - from django.core.paginator import Paginator, EmptyPage - if not self._qs.ordered: - meta = getattr(self._model, '_meta', None) - if meta and meta.pk: - self._qs = self._qs.order_by(meta.pk.name) - paginator = Paginator(self._qs, per_page) - try: - page_obj = paginator.page(page) - except EmptyPage: - page_obj = paginator.page(1) - page_obj.total = paginator.count - page_obj.items = page_obj.object_list - page_obj.pages = paginator.num_pages - page_obj.page = page - page_obj.per_page = per_page - page_obj.has_next = page_obj.has_next() - page_obj.has_prev = page_obj.has_previous() - page_obj.next_num = page_obj.next_page_number() if page_obj.has_next else None - page_obj.prev_num = page_obj.previous_page_number() if page_obj.has_prev else None - return page_obj - - # ---- 魔术方法 ---- - - def __getitem__(self, key: Union[int, slice]) -> Any: - """支持切片 ``[start:end]`` 和索引 ``[n]``。切片返回 self,索引返回模型实例。""" - if isinstance(key, slice): - self._qs = self._qs[key] - return self - else: - return self._qs[key] - - def __iter__(self) -> Iterator[_M]: - return iter(self._qs) - - def __len__(self) -> int: - return self.count() - - def __bool__(self) -> bool: - return self.exists() - - def __repr__(self) -> str: - return f'FluentQuery({self._model.__name__})' - - -# --------------------------------------------------------------------------- -# Session 会话管理 -# --------------------------------------------------------------------------- - -class Session: - """简易会话管理器,支持 add/delete/commit/rollback 操作。 - - 用法:: - - session = db.session - session.add(obj) - session.commit() - """ - - def __init__(self) -> None: - self._pending_adds: List[models.Model] = [] - self._pending_deletes: List[models.Model] = [] - self._tracked: set = set() - - def add(self, obj: models.Model) -> None: - """添加对象到待保存列表。""" - self._pending_adds.append(obj) - - def delete(self, obj: models.Model) -> None: - """添加对象到待删除列表。""" - self._pending_deletes.append(obj) - self._tracked.discard(obj) - - def track(self, obj: models.Model) -> None: - """跟踪对象变更(commit 时自动 save)。""" - if isinstance(obj, models.Model): - self._tracked.add(obj) - - def commit(self) -> None: - """提交所有待处理操作(删除 → 新增 → 更新)。""" - delete_pks = {(obj.__class__, obj.pk) for obj in self._pending_deletes - if obj.pk is not None} - with transaction.atomic(): - for obj in self._pending_deletes: - obj.delete() - for obj in self._pending_adds: - obj.save() - for obj in self._tracked: - if obj.pk is not None and (obj.__class__, obj.pk) in delete_pks: - continue - try: - obj.save() - except Exception: - logger.debug('Session.commit: skip tracked obj %s', obj, exc_info=True) - self._pending_adds.clear() - self._pending_deletes.clear() - self._tracked.clear() - _invalidate_cache() - - def rollback(self) -> None: - """回滚所有待处理操作。""" - self._pending_adds.clear() - self._pending_deletes.clear() - self._tracked.clear() - _invalidate_cache() - - def query(self, *args) -> Optional[FluentQuery[Any]]: - """创建查询构建器,支持传入模型类或聚合表达式。""" - model: Optional[Type[models.Model]] = None - aggregates: List = [] - for a in args: - if isinstance(a, type) and issubclass(a, models.Model): - model = a - elif isinstance(a, _AggregateExpr): - if a.model: - model = a.model - aggregates.append(a.aggregate) - elif isinstance(a, (models.Aggregate, models.Expression)): - aggregates.append(a) - if not aggregates and model: - return FluentQuery(model) - if aggregates and model: - fq = FluentQuery(model) - return fq.values(*args) - return None - - def flush(self) -> None: - """flush() 的别名,等同于 commit()。""" - self.commit() - - def execute(self, stmt) -> None: - """执行语句(支持 _DeleteStmt 和原始 SQL 字符串)。""" - if isinstance(stmt, _DeleteStmt): - stmt.execute() - elif isinstance(stmt, str): - connection = default_db_connection - with connection.cursor() as cursor: - cursor.execute(stmt) - - -# --------------------------------------------------------------------------- -# 全局入口 -# --------------------------------------------------------------------------- - -class DB: - """全局数据库操作入口。 - - 用法:: - - from gvsdsdk.fluent import db - - session = db.session - q = db.or_(Model.field == 1, Model.field == 2) - q = db.and_(Model.field > 0, Model.field < 100) - """ - - @property - def session(self) -> Session: - """获取当前线程的 Session 实例。""" - return _get_session() - - @staticmethod - def or_(*args: Q) -> Q: - """逻辑 OR,合并多个 Q 对象。""" - result: Optional[Q] = None - for q in args: - if isinstance(q, Q): - if result is None: - result = q - else: - result |= q - return result if result is not None else Q() - - @staticmethod - def and_(*args: Q) -> Q: - """逻辑 AND,合并多个 Q 对象。""" - result = Q() - for q in args: - if isinstance(q, Q): - result &= q - return result - - -db = DB() - - -def FQ(model_class: Type[_M]) -> FluentQuery[_M]: - """FluentQuery 工厂函数,等同于 ``Model.query``。""" - return FluentQuery(model_class) +"""FluentQuery 兼容层查询构建器 + +提供 SQLAlchemy 风格的链式 OOP 查询接口,包装 Django QuerySet。 + +核心类: +- ``FluentQuery`` — 链式查询构建器,由 ``Model.query`` 返回 +- ``Session`` — 简易会话管理(add/commit/rollback) +- ``_CompatFunc`` — 兼容层聚合函数(func.sum / func.count 等) +- ``db`` — 全局数据库操作入口(db.session / db.or_ / db.and_) + +典型用法:: + + from gvsdsdk.fluent import db, func, FQ + + # 链式查询 + orders = Dingdan.query.filter(zhuangtai=8).order_by('-CreateTime').to_list() + + # 字段表达式 + Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3])) + + # 聚合 + Dingdan.query.aggregate(total=Sum('jine')) + + # values / annotate + Dingdan.query.values('zhuangtai').annotate(cnt=Count('id')) + + # 分页 + Dingdan.query.paginate(page=1, per_page=20) + + # 兼容层聚合函数 + func.sum(Dingdan.jine) + func.count(Dingdan.id) + func.year(Dingdan.CreateTime) +""" +from __future__ import annotations + +import logging +import threading +from typing import Any, Dict, Generic, Iterator, List, Optional, Sequence, Tuple, Type, TypeVar, Union + +from django.db import connection as default_db_connection, models, transaction +from django.db.models import Q, F, Count, Sum, Avg, Min, Max, Subquery, OuterRef, Case, When, Value + +logger = logging.getLogger(__name__) + +_local = threading.local() + +#: 模型类型变量,用于 FluentQuery[_M] 泛型参数 +_M = TypeVar('_M', bound=models.Model) + + +def _get_field_expression_cls(): + """延迟导入 FieldExpression,避免 fluent ↔ model_base 循环依赖。""" + from gvsdsdk.model_base import FieldExpression + return FieldExpression + + +# --------------------------------------------------------------------------- +# 兼容层聚合函数 +# --------------------------------------------------------------------------- + +class _AggregateExpr: + """聚合表达式包装,关联模型类和 Django Aggregate 对象。""" + + def __init__(self, model: Optional[Type[models.Model]], aggregate: models.Aggregate) -> None: + self.model = model + self.aggregate = aggregate + + +class _ExtractExpr: + """日期提取表达式(year/month/day),用于 ``func.year(field) == 2024`` 等。""" + + def __init__(self, field_name: str, lookup_type: str) -> None: + self.field_name = field_name + self.lookup_type = lookup_type + + def __eq__(self, other) -> Q: # type: ignore[override] + return Q(**{f'{self.field_name}__{self.lookup_type}': other}) + + def __and__(self, other) -> Q: + if isinstance(other, Q): + return Q(**{f'{self.field_name}__{self.lookup_type}': None}) & other + return self.__eq__(other) + + +class _CompatFunc: + """兼容层聚合函数集合,提供 SQLAlchemy 风格的 ``func.sum()`` / ``func.count()`` 等。 + + 用法:: + + from gvsdsdk.fluent import func + + func.sum(Model.jine) → Sum('jine') + func.count(Model.id) → Count('id') + func.max(Model.jine) → Max('jine') + func.min(Model.jine) → Min('jine') + func.avg(Model.jine) → Avg('jine') + func.year(Model.riqi) → _ExtractExpr('riqi', 'year') + func.month(Model.riqi) → _ExtractExpr('riqi', 'month') + func.day(Model.riqi) → _ExtractExpr('riqi', 'day') + """ + + def sum(self, field_expr) -> Union[_AggregateExpr, Sum]: + """求和聚合。""" + name = self._resolve_name(field_expr) + agg = Sum(name) + model = getattr(field_expr, 'model_class', None) + return _AggregateExpr(model, agg) if model else agg + + def count(self, field_expr=None) -> Union[_AggregateExpr, Count]: + """计数聚合,无参数时等价于 COUNT(*)。""" + if field_expr is None: + return Count('*') + name = self._resolve_name(field_expr) + agg = Count(name) + model = getattr(field_expr, 'model_class', None) + return _AggregateExpr(model, agg) if model else agg + + def max(self, field_expr) -> Union[_AggregateExpr, Max]: + """最大值聚合。""" + name = self._resolve_name(field_expr) + agg = Max(name) + model = getattr(field_expr, 'model_class', None) + return _AggregateExpr(model, agg) if model else agg + + def min(self, field_expr) -> Union[_AggregateExpr, Min]: + """最小值聚合。""" + name = self._resolve_name(field_expr) + agg = Min(name) + model = getattr(field_expr, 'model_class', None) + return _AggregateExpr(model, agg) if model else agg + + def avg(self, field_expr) -> Union[_AggregateExpr, Avg]: + """平均值聚合。""" + name = self._resolve_name(field_expr) + agg = Avg(name) + model = getattr(field_expr, 'model_class', None) + return _AggregateExpr(model, agg) if model else agg + + def year(self, field_expr) -> _ExtractExpr: + """提取年份,用于 ``func.year(Model.date) == 2024``。""" + return _ExtractExpr(self._resolve_name(field_expr), 'year') + + def month(self, field_expr) -> _ExtractExpr: + """提取月份。""" + return _ExtractExpr(self._resolve_name(field_expr), 'month') + + def day(self, field_expr) -> _ExtractExpr: + """提取日期。""" + return _ExtractExpr(self._resolve_name(field_expr), 'day') + + @staticmethod + def _resolve_name(field_expr) -> str: + """从 FieldExpression 或字符串解析 Django 字段名。""" + if hasattr(field_expr, '_django_name'): + return field_expr._django_name + if hasattr(field_expr, 'field_name'): + return field_expr.field_name + return str(field_expr) + + +func = _CompatFunc() + + +# --------------------------------------------------------------------------- +# DELETE 语句构建器 +# --------------------------------------------------------------------------- + +class _DeleteStmt: + """DELETE 语句构建器,支持 ``Model.query.filter(...).delete()`` 风格。""" + + def __init__(self, model_class: Type[models.Model], conditions: Optional[Q] = None) -> None: + self.model_class = model_class + self.conditions = conditions or Q() + + def where(self, *args: Q, **kwargs: Any) -> _DeleteStmt: + """添加 WHERE 条件。""" + new_conditions = Q() + for a in args: + if isinstance(a, Q): + new_conditions &= a + if kwargs: + new_conditions &= Q(**kwargs) + self.conditions = new_conditions + return self + + def execute(self) -> Tuple[int, Dict[str, int]]: + """执行删除,返回 (删除数, {表名: 删除数})。""" + if self.model_class and self.conditions: + return self.model_class.objects.filter(self.conditions).delete() + elif self.model_class: + return self.model_class.objects.all().delete() + return (0, {}) + + +# --------------------------------------------------------------------------- +# 会话与缓存 +# --------------------------------------------------------------------------- + +def _get_session() -> Session: + """获取当前线程的 Session 实例。""" + if not hasattr(_local, 'db_session'): + _local.db_session = Session() + return _local.db_session + + +def _get_cache() -> Dict: + """获取当前线程的查询缓存。""" + if not hasattr(_local, 'query_cache'): + _local.query_cache = {} + return _local.query_cache + + +def _invalidate_cache() -> None: + """清除当前线程的查询缓存。""" + if hasattr(_local, 'query_cache'): + _local.query_cache.clear() + + +# --------------------------------------------------------------------------- +# 关联字段访问器 +# --------------------------------------------------------------------------- + +class RelationAccessor: + """关联字段访问器,支持 ``Model.relation.field`` 跨表字段表达式。 + + 用法:: + + # 自动解析关联字段 + Dingdan.query.filter(Dingdan.shangjia.nicheng == '测试') + """ + + def __init__(self, model_class: Type[models.Model], prefix: str = '') -> None: + self._model = model_class + self._prefix = prefix + + def __getattr__(self, name: str): + if name.startswith('_'): + return object.__getattribute__(self, name) + try: + self._model._meta.get_field(name) + full_name = f'{self._prefix}__{name}' if self._prefix else name + return _get_field_expression_cls()(self._model, full_name) + except Exception: + for rel in self._model._meta.get_fields(include_hidden=True): + if hasattr(rel, 'related_model') and rel.related_model: + rel_name = '' + if hasattr(rel, 'get_accessor_name'): + rel_name = rel.get_accessor_name() + elif hasattr(rel, 'name'): + rel_name = rel.name + if rel_name and rel_name == name: + full_prefix = f'{self._prefix}__{rel_name}' if self._prefix else rel_name + return RelationAccessor(rel.related_model, full_prefix) + return _get_field_expression_cls()(self._model, name) + + def __repr__(self) -> str: + return f'RelationAccessor({self._model.__name__}, prefix={self._prefix!r})' + + +# --------------------------------------------------------------------------- +# Values 结果集 +# --------------------------------------------------------------------------- + +class _ValuesResult: + """包装 Django ValuesQuerySet,支持链式调用。 + + 底层使用 Django ``.values()`` 返回字典,保持与 Django 行为一致。 + 额外提供 ``.all()`` 返回字典列表,``.first()`` 返回字典或 None, + ``.scalar()`` 返回单字段场景的标量值。 + """ + + def __init__(self, qs: models.QuerySet, fields: List[str], original_fields: Tuple) -> None: + self._qs: models.QuerySet = qs + self._fields: List[str] = fields + self._original_fields: Tuple = original_fields + + @property + def query(self) -> Any: + """暴露底层 Django QuerySet 的 query 属性,支持 Subquery() 等场景。""" + return self._qs.query + + def filter(self, *args: Any, **kwargs: Any) -> _ValuesResult: + self._qs = self._qs.filter(*args, **kwargs) + return self + + def exclude(self, *args: Any, **kwargs: Any) -> _ValuesResult: + self._qs = self._qs.exclude(*args, **kwargs) + return self + + def order_by(self, *fields: str) -> _ValuesResult: + self._qs = self._qs.order_by(*fields) + return self + + def distinct(self) -> _ValuesResult: + self._qs = self._qs.distinct() + return self + + def annotate(self, **kwargs: Any) -> _ValuesResult: + self._qs = self._qs.annotate(**kwargs) + return self + + def first(self) -> Optional[Dict[str, Any]]: + """返回第一条记录(字典)或 None。""" + return self._qs.first() + + def all(self) -> List[Dict[str, Any]]: + """返回所有记录的字典列表。""" + return list(self._qs) + + def scalar(self) -> Any: + """返回单字段场景的标量值。""" + row = self._qs.first() + if row is None: + return None + if self._fields: + return row.get(self._fields[0]) + return row + + def count(self) -> int: + return self._qs.count() + + def exists(self) -> bool: + return self._qs.exists() + + def __getitem__(self, key: Union[int, slice]) -> Any: + if isinstance(key, slice): + self._qs = self._qs[key] + return self + else: + return self._qs[key] + + def __iter__(self) -> Iterator[Dict[str, Any]]: + return iter(self._qs) + + def __len__(self) -> int: + return self.count() + + def __bool__(self) -> bool: + return self._qs.exists() + + def __repr__(self) -> str: + return f'_ValuesResult(fields={self._fields}, count={self.count()})' + + +# --------------------------------------------------------------------------- +# FluentQuery 核心查询构建器 +# --------------------------------------------------------------------------- + +class FluentQuery(Generic[_M]): + """QModel 兼容层查询构建器,提供 SQLAlchemy 风格的链式 OOP 查询接口。 + + 由 ``Model.query`` 返回,支持链式调用:: + + # 基础查询 + Dingdan.query.filter(zhuangtai=8).all() + + # 链式过滤 + Dingdan.query.filter(jine__gt=100).order_by('-CreateTime').limit(10) + + # 字段表达式 + Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3])) + + # 聚合 + Dingdan.query.aggregate(total=Sum('jine')) + + # values / annotate + Dingdan.query.values('zhuangtai').annotate(cnt=Count('id')) + + # 分页 + Dingdan.query.paginate(page=1, per_page=20) + + 泛型参数 ``_M`` 携带模型类型,使 IDE 能推断链式调用返回的模型实例类型。 + """ + + def __init__(self, model_class: Type[_M]) -> None: + self._model: Type[_M] = model_class + self._qs: models.QuerySet = model_class.objects.all() + self._joined_models: List[Type[models.Model]] = [] + self._cache_enabled: bool = True + + @property + def query(self) -> Any: + """暴露底层 Django QuerySet 的 query 属性,支持 Subquery() 等场景。""" + return self._qs.query + + @property + def model(self) -> Type[_M]: + """返回关联的模型类。""" + return self._model + + def _clone(self) -> FluentQuery[_M]: + """克隆当前查询状态。""" + q = FluentQuery(self._model) + q._qs = self._qs + q._joined_models = list(self._joined_models) + q._cache_enabled = self._cache_enabled + return q + + def _cache_key(self, method: str) -> Tuple[Any, str, Any]: + """生成缓存键。""" + try: + query_str = str(self._qs.query) + except Exception: + query_str = id(self._qs) + return (self._model, method, query_str) + + def _remap_q(self, q: Q) -> Q: + """重映射 Q 对象中的字段名(用于 join 跨表查询)。""" + new_children: List[Any] = [] + for child in q.children: + if isinstance(child, Q): + new_children.append(self._remap_q(child)) + elif isinstance(child, tuple): + key, val = child + new_key = self._remap_key(key) + new_children.append((new_key, val)) + else: + new_children.append(child) + if new_children: + new_q = Q(*new_children) + new_q.connector = q.connector + new_q.negated = q.negated + return new_q + return q + + def _remap_key(self, key: str) -> str: + """重映射字段名(用于 join 跨表查询自动添加关联前缀)。""" + parts = key.split('__') + field_name = parts[0] + try: + self._model._meta.get_field(field_name) + return key + except Exception: + pass + for joined in self._joined_models: + try: + joined._meta.get_field(field_name) + for rel in self._model._meta.get_fields(include_hidden=True): + if hasattr(rel, 'related_model') and rel.related_model == joined: + acc = '' + if hasattr(rel, 'get_accessor_name'): + acc = rel.get_accessor_name() + elif hasattr(rel, 'name'): + acc = rel.name + if acc and acc != '+': + return acc + '__' + key + return joined._meta.model_name + '__' + key + except Exception: + pass + return key + + # ---- 过滤 ---- + + def filter(self, *args: Any, **kwargs: Any) -> FluentQuery[_M]: + """过滤查询,支持 Q 对象和关键字参数,返回 self 以支持链式调用。""" + converted_args: List[Any] = [] + for arg in args: + if isinstance(arg, Q) and self._joined_models: + converted_args.append(self._remap_q(arg)) + else: + converted_args.append(arg) + self._qs = self._qs.filter(*converted_args, **kwargs) + return self + + def exclude(self, *args: Any, **kwargs: Any) -> FluentQuery[_M]: + """排除查询,返回 self 以支持链式调用。""" + converted_args: List[Any] = [] + for arg in args: + if isinstance(arg, Q) and self._joined_models: + converted_args.append(self._remap_q(arg)) + else: + converted_args.append(arg) + self._qs = self._qs.exclude(*converted_args, **kwargs) + return self + + # ---- 排序 ---- + + def order_by(self, *args: Any) -> FluentQuery[_M]: + """排序,支持字符串和 FieldExpression,返回 self 以支持链式调用。""" + converted: List[str] = [] + for a in args: + if isinstance(a, str): + converted.append(a) + elif isinstance(a, _get_field_expression_cls()): + converted.append(str(a)) + else: + converted.append(str(a)) + self._qs = self._qs.order_by(*converted) + return self + + # ---- 获取记录 ---- + + def first(self) -> Optional[_M]: + """返回第一条记录或 None。""" + if self._cache_enabled: + cache = _get_cache() + key = self._cache_key('first') + if key in cache: + return cache[key] + obj = self._qs.first() + if obj is not None: + _get_session().track(obj) + if self._cache_enabled: + _get_cache()[self._cache_key('first')] = obj + return obj + + def last(self) -> Optional[_M]: + """返回最后一条记录或 None。""" + return self._qs.last() + + def get(self, *args: Any, **kwargs: Any) -> _M: + """获取唯一匹配记录,不存在或多个时抛异常。""" + if args: + obj = self._qs.filter(*args).get(**kwargs) + else: + obj = self._qs.get(**kwargs) + _get_session().track(obj) + return obj + + def all(self) -> FluentQuery[_M]: + """返回 self 以支持链式调用(与 Django QuerySet.all() 一致)。 + 如需获取列表,使用 ``.to_list()`` 或 ``list()``。""" + self._qs = self._qs.all() + return self + + def to_list(self) -> List[_M]: + """执行查询并返回模型实例列表。""" + if self._cache_enabled: + cache = _get_cache() + key = self._cache_key('to_list') + if key in cache: + return cache[key] + results = list(self._qs) + session = _get_session() + for obj in results: + session.track(obj) + if self._cache_enabled: + _get_cache()[self._cache_key('to_list')] = results + return results + + def count(self) -> int: + """返回匹配记录数。""" + if self._cache_enabled: + cache = _get_cache() + key = self._cache_key('count') + if key in cache: + return cache[key] + result = self._qs.count() + if self._cache_enabled: + _get_cache()[self._cache_key('count')] = result + return result + + def exists(self) -> bool: + """判断是否存在匹配记录。""" + return self._qs.exists() + + # ---- 创建/更新/删除 ---- + + def create(self, **kwargs: Any) -> _M: + """创建并保存新记录。""" + obj = self._model.objects.create(**kwargs) + _invalidate_cache() + return obj + + def get_or_create(self, defaults: Optional[Dict[str, Any]] = None, + **kwargs: Any) -> Tuple[_M, bool]: + """查询或创建记录,返回 ``(obj, created)`` 元组。""" + obj, created = self._model.objects.get_or_create(defaults=defaults, **kwargs) + if created: + _invalidate_cache() + _get_session().track(obj) + return obj, created + + def update_or_create(self, defaults: Optional[Dict[str, Any]] = None, + **kwargs: Any) -> Tuple[_M, bool]: + """更新或创建记录,返回 ``(obj, created)`` 元组。""" + obj, created = self._model.objects.update_or_create(defaults=defaults, **kwargs) + _invalidate_cache() + _get_session().track(obj) + return obj, created + + def bulk_create(self, objs: List[_M], batch_size: Optional[int] = None, + ignore_conflicts: bool = False) -> List[_M]: + """批量创建记录。""" + result = self._model.objects.bulk_create( + objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts + ) + _invalidate_cache() + return result + + def bulk_update(self, objs: List[_M], fields: List[str], + batch_size: Optional[int] = None) -> int: + """批量更新记录,返回受影响行数。""" + result = self._model.objects.bulk_update(objs, fields, batch_size=batch_size) + _invalidate_cache() + return result + + def update(self, **kwargs: Any) -> int: + """批量更新匹配记录,返回受影响行数。""" + result = self._qs.update(**kwargs) + _invalidate_cache() + return result + + def delete(self) -> Tuple[int, Dict[str, int]]: + """批量删除匹配记录,返回 ``(删除数, {表名: 删除数})``。""" + result = self._qs.delete() + _invalidate_cache() + return result + + # ---- 查询修饰 ---- + + def none(self) -> FluentQuery[_M]: + """返回空结果集,返回 self 以支持链式调用。""" + self._qs = self._qs.none() + return self + + def limit(self, n: int) -> FluentQuery[_M]: + """限制返回记录数,返回 self 以支持链式调用。""" + self._qs = self._qs[:n] + return self + + def offset(self, n: int) -> FluentQuery[_M]: + """跳过前 n 条记录,返回 self 以支持链式调用。""" + self._qs = self._qs[n:] + return self + + def join(self, *args: Type[models.Model]) -> FluentQuery[_M]: + """关联模型,用于跨表查询自动映射字段,返回 self 以支持链式调用。""" + for arg in args: + if isinstance(arg, type) and issubclass(arg, models.Model): + if arg not in self._joined_models: + self._joined_models.append(arg) + return self + + def select_related(self, *fields: str) -> FluentQuery[_M]: + """外键关联查询优化,返回 self 以支持链式调用。""" + self._qs = self._qs.select_related(*fields) + return self + + def prefetch_related(self, *fields: str) -> FluentQuery[_M]: + """多对多/反向关联查询优化,返回 self 以支持链式调用。""" + self._qs = self._qs.prefetch_related(*fields) + return self + + def only(self, *fields: str) -> FluentQuery[_M]: + """只加载指定字段,返回 self 以支持链式调用。""" + self._qs = self._qs.only(*fields) + return self + + def defer(self, *fields: str) -> FluentQuery[_M]: + """延迟加载指定字段,返回 self 以支持链式调用。""" + self._qs = self._qs.defer(*fields) + return self + + def select_for_update(self, nowait: bool = False, skip_locked: bool = False, + of: Tuple = (), no_key: bool = False) -> FluentQuery[_M]: + """行级锁,返回 self 以支持链式调用。""" + self._qs = self._qs.select_for_update( + nowait=nowait, skip_locked=skip_locked, of=of, no_key=no_key + ) + return self + + def distinct(self) -> FluentQuery[_M]: + """去重查询,返回 self 以支持链式调用。""" + self._qs = self._qs.distinct() + return self + + def using(self, alias: str) -> FluentQuery[_M]: + """指定数据库别名,返回 self 以支持链式调用。""" + self._qs = self._qs.using(alias) + return self + + def no_cache(self) -> FluentQuery[_M]: + """禁用查询缓存,返回 self 以支持链式调用。""" + self._cache_enabled = False + return self + + def params(self, **kwargs: Any) -> FluentQuery[_M]: + """参数占位(SQLAlchemy 兼容),返回 self 以支持链式调用。""" + return self + + # ---- 聚合与注解 ---- + + def annotate(self, **kwargs: Any) -> FluentQuery[_M]: + """注解查询,添加聚合/计算字段,返回 self 以支持链式调用。""" + self._qs = self._qs.annotate(**kwargs) + return self + + def aggregate(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: + """聚合查询,返回聚合结果字典。 + + 兼容 Django QuerySet.aggregate() 的两种调用方式: + aggregate(Max('field')) # 位置参数 + aggregate(field__max=Max('field')) # 关键字参数 + """ + return self._qs.aggregate(*args, **kwargs) + + def values(self, *fields: Union[str, _AggregateExpr, models.Expression]) -> _ValuesResult: + """返回指定字段的字典结果,支持链式调用。""" + if not fields: + return _ValuesResult(self._qs.values(), [], fields) + + annotations: Dict[str, models.Aggregate] = {} + converted: List[str] = [] + for i, f in enumerate(fields): + if isinstance(f, _AggregateExpr): + annotations[f'val{i}'] = f.aggregate + converted.append(f'val{i}') + elif isinstance(f, (models.Aggregate, models.Expression)): + annotations[f'val{i}'] = f + converted.append(f'val{i}') + elif isinstance(f, str): + converted.append(f) + else: + converted.append(str(f)) + + qs = self._qs + if annotations: + qs = qs.annotate(**annotations) + qs = qs.values(*converted) + return _ValuesResult(qs, converted, fields) + + def with_entities(self, *fields: Union[str, _AggregateExpr, models.Expression]) -> _ValuesResult: + """values() 的别名,SQLAlchemy 风格。""" + return self.values(*fields) + + def values_list(self, *fields: str, flat: bool = False) -> Any: + """返回指定字段的元组列表。""" + return self._qs.values_list(*fields, flat=flat) + + # ---- 分页 ---- + + def paginate(self, page: int = 1, per_page: int = 20, + error_out: bool = False) -> Any: + """分页查询,返回 Page 对象(含 items, total, pages 等属性)。""" + from django.core.paginator import Paginator, EmptyPage + if not self._qs.ordered: + meta = getattr(self._model, '_meta', None) + if meta and meta.pk: + self._qs = self._qs.order_by(meta.pk.name) + paginator = Paginator(self._qs, per_page) + try: + page_obj = paginator.page(page) + except EmptyPage: + page_obj = paginator.page(1) + page_obj.total = paginator.count + page_obj.items = page_obj.object_list + page_obj.pages = paginator.num_pages + page_obj.page = page + page_obj.per_page = per_page + page_obj.has_next = page_obj.has_next() + page_obj.has_prev = page_obj.has_previous() + page_obj.next_num = page_obj.next_page_number() if page_obj.has_next else None + page_obj.prev_num = page_obj.previous_page_number() if page_obj.has_prev else None + return page_obj + + # ---- 魔术方法 ---- + + def __getitem__(self, key: Union[int, slice]) -> Any: + """支持切片 ``[start:end]`` 和索引 ``[n]``。切片返回 self,索引返回模型实例。""" + if isinstance(key, slice): + self._qs = self._qs[key] + return self + else: + return self._qs[key] + + def __iter__(self) -> Iterator[_M]: + return iter(self._qs) + + def __len__(self) -> int: + return self.count() + + def __bool__(self) -> bool: + return self.exists() + + def __repr__(self) -> str: + return f'FluentQuery({self._model.__name__})' + + +# --------------------------------------------------------------------------- +# Session 会话管理 +# --------------------------------------------------------------------------- + +class Session: + """简易会话管理器,支持 add/delete/commit/rollback 操作。 + + 用法:: + + session = db.session + session.add(obj) + session.commit() + """ + + def __init__(self) -> None: + self._pending_adds: List[models.Model] = [] + self._pending_deletes: List[models.Model] = [] + self._tracked: set = set() + + def add(self, obj: models.Model) -> None: + """添加对象到待保存列表。""" + self._pending_adds.append(obj) + + def delete(self, obj: models.Model) -> None: + """添加对象到待删除列表。""" + self._pending_deletes.append(obj) + self._tracked.discard(obj) + + def track(self, obj: models.Model) -> None: + """跟踪对象变更(commit 时自动 save)。""" + if isinstance(obj, models.Model): + self._tracked.add(obj) + + def commit(self) -> None: + """提交所有待处理操作(删除 → 新增 → 更新)。""" + delete_pks = {(obj.__class__, obj.pk) for obj in self._pending_deletes + if obj.pk is not None} + with transaction.atomic(): + for obj in self._pending_deletes: + obj.delete() + for obj in self._pending_adds: + obj.save() + for obj in self._tracked: + if obj.pk is not None and (obj.__class__, obj.pk) in delete_pks: + continue + try: + obj.save() + except Exception: + logger.debug('Session.commit: skip tracked obj %s', obj, exc_info=True) + self._pending_adds.clear() + self._pending_deletes.clear() + self._tracked.clear() + _invalidate_cache() + + def rollback(self) -> None: + """回滚所有待处理操作。""" + self._pending_adds.clear() + self._pending_deletes.clear() + self._tracked.clear() + _invalidate_cache() + + def query(self, *args) -> Optional[FluentQuery[Any]]: + """创建查询构建器,支持传入模型类或聚合表达式。""" + model: Optional[Type[models.Model]] = None + aggregates: List = [] + for a in args: + if isinstance(a, type) and issubclass(a, models.Model): + model = a + elif isinstance(a, _AggregateExpr): + if a.model: + model = a.model + aggregates.append(a.aggregate) + elif isinstance(a, (models.Aggregate, models.Expression)): + aggregates.append(a) + if not aggregates and model: + return FluentQuery(model) + if aggregates and model: + fq = FluentQuery(model) + return fq.values(*args) + return None + + def flush(self) -> None: + """flush() 的别名,等同于 commit()。""" + self.commit() + + def execute(self, stmt) -> None: + """执行语句(支持 _DeleteStmt 和原始 SQL 字符串)。""" + if isinstance(stmt, _DeleteStmt): + stmt.execute() + elif isinstance(stmt, str): + connection = default_db_connection + with connection.cursor() as cursor: + cursor.execute(stmt) + + +# --------------------------------------------------------------------------- +# 全局入口 +# --------------------------------------------------------------------------- + +class DB: + """全局数据库操作入口。 + + 用法:: + + from gvsdsdk.fluent import db + + session = db.session + q = db.or_(Model.field == 1, Model.field == 2) + q = db.and_(Model.field > 0, Model.field < 100) + """ + + @property + def session(self) -> Session: + """获取当前线程的 Session 实例。""" + return _get_session() + + @staticmethod + def or_(*args: Q) -> Q: + """逻辑 OR,合并多个 Q 对象。""" + result: Optional[Q] = None + for q in args: + if isinstance(q, Q): + if result is None: + result = q + else: + result |= q + return result if result is not None else Q() + + @staticmethod + def and_(*args: Q) -> Q: + """逻辑 AND,合并多个 Q 对象。""" + result = Q() + for q in args: + if isinstance(q, Q): + result &= q + return result + + +db = DB() + + +def FQ(model_class: Type[_M]) -> FluentQuery[_M]: + """FluentQuery 工厂函数,等同于 ``Model.query``。""" + return FluentQuery(model_class) diff --git a/gvsdsdk/migrations/0002_alter_user_useruuid.py b/gvsdsdk/migrations/0002_alter_user_useruuid.py deleted file mode 100644 index 58f2003..0000000 --- a/gvsdsdk/migrations/0002_alter_user_useruuid.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.2.27 on 2026-06-19 01:54 - -from django.db import migrations -import gvsdsdk.models -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('gvsdsdk', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='UserUUID', - field=gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='用户UUID'), - ), - ] diff --git a/gvsdsdk/migrations/0002_position_roleposition_roleshop_shop.py b/gvsdsdk/migrations/0002_position_roleposition_roleshop_shop.py deleted file mode 100644 index 8251940..0000000 --- a/gvsdsdk/migrations/0002_position_roleposition_roleshop_shop.py +++ /dev/null @@ -1,90 +0,0 @@ -# Generated by Django 6.0.5 on 2026-06-13 12:29 - -import datetime -import gvsdsdk.models -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gvsdsdk', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Position', - fields=[ - ('PositionUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionUUID', primary_key=True, serialize=False)), - ('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID')), - ('PositionName', models.CharField(db_column='PositionName', max_length=128)), - ('PositionCode', models.CharField(db_column='PositionCode', default='', max_length=64)), - ('PositionLevel', models.SmallIntegerField(db_column='PositionLevel', default=0)), - ('PositionStatus', models.SmallIntegerField(db_column='PositionStatus', default=1)), - ('PositionDesc', models.CharField(db_column='PositionDesc', default='', max_length=512)), - ('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)), - ('UpdateTime', models.DateTimeField(db_column='UpdateTime', default=datetime.datetime.utcnow)), - ], - options={ - 'verbose_name': '职务', - 'verbose_name_plural': '职务', - 'db_table': 'Position', - 'managed': True, - }, - ), - migrations.CreateModel( - name='RolePosition', - fields=[ - ('RolePositionUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RolePositionUUID', primary_key=True, serialize=False)), - ('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')), - ('PositionUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionUUID')), - ('IsMainPosition', models.SmallIntegerField(db_column='IsMainPosition', default=0)), - ('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)), - ], - options={ - 'verbose_name': '角色职务关联', - 'verbose_name_plural': '角色职务关联', - 'db_table': 'RolePosition', - 'managed': True, - }, - ), - migrations.CreateModel( - name='RoleShop', - fields=[ - ('RoleShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleShopUUID', primary_key=True, serialize=False)), - ('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')), - ('ShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ShopUUID')), - ('IsMainShop', models.SmallIntegerField(db_column='IsMainShop', default=0)), - ('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)), - ], - options={ - 'verbose_name': '角色商铺关联', - 'verbose_name_plural': '角色商铺关联', - 'db_table': 'RoleShop', - 'managed': True, - }, - ), - migrations.CreateModel( - name='Shop', - fields=[ - ('ShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ShopUUID', primary_key=True, serialize=False)), - ('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID')), - ('ShopName', models.CharField(db_column='ShopName', max_length=128)), - ('ShopCode', models.CharField(db_column='ShopCode', default='', max_length=64)), - ('ShopType', models.SmallIntegerField(db_column='ShopType', default=0)), - ('ShopStatus', models.SmallIntegerField(db_column='ShopStatus', default=1)), - ('ShopAddress', models.CharField(db_column='ShopAddress', default='', max_length=512)), - ('ShopContact', models.CharField(db_column='ShopContact', default='', max_length=128)), - ('ShopPhone', models.CharField(db_column='ShopPhone', default='', max_length=32)), - ('ShopDesc', models.CharField(db_column='ShopDesc', default='', max_length=512)), - ('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)), - ('UpdateTime', models.DateTimeField(db_column='UpdateTime', default=datetime.datetime.utcnow)), - ], - options={ - 'verbose_name': '商铺', - 'verbose_name_plural': '商铺', - 'db_table': 'Shop', - 'managed': True, - }, - ), - ] diff --git a/gvsdsdk/model_base.py b/gvsdsdk/model_base.py index 360cf16..01c3924 100644 --- a/gvsdsdk/model_base.py +++ b/gvsdsdk/model_base.py @@ -1,540 +1,540 @@ -"""QModel 兼容层基类模块 - -提供 SQLAlchemy 风格的链式 OOP 查询接口,使 Django ORM 支持: - -- ``Model.query.filter(...)`` — 链式 FluentQuery 查询 -- ``Model.field_name`` — 类级别返回 FieldExpression(支持 ``==``, ``>``, ``in_()`` 等运算符) -- ``Model.field_name`` — 实例级别返回字段值(与原生 Django Model 一致) -- ``Model[condition]`` — 快捷 filter 语法 - -典型用法:: - - # 链式查询 - orders = Dingdan.query.filter(zhuangtai=8).order_by('-create_time').to_list() - - # 字段表达式 - Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1, 2, 3])) - - # 聚合 - Dingdan.query.aggregate(total=Sum('jine')) - - # 分页 - Dingdan.query.paginate(page=1, per_page=20) -""" -from __future__ import annotations - -import re -from typing import Any, Callable, Dict, Generic, Optional, Tuple, Type, TypeVar, Union - -try: - from typing import Self -except ImportError: - from typing_extensions import Self - -from django.db import models -from django.db.models import Q - -from .fluent import FluentQuery - - -# --------------------------------------------------------------------------- -# 工具函数 -# --------------------------------------------------------------------------- - -#: classproperty 返回值类型变量 -_RT = TypeVar('_RT') - -#: QModel 子类类型变量(仅用于 __class_getitem__ 兼容) -_QM = TypeVar('_QM', bound='QModel') - - -class classproperty(Generic[_RT]): - """类属性描述符,支持 ``Model.query`` 等类级别属性访问。 - - 泛型参数 ``_RT`` 传播函数返回类型,使 Pylance 能穿透描述符推断 - ``Model.query`` 的返回类型为 ``FluentQuery[Self]``。 - """ - - def __init__(self, func: Callable[[type], _RT]) -> None: - self.fget = func - - def __get__(self, obj: Any, objtype: Optional[type] = None) -> _RT: - return self.fget(objtype if objtype is not None else type(obj)) - - -ABBREVIATIONS: Dict[str, str] = { - 'uuid': 'UUID', 'id': 'ID', 'ip': 'IP', 'url': 'URL', 'uri': 'URI', - 'api': 'API', 'html': 'HTML', 'css': 'CSS', 'js': 'JS', 'json': 'JSON', - 'xml': 'XML', 'sql': 'SQL', 'http': 'HTTP', 'https': 'HTTPS', - 'smtp': 'SMTP', 'ssl': 'SSL', 'tls': 'TLS', 'ssh': 'SSH', 'ftp': 'FTP', - '2fa': '2FA', 'elt': 'ELT', 'sve': 'SVE', 't4': 'T4', 'mc': 'MC', - 'crb': 'CRB', 'ccgo': 'CCGO', 'ugm': 'UGM', 'gerp': 'GERP', - 'abac': 'ABAC', 'rbac': 'RBAC', 'smtm': 'SMTM', 'pwd': 'PWD', -} - - -def snake_to_pascal(name: str) -> str: - """将 snake_case 转换为 PascalCase,自动识别常见缩写。 - - >>> snake_to_pascal('user_uuid') - 'UserUUID' - >>> snake_to_pascal('paifa_dingdan_shu') - 'PaifaDingdanShu' - """ - if '_' not in name: - return name[0].upper() + name[1:] if name else name - parts = name.split('_') - result = [] - for p in parts: - lower = p.lower() - if lower in ABBREVIATIONS: - result.append(ABBREVIATIONS[lower]) - else: - result.append(p.capitalize()) - return ''.join(result) - - -def pascal_to_snake(name: str) -> str: - """将 PascalCase 转换为 snake_case。 - - >>> pascal_to_snake('UserUUID') - 'user_uuid' - """ - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - s2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1) - return s2.lower() - - -# --------------------------------------------------------------------------- -# 字段表达式(类级别访问 Model.field 时返回) -# --------------------------------------------------------------------------- - -class _InvertedFieldExpression: - """取反字段表达式,由 ``~Model.field`` 产生,支持 ``~field.startswith(...)`` 等否定查询。""" - - def __init__(self, field_expr: FieldExpression) -> None: - self._field_expr = field_expr - - def startswith(self, value: str) -> Q: - return ~Q(**{f'{self._field_expr._django_name}__startswith': value}) - - def contains(self, value: str) -> Q: - return ~Q(**{f'{self._field_expr._django_name}__contains': value}) - - def like(self, pattern: str) -> Q: - clean = pattern.strip('%') - return ~Q(**{f'{self._field_expr._django_name}__icontains': clean}) - - def in_(self, values) -> Q: - return ~Q(**{f'{self._field_expr._django_name}__in': values}) - - def is_(self, other) -> Q: - if other is None: - return ~Q(**{f'{self._field_expr._django_name}__isnull': True}) - return ~Q(**{self._field_expr._django_name: other}) - - def between(self, low, high) -> Q: - return ~(Q(**{f'{self._field_expr._django_name}__gte': low}) - & Q(**{f'{self._field_expr._django_name}__lte': high})) - - def __repr__(self) -> str: - return f'~{self._field_expr!r}' - - -class FieldExpression: - """字段表达式,由类级别 ``Model.field_name`` 访问产生。 - - 支持通过 Python 运算符构建 Django Q 对象:: - - Model.field == value → Q(field=value) - Model.field > value → Q(field__gt=value) - Model.field.in_([1,2,3]) → Q(field__in=[1,2,3]) - Model.field.between(a, b) → Q(field__gte=a) & Q(field__lte=b) - ~Model.field → _InvertedFieldExpression(否定查询) - """ - - _resolve_cache: Dict[Tuple[str, str], str] = {} - - def __init__(self, model_class: Type, field_name: str) -> None: - self.model_class = model_class - self.field_name = field_name - self._django_name: str = self._resolve_django_name() - - def _resolve_django_name(self) -> str: - """将 Python 字段名解析为 Django ORM 查询使用的字段名。""" - cache_key = (self.model_class.__name__, self.field_name) - if cache_key in FieldExpression._resolve_cache: - return FieldExpression._resolve_cache[cache_key] - if hasattr(self.model_class, '_meta'): - for f in self.model_class._meta.local_fields: - if f.name == self.field_name: - FieldExpression._resolve_cache[cache_key] = f.name - return f.name - FieldExpression._resolve_cache[cache_key] = self.field_name - return self.field_name - - # ---- 比较运算符 ---- - - def __eq__(self, other) -> Q: # type: ignore[override] - if isinstance(other, FieldExpression): - return NotImplemented - if other is None: - return Q(**{f'{self._django_name}__isnull': True}) - return Q(**{self._django_name: other}) - - def __ne__(self, other) -> Q: # type: ignore[override] - if isinstance(other, FieldExpression): - return NotImplemented - return ~Q(**{self._django_name: other}) - - def __gt__(self, other) -> Q: - return Q(**{f'{self._django_name}__gt': other}) - - def __ge__(self, other) -> Q: - return Q(**{f'{self._django_name}__gte': other}) - - def __lt__(self, other) -> Q: - return Q(**{f'{self._django_name}__lt': other}) - - def __le__(self, other) -> Q: - return Q(**{f'{self._django_name}__lte': other}) - - def __hash__(self) -> int: - return hash((self.model_class.__name__, self.field_name)) - - # ---- 逻辑运算符 ---- - - def __and__(self, other) -> Q: - if isinstance(other, Q): - return Q(self) & other - if isinstance(other, FieldExpression): - return Q(self) & Q(other) - return NotImplemented - - def __or__(self, other) -> Q: - if isinstance(other, Q): - return Q(self) | other - if isinstance(other, FieldExpression): - return Q(self) | Q(other) - return NotImplemented - - def __invert__(self) -> _InvertedFieldExpression: - return _InvertedFieldExpression(self) - - # ---- 查询方法 ---- - - def is_(self, other) -> Q: - """``field IS NULL`` 或 ``field IS value``。""" - if other is None: - return Q(**{f'{self._django_name}__isnull': True}) - return Q(**{self._django_name: other}) - - def is_not(self, other) -> Q: - """``field IS NOT NULL`` 或 ``field IS NOT value``。""" - if other is None: - return Q(**{f'{self._django_name}__isnull': False}) - return ~Q(**{self._django_name: other}) - - def like(self, pattern: str) -> Q: - """模糊匹配(不区分大小写),自动去除 ``%`` 通配符。""" - clean = pattern.strip('%') - return Q(**{f'{self._django_name}__icontains': clean}) - - def contains(self, value: str) -> Q: - """包含子串(区分大小写)。""" - return Q(**{f'{self._django_name}__contains': value}) - - def ilike(self, pattern: str) -> Q: - """like() 的别名。""" - return self.like(pattern) - - def startswith(self, value: str) -> Q: - """前缀匹配(区分大小写)。""" - return Q(**{f'{self._django_name}__startswith': value}) - - def istartswith(self, value: str) -> Q: - """前缀匹配(不区分大小写)。""" - return Q(**{f'{self._django_name}__istartswith': value}) - - def endswith(self, value: str) -> Q: - """后缀匹配(区分大小写)。""" - return Q(**{f'{self._django_name}__endswith': value}) - - def iendswith(self, value: str) -> Q: - """后缀匹配(不区分大小写)。""" - return Q(**{f'{self._django_name}__iendswith': value}) - - def in_(self, values) -> Q: - """``field IN (values)``,支持列表和 FluentQuery 子查询。""" - if hasattr(values, '_qs'): - return Q(**{f'{self._django_name}__in': values._qs.values(self._django_name)}) - return Q(**{f'{self._django_name}__in': values}) - - def between(self, low, high) -> Q: - """``field BETWEEN low AND high``。""" - return Q(**{f'{self._django_name}__gte': low}) & Q(**{f'{self._django_name}__lte': high}) - - def regex(self, pattern: str) -> Q: - """正则匹配(区分大小写)。""" - return Q(**{f'{self._django_name}__regex': pattern}) - - def iregex(self, pattern: str) -> Q: - """正则匹配(不区分大小写)。""" - return Q(**{f'{self._django_name}__iregex': pattern}) - - # ---- 排序辅助 ---- - - def desc(self) -> str: - """返回降序排序字符串 ``'-field_name'``。""" - return f'-{self._django_name}' - - def asc(self) -> str: - """返回升序排序字符串 ``'field_name'``。""" - return self._django_name - - # ---- JSON 字段访问 ---- - - def __getitem__(self, key: str) -> _JsonKeyExpression: - """访问 JSON 字段内的键,如 ``Model.meta['key'] == value``。""" - return _JsonKeyExpression(self._django_name, key) - - # ---- 显示 ---- - - def __repr__(self) -> str: - return f'FieldExpr({self.model_class.__name__}.{self.field_name})' - - def __str__(self) -> str: - return self._django_name - - -class _JsonKeyExpression: - """JSON 字段键表达式,由 ``Model.json_field['key']`` 产生。""" - - def __init__(self, field_name: str, key: str) -> None: - self.field_name = field_name - self.key = key - - def _lookup(self, suffix: str = '') -> str: - if suffix: - return f'{self.field_name}__{self.key}__{suffix}' - return f'{self.field_name}__{self.key}' - - def cast(self, type_hint) -> _CastJsonExpression: - """将 JSON 值转换为指定类型后再比较。""" - return _CastJsonExpression(self.field_name, self.key, type_hint) - - def __eq__(self, other) -> Q: # type: ignore[override] - return Q(**{self._lookup(): other}) - - def __ne__(self, other) -> Q: # type: ignore[override] - return ~Q(**{self._lookup(): other}) - - def __gt__(self, other) -> Q: - return Q(**{self._lookup('gt'): other}) - - def __ge__(self, other) -> Q: - return Q(**{self._lookup('gte'): other}) - - def __lt__(self, other) -> Q: - return Q(**{self._lookup('lt'): other}) - - def __le__(self, other) -> Q: - return Q(**{self._lookup('lte'): other}) - - def between(self, low, high) -> Q: - return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high}) - - def __repr__(self) -> str: - return f'JsonKeyExpr({self.field_name}[{self.key!r}])' - - -class _CastJsonExpression: - """类型转换后的 JSON 键表达式。""" - - def __init__(self, field_name: str, key: str, type_hint) -> None: - self.field_name = field_name - self.key = key - self.type_hint = type_hint - - def _lookup(self, suffix: str = '') -> str: - if suffix: - return f'{self.field_name}__{self.key}__{suffix}' - return f'{self.field_name}__{self.key}' - - def __eq__(self, other) -> Q: # type: ignore[override] - return Q(**{self._lookup(): other}) - - def __ne__(self, other) -> Q: # type: ignore[override] - return ~Q(**{self._lookup(): other}) - - def __gt__(self, other) -> Q: - return Q(**{self._lookup('gt'): other}) - - def __ge__(self, other) -> Q: - return Q(**{self._lookup('gte'): other}) - - def __lt__(self, other) -> Q: - return Q(**{self._lookup('lt'): other}) - - def __le__(self, other) -> Q: - return Q(**{self._lookup('lte'): other}) - - def between(self, low, high) -> Q: - return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high}) - - def __repr__(self) -> str: - type_name = getattr(self.type_hint, '__name__', str(self.type_hint)) - return f'CastJsonExpr({self.field_name}[{self.key!r}]->{type_name})' - - -# --------------------------------------------------------------------------- -# QModel 元类与基类 -# --------------------------------------------------------------------------- - -class QModelBase(models.base.ModelBase): - """QModel 元类,负责: - - 1. 根据 ``GVSDSDK_MANAGE_TABLES`` 设置动态控制 ``managed`` - 2. 包装字段描述符,使类级别访问返回 FieldExpression - """ - - def __new__(mcs, name, bases, namespace, **kwargs): - # 动态设置 managed:子服务器可设 GVSDSDK_MANAGE_TABLES=True 让 gvsdsdk 管理表 - meta = namespace.get('Meta') - if meta and getattr(meta, 'app_label', None) == 'gvsdsdk': - try: - from django.conf import settings - if getattr(settings, 'GVSDSDK_MANAGE_TABLES', False): - meta.managed = True - except Exception: - pass - - cls = super().__new__(mcs, name, bases, namespace, **kwargs) - - # 模型创建后,包装字段描述符以支持类级别 FieldExpression - if not cls._meta.abstract: - _wrap_field_descriptors(cls) - - return cls - - -def _wrap_field_descriptors(cls: Type) -> None: - """包装 Django 字段描述符,使类级别访问返回 FieldExpression, - 实例级别完全委托给 Django 原生描述符。 - - Django 的字段描述符(DeferredAttribute / ForwardManyToOneDescriptor): - - ``__get__(None, cls)`` → 返回字段对象本身(类级别) - - ``__get__(instance, cls)`` → 返回字段值(实例级别) - - 我们包装后: - - ``__get__(None, cls)`` → 返回 FieldExpression(类级别,用于链式 OOP) - - ``__get__(instance, cls)`` → 委托给原生描述符(实例级别,正常行为) - """ - from django.db.models.query_utils import DeferredAttribute - from django.db.models.fields.related_descriptors import ( - ForwardManyToOneDescriptor, - ForwardOneToOneDescriptor, - ) - - for field in cls._meta.local_fields: - fname = field.name - if fname.startswith('_'): - continue - - original_desc = cls.__dict__.get(fname) - if original_desc is None: - continue - - # 只包装已知的 Django 描述符类型 - if isinstance(original_desc, (DeferredAttribute, ForwardManyToOneDescriptor, - ForwardOneToOneDescriptor)): - wrapper = _FieldExpressionWrapper(original_desc, cls, fname) - setattr(cls, fname, wrapper) - - -class _FieldExpressionWrapper: - """包装 Django 字段描述符,类级别返回 FieldExpression,实例级别委托给原生描述符。 - - 这是 QModel 字段表达式机制的核心:当你在类上访问 ``Model.field_name`` 时, - 返回 ``FieldExpression`` 对象,支持 ``Model.field == value`` 等运算符构建 Q 对象; - 当你在实例上访问 ``obj.field_name`` 时,行为与原生 Django 完全一致。 - """ - - def __init__(self, original_descriptor, model_class: Type, field_name: str) -> None: - self._original = original_descriptor - self._model = model_class - self._field_name = field_name - self._has_set: bool = hasattr(original_descriptor, '__set__') - - def __get__(self, obj, objtype=None): - if obj is None: - # 类级别访问:返回 FieldExpression - return FieldExpression(self._model, self._field_name) - # 实例级别访问:委托给原生 Django 描述符 - return self._original.__get__(obj, objtype) - - def __set__(self, obj, value) -> None: - if self._has_set: - # FK/O2O 描述符有 __set__ - self._original.__set__(obj, value) - else: - # 普通字段(DeferredAttribute)没有 __set__,直接写 __dict__ - obj.__dict__[self._field_name] = value - - def __repr__(self) -> str: - return f'_FieldExpressionWrapper({self._model.__name__}.{self._field_name})' - - -class QModel(models.Model, metaclass=QModelBase): - """QModel 兼容层基类,提供 SQLAlchemy 风格的链式 OOP 查询接口。 - - 所有继承 QModel 的模型自动获得: - - - ``Model.query`` : 返回 FluentQuery,支持链式查询 - - ``Model.field_name`` : 类级别访问返回 FieldExpression,支持表达式查询 - - ``Model.field_name`` : 实例级别访问返回字段值(与普通 Django Model 一致) - - ``Model[condition]`` : 快捷 filter 语法 - - 用法示例:: - - # 链式查询 - orders = Dingdan.query.filter(zhuangtai=8).order_by('-create_time').to_list() - - # 字段表达式 - Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3])) - - # 聚合 - Dingdan.query.aggregate(total=Sum('jine')) - - # values / annotate - Dingdan.query.values('zhuangtai').annotate(cnt=Count('id')) - - # 分页 - Dingdan.query.paginate(page=1, per_page=20) - - # 快捷 filter - Dingdan[Dingdan.zhuangtai == 8].order_by('-create_time').to_list() - """ - - class Meta: - abstract = True - - @classproperty - def query(cls) -> FluentQuery[Self]: - """返回 FluentQuery 兼容层查询构建器,支持链式 OOP 查询。""" - return FluentQuery(cls) - - @classmethod - def __class_getitem__(cls, item) -> FluentQuery[Self]: - """支持 ``Model[condition]`` 语法,返回 filter 后的 FluentQuery。 - - 用法:: - - Dingdan[Dingdan.zhuangtai == 8].order_by('-create_time').to_list() - Dingdan[{'zhuangtai': 8}].to_list() - """ - if isinstance(item, Q): - return cls.query.filter(item) - if isinstance(item, dict): - return cls.query.filter(**item) - return cls.query.filter(item) +"""QModel 兼容层基类模块 + +提供 SQLAlchemy 风格的链式 OOP 查询接口,使 Django ORM 支持: + +- ``Model.query.filter(...)`` — 链式 FluentQuery 查询 +- ``Model.field_name`` — 类级别返回 FieldExpression(支持 ``==``, ``>``, ``in_()`` 等运算符) +- ``Model.field_name`` — 实例级别返回字段值(与原生 Django Model 一致) +- ``Model[condition]`` — 快捷 filter 语法 + +典型用法:: + + # 链式查询 + orders = Dingdan.query.filter(zhuangtai=8).order_by('-CreateTime').to_list() + + # 字段表达式 + Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1, 2, 3])) + + # 聚合 + Dingdan.query.aggregate(total=Sum('jine')) + + # 分页 + Dingdan.query.paginate(page=1, per_page=20) +""" +from __future__ import annotations + +import re +from typing import Any, Callable, Dict, Generic, Optional, Tuple, Type, TypeVar, Union + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +from django.db import models +from django.db.models import Q + +from .fluent import FluentQuery + + +# --------------------------------------------------------------------------- +# 工具函数 +# --------------------------------------------------------------------------- + +#: classproperty 返回值类型变量 +_RT = TypeVar('_RT') + +#: QModel 子类类型变量(仅用于 __class_getitem__ 兼容) +_QM = TypeVar('_QM', bound='QModel') + + +class classproperty(Generic[_RT]): + """类属性描述符,支持 ``Model.query`` 等类级别属性访问。 + + 泛型参数 ``_RT`` 传播函数返回类型,使 Pylance 能穿透描述符推断 + ``Model.query`` 的返回类型为 ``FluentQuery[Self]``。 + """ + + def __init__(self, func: Callable[[type], _RT]) -> None: + self.fget = func + + def __get__(self, obj: Any, objtype: Optional[type] = None) -> _RT: + return self.fget(objtype if objtype is not None else type(obj)) + + +ABBREVIATIONS: Dict[str, str] = { + 'uuid': 'UUID', 'id': 'ID', 'ip': 'IP', 'url': 'URL', 'uri': 'URI', + 'api': 'API', 'html': 'HTML', 'css': 'CSS', 'js': 'JS', 'json': 'JSON', + 'xml': 'XML', 'sql': 'SQL', 'http': 'HTTP', 'https': 'HTTPS', + 'smtp': 'SMTP', 'ssl': 'SSL', 'tls': 'TLS', 'ssh': 'SSH', 'ftp': 'FTP', + '2fa': '2FA', 'elt': 'ELT', 'sve': 'SVE', 't4': 'T4', 'mc': 'MC', + 'crb': 'CRB', 'ccgo': 'CCGO', 'ugm': 'UGM', 'gerp': 'GERP', + 'abac': 'ABAC', 'rbac': 'RBAC', 'smtm': 'SMTM', 'pwd': 'PWD', +} + + +def snake_to_pascal(name: str) -> str: + """将 snake_case 转换为 PascalCase,自动识别常见缩写。 + + >>> snake_to_pascal('user_uuid') + 'UserUUID' + >>> snake_to_pascal('paifa_dingdan_shu') + 'PaifaDingdanShu' + """ + if '_' not in name: + return name[0].upper() + name[1:] if name else name + parts = name.split('_') + result = [] + for p in parts: + lower = p.lower() + if lower in ABBREVIATIONS: + result.append(ABBREVIATIONS[lower]) + else: + result.append(p.capitalize()) + return ''.join(result) + + +def pascal_to_snake(name: str) -> str: + """将 PascalCase 转换为 snake_case。 + + >>> pascal_to_snake('UserUUID') + 'user_uuid' + """ + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) + s2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1) + return s2.lower() + + +# --------------------------------------------------------------------------- +# 字段表达式(类级别访问 Model.field 时返回) +# --------------------------------------------------------------------------- + +class _InvertedFieldExpression: + """取反字段表达式,由 ``~Model.field`` 产生,支持 ``~field.startswith(...)`` 等否定查询。""" + + def __init__(self, field_expr: FieldExpression) -> None: + self._field_expr = field_expr + + def startswith(self, value: str) -> Q: + return ~Q(**{f'{self._field_expr._django_name}__startswith': value}) + + def contains(self, value: str) -> Q: + return ~Q(**{f'{self._field_expr._django_name}__contains': value}) + + def like(self, pattern: str) -> Q: + clean = pattern.strip('%') + return ~Q(**{f'{self._field_expr._django_name}__icontains': clean}) + + def in_(self, values) -> Q: + return ~Q(**{f'{self._field_expr._django_name}__in': values}) + + def is_(self, other) -> Q: + if other is None: + return ~Q(**{f'{self._field_expr._django_name}__isnull': True}) + return ~Q(**{self._field_expr._django_name: other}) + + def between(self, low, high) -> Q: + return ~(Q(**{f'{self._field_expr._django_name}__gte': low}) + & Q(**{f'{self._field_expr._django_name}__lte': high})) + + def __repr__(self) -> str: + return f'~{self._field_expr!r}' + + +class FieldExpression: + """字段表达式,由类级别 ``Model.field_name`` 访问产生。 + + 支持通过 Python 运算符构建 Django Q 对象:: + + Model.field == value → Q(field=value) + Model.field > value → Q(field__gt=value) + Model.field.in_([1,2,3]) → Q(field__in=[1,2,3]) + Model.field.between(a, b) → Q(field__gte=a) & Q(field__lte=b) + ~Model.field → _InvertedFieldExpression(否定查询) + """ + + _resolve_cache: Dict[Tuple[str, str], str] = {} + + def __init__(self, model_class: Type, field_name: str) -> None: + self.model_class = model_class + self.field_name = field_name + self._django_name: str = self._resolve_django_name() + + def _resolve_django_name(self) -> str: + """将 Python 字段名解析为 Django ORM 查询使用的字段名。""" + cache_key = (self.model_class.__name__, self.field_name) + if cache_key in FieldExpression._resolve_cache: + return FieldExpression._resolve_cache[cache_key] + if hasattr(self.model_class, '_meta'): + for f in self.model_class._meta.local_fields: + if f.name == self.field_name: + FieldExpression._resolve_cache[cache_key] = f.name + return f.name + FieldExpression._resolve_cache[cache_key] = self.field_name + return self.field_name + + # ---- 比较运算符 ---- + + def __eq__(self, other) -> Q: # type: ignore[override] + if isinstance(other, FieldExpression): + return NotImplemented + if other is None: + return Q(**{f'{self._django_name}__isnull': True}) + return Q(**{self._django_name: other}) + + def __ne__(self, other) -> Q: # type: ignore[override] + if isinstance(other, FieldExpression): + return NotImplemented + return ~Q(**{self._django_name: other}) + + def __gt__(self, other) -> Q: + return Q(**{f'{self._django_name}__gt': other}) + + def __ge__(self, other) -> Q: + return Q(**{f'{self._django_name}__gte': other}) + + def __lt__(self, other) -> Q: + return Q(**{f'{self._django_name}__lt': other}) + + def __le__(self, other) -> Q: + return Q(**{f'{self._django_name}__lte': other}) + + def __hash__(self) -> int: + return hash((self.model_class.__name__, self.field_name)) + + # ---- 逻辑运算符 ---- + + def __and__(self, other) -> Q: + if isinstance(other, Q): + return Q(self) & other + if isinstance(other, FieldExpression): + return Q(self) & Q(other) + return NotImplemented + + def __or__(self, other) -> Q: + if isinstance(other, Q): + return Q(self) | other + if isinstance(other, FieldExpression): + return Q(self) | Q(other) + return NotImplemented + + def __invert__(self) -> _InvertedFieldExpression: + return _InvertedFieldExpression(self) + + # ---- 查询方法 ---- + + def is_(self, other) -> Q: + """``field IS NULL`` 或 ``field IS value``。""" + if other is None: + return Q(**{f'{self._django_name}__isnull': True}) + return Q(**{self._django_name: other}) + + def is_not(self, other) -> Q: + """``field IS NOT NULL`` 或 ``field IS NOT value``。""" + if other is None: + return Q(**{f'{self._django_name}__isnull': False}) + return ~Q(**{self._django_name: other}) + + def like(self, pattern: str) -> Q: + """模糊匹配(不区分大小写),自动去除 ``%`` 通配符。""" + clean = pattern.strip('%') + return Q(**{f'{self._django_name}__icontains': clean}) + + def contains(self, value: str) -> Q: + """包含子串(区分大小写)。""" + return Q(**{f'{self._django_name}__contains': value}) + + def ilike(self, pattern: str) -> Q: + """like() 的别名。""" + return self.like(pattern) + + def startswith(self, value: str) -> Q: + """前缀匹配(区分大小写)。""" + return Q(**{f'{self._django_name}__startswith': value}) + + def istartswith(self, value: str) -> Q: + """前缀匹配(不区分大小写)。""" + return Q(**{f'{self._django_name}__istartswith': value}) + + def endswith(self, value: str) -> Q: + """后缀匹配(区分大小写)。""" + return Q(**{f'{self._django_name}__endswith': value}) + + def iendswith(self, value: str) -> Q: + """后缀匹配(不区分大小写)。""" + return Q(**{f'{self._django_name}__iendswith': value}) + + def in_(self, values) -> Q: + """``field IN (values)``,支持列表和 FluentQuery 子查询。""" + if hasattr(values, '_qs'): + return Q(**{f'{self._django_name}__in': values._qs.values(self._django_name)}) + return Q(**{f'{self._django_name}__in': values}) + + def between(self, low, high) -> Q: + """``field BETWEEN low AND high``。""" + return Q(**{f'{self._django_name}__gte': low}) & Q(**{f'{self._django_name}__lte': high}) + + def regex(self, pattern: str) -> Q: + """正则匹配(区分大小写)。""" + return Q(**{f'{self._django_name}__regex': pattern}) + + def iregex(self, pattern: str) -> Q: + """正则匹配(不区分大小写)。""" + return Q(**{f'{self._django_name}__iregex': pattern}) + + # ---- 排序辅助 ---- + + def desc(self) -> str: + """返回降序排序字符串 ``'-field_name'``。""" + return f'-{self._django_name}' + + def asc(self) -> str: + """返回升序排序字符串 ``'field_name'``。""" + return self._django_name + + # ---- JSON 字段访问 ---- + + def __getitem__(self, key: str) -> _JsonKeyExpression: + """访问 JSON 字段内的键,如 ``Model.meta['key'] == value``。""" + return _JsonKeyExpression(self._django_name, key) + + # ---- 显示 ---- + + def __repr__(self) -> str: + return f'FieldExpr({self.model_class.__name__}.{self.field_name})' + + def __str__(self) -> str: + return self._django_name + + +class _JsonKeyExpression: + """JSON 字段键表达式,由 ``Model.json_field['key']`` 产生。""" + + def __init__(self, field_name: str, key: str) -> None: + self.field_name = field_name + self.key = key + + def _lookup(self, suffix: str = '') -> str: + if suffix: + return f'{self.field_name}__{self.key}__{suffix}' + return f'{self.field_name}__{self.key}' + + def cast(self, type_hint) -> _CastJsonExpression: + """将 JSON 值转换为指定类型后再比较。""" + return _CastJsonExpression(self.field_name, self.key, type_hint) + + def __eq__(self, other) -> Q: # type: ignore[override] + return Q(**{self._lookup(): other}) + + def __ne__(self, other) -> Q: # type: ignore[override] + return ~Q(**{self._lookup(): other}) + + def __gt__(self, other) -> Q: + return Q(**{self._lookup('gt'): other}) + + def __ge__(self, other) -> Q: + return Q(**{self._lookup('gte'): other}) + + def __lt__(self, other) -> Q: + return Q(**{self._lookup('lt'): other}) + + def __le__(self, other) -> Q: + return Q(**{self._lookup('lte'): other}) + + def between(self, low, high) -> Q: + return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high}) + + def __repr__(self) -> str: + return f'JsonKeyExpr({self.field_name}[{self.key!r}])' + + +class _CastJsonExpression: + """类型转换后的 JSON 键表达式。""" + + def __init__(self, field_name: str, key: str, type_hint) -> None: + self.field_name = field_name + self.key = key + self.type_hint = type_hint + + def _lookup(self, suffix: str = '') -> str: + if suffix: + return f'{self.field_name}__{self.key}__{suffix}' + return f'{self.field_name}__{self.key}' + + def __eq__(self, other) -> Q: # type: ignore[override] + return Q(**{self._lookup(): other}) + + def __ne__(self, other) -> Q: # type: ignore[override] + return ~Q(**{self._lookup(): other}) + + def __gt__(self, other) -> Q: + return Q(**{self._lookup('gt'): other}) + + def __ge__(self, other) -> Q: + return Q(**{self._lookup('gte'): other}) + + def __lt__(self, other) -> Q: + return Q(**{self._lookup('lt'): other}) + + def __le__(self, other) -> Q: + return Q(**{self._lookup('lte'): other}) + + def between(self, low, high) -> Q: + return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high}) + + def __repr__(self) -> str: + type_name = getattr(self.type_hint, '__name__', str(self.type_hint)) + return f'CastJsonExpr({self.field_name}[{self.key!r}]->{type_name})' + + +# --------------------------------------------------------------------------- +# QModel 元类与基类 +# --------------------------------------------------------------------------- + +class QModelBase(models.base.ModelBase): + """QModel 元类,负责: + + 1. 根据 ``GVSDSDK_MANAGE_TABLES`` 设置动态控制 ``managed`` + 2. 包装字段描述符,使类级别访问返回 FieldExpression + """ + + def __new__(mcs, name, bases, namespace, **kwargs): + # 动态设置 managed:子服务器可设 GVSDSDK_MANAGE_TABLES=True 让 gvsdsdk 管理表 + meta = namespace.get('Meta') + if meta and getattr(meta, 'app_label', None) == 'gvsdsdk': + try: + from django.conf import settings + if getattr(settings, 'GVSDSDK_MANAGE_TABLES', False): + meta.managed = True + except Exception: + pass + + cls = super().__new__(mcs, name, bases, namespace, **kwargs) + + # 模型创建后,包装字段描述符以支持类级别 FieldExpression + if not cls._meta.abstract: + _wrap_field_descriptors(cls) + + return cls + + +def _wrap_field_descriptors(cls: Type) -> None: + """包装 Django 字段描述符,使类级别访问返回 FieldExpression, + 实例级别完全委托给 Django 原生描述符。 + + Django 的字段描述符(DeferredAttribute / ForwardManyToOneDescriptor): + - ``__get__(None, cls)`` → 返回字段对象本身(类级别) + - ``__get__(instance, cls)`` → 返回字段值(实例级别) + + 我们包装后: + - ``__get__(None, cls)`` → 返回 FieldExpression(类级别,用于链式 OOP) + - ``__get__(instance, cls)`` → 委托给原生描述符(实例级别,正常行为) + """ + from django.db.models.query_utils import DeferredAttribute + from django.db.models.fields.related_descriptors import ( + ForwardManyToOneDescriptor, + ForwardOneToOneDescriptor, + ) + + for field in cls._meta.local_fields: + fname = field.name + if fname.startswith('_'): + continue + + original_desc = cls.__dict__.get(fname) + if original_desc is None: + continue + + # 只包装已知的 Django 描述符类型 + if isinstance(original_desc, (DeferredAttribute, ForwardManyToOneDescriptor, + ForwardOneToOneDescriptor)): + wrapper = _FieldExpressionWrapper(original_desc, cls, fname) + setattr(cls, fname, wrapper) + + +class _FieldExpressionWrapper: + """包装 Django 字段描述符,类级别返回 FieldExpression,实例级别委托给原生描述符。 + + 这是 QModel 字段表达式机制的核心:当你在类上访问 ``Model.field_name`` 时, + 返回 ``FieldExpression`` 对象,支持 ``Model.field == value`` 等运算符构建 Q 对象; + 当你在实例上访问 ``obj.field_name`` 时,行为与原生 Django 完全一致。 + """ + + def __init__(self, original_descriptor, model_class: Type, field_name: str) -> None: + self._original = original_descriptor + self._model = model_class + self._field_name = field_name + self._has_set: bool = hasattr(original_descriptor, '__set__') + + def __get__(self, obj, objtype=None): + if obj is None: + # 类级别访问:返回 FieldExpression + return FieldExpression(self._model, self._field_name) + # 实例级别访问:委托给原生 Django 描述符 + return self._original.__get__(obj, objtype) + + def __set__(self, obj, value) -> None: + if self._has_set: + # FK/O2O 描述符有 __set__ + self._original.__set__(obj, value) + else: + # 普通字段(DeferredAttribute)没有 __set__,直接写 __dict__ + obj.__dict__[self._field_name] = value + + def __repr__(self) -> str: + return f'_FieldExpressionWrapper({self._model.__name__}.{self._field_name})' + + +class QModel(models.Model, metaclass=QModelBase): + """QModel 兼容层基类,提供 SQLAlchemy 风格的链式 OOP 查询接口。 + + 所有继承 QModel 的模型自动获得: + + - ``Model.query`` : 返回 FluentQuery,支持链式查询 + - ``Model.field_name`` : 类级别访问返回 FieldExpression,支持表达式查询 + - ``Model.field_name`` : 实例级别访问返回字段值(与普通 Django Model 一致) + - ``Model[condition]`` : 快捷 filter 语法 + + 用法示例:: + + # 链式查询 + orders = Dingdan.query.filter(zhuangtai=8).order_by('-CreateTime').to_list() + + # 字段表达式 + Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3])) + + # 聚合 + Dingdan.query.aggregate(total=Sum('jine')) + + # values / annotate + Dingdan.query.values('zhuangtai').annotate(cnt=Count('id')) + + # 分页 + Dingdan.query.paginate(page=1, per_page=20) + + # 快捷 filter + Dingdan[Dingdan.zhuangtai == 8].order_by('-CreateTime').to_list() + """ + + class Meta: + abstract = True + + @classproperty + def query(cls) -> FluentQuery[Self]: + """返回 FluentQuery 兼容层查询构建器,支持链式 OOP 查询。""" + return FluentQuery(cls) + + @classmethod + def __class_getitem__(cls, item) -> FluentQuery[Self]: + """支持 ``Model[condition]`` 语法,返回 filter 后的 FluentQuery。 + + 用法:: + + Dingdan[Dingdan.zhuangtai == 8].order_by('-CreateTime').to_list() + Dingdan[{'zhuangtai': 8}].to_list() + """ + if isinstance(item, Q): + return cls.query.filter(item) + if isinstance(item, dict): + return cls.query.filter(**item) + return cls.query.filter(item) diff --git a/migrate_to_gvsdsdk_user.py b/migrate_to_gvsdsdk_user.py index 8b1db82..0699c79 100644 --- a/migrate_to_gvsdsdk_user.py +++ b/migrate_to_gvsdsdk_user.py @@ -1,260 +1,260 @@ -#!/usr/bin/env python3 -""" -migrate_to_gvsdsdk_user.py — 将 UserMain 用户体系迁移到 gvsdsdk.User + RBAC - -使用方式: - cd /opt/.../django - python manage.py shell < migrate_to_gvsdsdk_user.py - - 或: - python manage.py shell - >>> exec(open('migrate_to_gvsdsdk_user.py').read()) -""" - -import uuid -import datetime -from django.db import transaction -from django.conf import settings - -# === 配置 === -TENANT_UUID = uuid.uuid4().bytes # 本租户 UUID(所有角色共用) -COMPANY_UUID = uuid.uuid4().bytes # 默认公司 UUID - -# 角色映射:user_type → (RoleName, RoleDesc) -ROLE_MAP = { - 'normal': ('消费者', '普通消费者/老板'), - 'dashou': ('打手', '接单打手'), - 'shop': ('商家', '发布订单的商家'), - 'guanshi': ('负责人', '管事/负责人'), - 'admin': ('管理员', '系统管理员'), - 'kefu': ('客服', '客服人员'), - 'zuzhang': ('组长', '组长'), - 'shenheguan': ('审核官', '审核官'), -} - -# 权限映射:每个角色拥有的权限码 -PERMISSION_MAP = { - '消费者': ['view_orders', 'create_order', 'view_own_profile'], - '打手': ['view_orders', 'accept_order', 'view_own_profile', 'withdraw'], - '商家': ['view_orders', 'create_order', 'view_own_profile', 'withdraw', 'manage_own_orders'], - '负责人': ['view_orders', 'view_own_profile', 'withdraw', 'invite_dashou', 'view_dashou_stats'], - '管理员': ['admin_access', 'manage_users', 'view_all_orders', 'manage_orders', 'view_stats', 'manage_fines'], - '客服': ['kefu_access', 'view_orders', 'manage_orders', 'apply_fine', 'view_stats'], - '组长': ['view_orders', 'view_own_profile', 'withdraw', 'invite_dashou'], - '审核官': ['view_orders', 'review_orders', 'view_own_profile', 'withdraw'], -} - - -def run_migration(): - from gvsdsdk.models import Role, Permission, UserRole, RolePermission - from users.business_models import User - from users.models import ( - UserMain, UserBoss, UserDashou, UserShangjia, UserGuanshi, - UserZuzhang, UserShenheguan, AdminProfile, UserKefu, - UserSensitiveData - ) - - print("=" * 60) - print("开始迁移 UserMain → gvsdsdk.User + RBAC") - print("=" * 60) - - # ========== 第1步:创建租户下的角色和权限 ========== - print("\n[1/6] 创建角色和权限...") - - created_roles = {} - created_perms = {} - - for user_type, (role_name, role_desc) in ROLE_MAP.items(): - # 创建角色 - role, created = Role.objects.get_or_create( - RoleName=role_name, - TenantUUID=TENANT_UUID, - defaults={ - 'RoleUUID': uuid.uuid4().bytes, - 'RoleDesc': role_desc, - 'RoleStatus': 1, - 'AssignScope': 0, - } - ) - if created: - print(f" 创建角色: {role_name}") - created_roles[user_type] = role - - # 创建该角色的权限 - perm_codes = PERMISSION_MAP.get(role_name, []) - for perm_code in perm_codes: - perm, perm_created = Permission.objects.get_or_create( - PermCode=perm_code, - TenantUUID=TENANT_UUID, - defaults={ - 'PermUUID': uuid.uuid4().bytes, - 'PermName': perm_code, - 'PermCategory': role_name, - 'PermStatus': 1, - } - ) - if perm_created: - print(f" 创建权限: {perm_code}") - created_perms[perm_code] = perm - - # 关联角色-权限 - RolePermission.objects.get_or_create( - RoleUUID=role.RoleUUID, - PermUUID=perm.PermUUID, - defaults={ - 'RolePermUUID': uuid.uuid4().bytes, - } - ) - - # ========== 第2步:迁移用户 ========== - print("\n[2/6] 迁移用户数据...") - - user_main_qs = UserMain.objects.all() - total = user_main_qs.count() - print(f" 共 {total} 个用户待迁移") - - # 构建 yonghuid → UserUUID 映射 - uid_map = {} # yonghuid → UserUUID (bytes) - - for i, um in enumerate(user_main_qs): - if (i + 1) % 100 == 0: - print(f" 进度: {i+1}/{total}") - - user_uuid = uuid.uuid4().bytes - - # 创建 gvsdsdk User - user = User.objects.create( - UserUUID=user_uuid, - UserName=um.yonghuid, # 用 yonghuid 作为用户名 - UserUID=um.yonghuid, - OpenID=um.openid or '', - UnionID=um.unionid or '', - Avatar=um.avatar or 'a_long/morentouxiang.jpg', - Phone=um.phone or '', - PhoneVerified=um.shoujihao_renzheng or False, - IP=um.ip or '', - UserAccountLicense=1, - UserPositionStatus=0, - UserCreateTime=um.create_time or datetime.datetime.utcnow(), - UserLastLoginDate=um.last_login_time or datetime.datetime.utcnow(), - IsStaff=(um.user_type == 'admin'), - IsSuperuser=(um.user_type == 'admin'), - ) - uid_map[um.yonghuid] = user_uuid - - # 创建 UserRole - user_type = um.user_type or 'normal' - role = created_roles.get(user_type) - if role: - UserRole.objects.create( - UserRoleUUID=uuid.uuid4().bytes, - UserUUID=user_uuid, - RoleUUID=role.RoleUUID, - CompanyUUID=COMPANY_UUID, - ) - - # 创建 UserSensitiveData - UserSensitiveData.objects.create( - user_uuid_id=user_uuid, - password=um.password or '', - zhifu=um.zhifu or '', - skzhanghao=um.skzhanghao or '', - shoujihao_renzheng=um.shoujihao_renzheng or False, - ) - - print(f" 用户迁移完成: {len(uid_map)} 个") - - # ========== 第3步:迁移扩展表 FK ========== - print("\n[3/6] 迁移扩展表 FK...") - - ext_tables = [ - ('UserBoss', UserBoss, 'boss_profile'), - ('UserDashou', UserDashou, 'dashou_profile'), - ('UserShangjia', UserShangjia, 'shop_profile'), - ('UserGuanshi', UserGuanshi, 'guanshi_profile'), - ('UserZuzhang', UserZuzhang, 'zuzhang_profile'), - ('UserShenheguan', UserShenheguan, 'shenheguan_profile'), - ('AdminProfile', AdminProfile, 'admin_profile'), - ('UserKefu', UserKefu, 'kefu_profile'), - ] - - for table_name, model, _ in ext_tables: - count = 0 - for obj in model.objects.all(): - old_user_id = obj.user_id # 旧 FK (UserMain.id) - # 通过旧 UserMain 查找 yonghuid → 新 UserUUID - try: - old_user = UserMain.objects.get(id=old_user_id) - new_uuid = uid_map.get(old_user.yonghuid) - if new_uuid: - obj.user_id = new_uuid # 更新 FK 为新 UserUUID - obj.save(update_fields=['user_id']) - count += 1 - except UserMain.DoesNotExist: - pass - print(f" {table_name}: 迁移 {count} 条") - - # ========== 第4步:迁移其他引用 yonghuid 的表 ========== - print("\n[4/6] 迁移其他表的 yonghuid 引用...") - # Tixianjilu, TixianShenheJilu, TixianAutoRecord, Xiugaijilu, RankingRecord - # 这些表使用 yonghuid 字符串字段,不需要修改(UserUID 保留相同值) - print(" 这些表使用 yonghuid 字符串字段,值不变,无需迁移") - - # ========== 第5步:迁移 backend.UserRole ========== - print("\n[5/6] 迁移 backend.UserRole...") - from backend.models import UserRole as BackendUserRole - count = 0 - for bur in BackendUserRole.objects.all(): - # account_id 是手机号,查找对应的 UserMain → 新 UserUUID - try: - um = UserMain.objects.get(phone=bur.account_id) - new_uuid = uid_map.get(um.yonghuid) - if new_uuid: - # 查找对应的新角色 - old_role = bur.role - # 尝试匹配角色名 - role_name_map = { - '客服': 'kefu', '打手': 'dashou', '商家': 'shop', - '管事': 'guanshi', '管理员': 'admin', '组长': 'zuzhang', - '审核官': 'shenheguan', '消费者': 'normal', - } - new_role = None - for ut, r in created_roles.items(): - if old_role.role_name in r.RoleName or old_role.role_code in ut: - new_role = r - break - if new_role: - UserRole.objects.get_or_create( - UserUUID=new_uuid, - RoleUUID=new_role.RoleUUID, - CompanyUUID=COMPANY_UUID, - defaults={'UserRoleUUID': uuid.uuid4().bytes} - ) - count += 1 - except UserMain.DoesNotExist: - pass - print(f" 迁移 {count} 条 backend.UserRole") - - # ========== 第6步:验证 ========== - print("\n[6/6] 验证迁移结果...") - new_user_count = User.objects.count() - new_role_count = Role.objects.filter(TenantUUID=TENANT_UUID).count() - new_perm_count = Permission.objects.filter(TenantUUID=TENANT_UUID).count() - new_ur_count = UserRole.objects.count() - - print(f" gvsdsdk.User: {new_user_count}") - print(f" Role: {new_role_count}") - print(f" Permission: {new_perm_count}") - print(f" UserRole: {new_ur_count}") - print(f" UserSensitiveData: {UserSensitiveData.objects.count()}") - - print("\n" + "=" * 60) - print("迁移完成!") - print(f"TENANT_UUID = {uuid.UUID(bytes=TENANT_UUID)}") - print(f"COMPANY_UUID = {uuid.UUID(bytes=COMPANY_UUID)}") - print("请将这两个 UUID 保存到 settings.py 中") - print("=" * 60) - - -if __name__ == '__main__': - run_migration() +#!/usr/bin/env python3 +""" +migrate_to_gvsdsdk_user.py — 将 UserMain 用户体系迁移到 gvsdsdk.User + RBAC + +使用方式: + cd /opt/.../django + python manage.py shell < migrate_to_gvsdsdk_user.py + + 或: + python manage.py shell + >>> exec(open('migrate_to_gvsdsdk_user.py').read()) +""" + +import uuid +import datetime +from django.db import transaction +from django.conf import settings + +# === 配置 === +TENANT_UUID = uuid.uuid4().bytes # 本租户 UUID(所有角色共用) +COMPANY_UUID = uuid.uuid4().bytes # 默认公司 UUID + +# 角色映射:user_type → (RoleName, RoleDesc) +ROLE_MAP = { + 'normal': ('消费者', '普通消费者/老板'), + 'dashou': ('打手', '接单打手'), + 'shop': ('商家', '发布订单的商家'), + 'guanshi': ('负责人', '管事/负责人'), + 'admin': ('管理员', '系统管理员'), + 'kefu': ('客服', '客服人员'), + 'zuzhang': ('组长', '组长'), + 'shenheguan': ('审核官', '审核官'), +} + +# 权限映射:每个角色拥有的权限码 +PERMISSION_MAP = { + '消费者': ['view_orders', 'create_order', 'view_own_profile'], + '打手': ['view_orders', 'accept_order', 'view_own_profile', 'withdraw'], + '商家': ['view_orders', 'create_order', 'view_own_profile', 'withdraw', 'manage_own_orders'], + '负责人': ['view_orders', 'view_own_profile', 'withdraw', 'invite_dashou', 'view_dashou_stats'], + '管理员': ['admin_access', 'manage_users', 'view_all_orders', 'manage_orders', 'view_stats', 'manage_fines'], + '客服': ['kefu_access', 'view_orders', 'manage_orders', 'apply_fine', 'view_stats'], + '组长': ['view_orders', 'view_own_profile', 'withdraw', 'invite_dashou'], + '审核官': ['view_orders', 'review_orders', 'view_own_profile', 'withdraw'], +} + + +def run_migration(): + from gvsdsdk.models import Role, Permission, UserRole, RolePermission + from users.business_models import User + from users.models import ( + UserMain, UserBoss, UserDashou, UserShangjia, UserGuanshi, + UserZuzhang, UserShenheguan, AdminProfile, UserKefu, + UserSensitiveData + ) + + print("=" * 60) + print("开始迁移 UserMain → gvsdsdk.User + RBAC") + print("=" * 60) + + # ========== 第1步:创建租户下的角色和权限 ========== + print("\n[1/6] 创建角色和权限...") + + created_roles = {} + created_perms = {} + + for user_type, (role_name, role_desc) in ROLE_MAP.items(): + # 创建角色 + role, created = Role.objects.get_or_create( + RoleName=role_name, + TenantUUID=TENANT_UUID, + defaults={ + 'RoleUUID': uuid.uuid4().bytes, + 'RoleDesc': role_desc, + 'RoleStatus': 1, + 'AssignScope': 0, + } + ) + if created: + print(f" 创建角色: {role_name}") + created_roles[user_type] = role + + # 创建该角色的权限 + perm_codes = PERMISSION_MAP.get(role_name, []) + for perm_code in perm_codes: + perm, perm_created = Permission.objects.get_or_create( + PermCode=perm_code, + TenantUUID=TENANT_UUID, + defaults={ + 'PermUUID': uuid.uuid4().bytes, + 'PermName': perm_code, + 'PermCategory': role_name, + 'PermStatus': 1, + } + ) + if perm_created: + print(f" 创建权限: {perm_code}") + created_perms[perm_code] = perm + + # 关联角色-权限 + RolePermission.objects.get_or_create( + RoleUUID=role.RoleUUID, + PermUUID=perm.PermUUID, + defaults={ + 'RolePermUUID': uuid.uuid4().bytes, + } + ) + + # ========== 第2步:迁移用户 ========== + print("\n[2/6] 迁移用户数据...") + + user_main_qs = UserMain.objects.all() + total = user_main_qs.count() + print(f" 共 {total} 个用户待迁移") + + # 构建 yonghuid → UserUUID 映射 + uid_map = {} # yonghuid → UserUUID (bytes) + + for i, um in enumerate(user_main_qs): + if (i + 1) % 100 == 0: + print(f" 进度: {i+1}/{total}") + + user_uuid = uuid.uuid4().bytes + + # 创建 gvsdsdk User + user = User.objects.create( + UserUUID=user_uuid, + UserName=um.yonghuid, # 用 yonghuid 作为用户名 + UserUID=um.yonghuid, + OpenID=um.openid or '', + UnionID=um.unionid or '', + Avatar=um.avatar or 'a_long/morentouxiang.jpg', + Phone=um.phone or '', + PhoneVerified=um.shoujihao_renzheng or False, + IP=um.ip or '', + UserAccountLicense=1, + UserPositionStatus=0, + UserCreateTime=um.CreateTime or datetime.datetime.utcnow(), + UserLastLoginDate=um.last_login_time or datetime.datetime.utcnow(), + IsStaff=(um.user_type == 'admin'), + IsSuperuser=(um.user_type == 'admin'), + ) + uid_map[um.yonghuid] = user_uuid + + # 创建 UserRole + user_type = um.user_type or 'normal' + role = created_roles.get(user_type) + if role: + UserRole.objects.create( + UserRoleUUID=uuid.uuid4().bytes, + UserUUID=user_uuid, + RoleUUID=role.RoleUUID, + CompanyUUID=COMPANY_UUID, + ) + + # 创建 UserSensitiveData + UserSensitiveData.objects.create( + user_uuid_id=user_uuid, + password=um.password or '', + zhifu=um.zhifu or '', + skzhanghao=um.skzhanghao or '', + shoujihao_renzheng=um.shoujihao_renzheng or False, + ) + + print(f" 用户迁移完成: {len(uid_map)} 个") + + # ========== 第3步:迁移扩展表 FK ========== + print("\n[3/6] 迁移扩展表 FK...") + + ext_tables = [ + ('UserBoss', UserBoss, 'boss_profile'), + ('UserDashou', UserDashou, 'dashou_profile'), + ('UserShangjia', UserShangjia, 'shop_profile'), + ('UserGuanshi', UserGuanshi, 'guanshi_profile'), + ('UserZuzhang', UserZuzhang, 'zuzhang_profile'), + ('UserShenheguan', UserShenheguan, 'shenheguan_profile'), + ('AdminProfile', AdminProfile, 'admin_profile'), + ('UserKefu', UserKefu, 'kefu_profile'), + ] + + for table_name, model, _ in ext_tables: + count = 0 + for obj in model.objects.all(): + old_user_id = obj.user_id # 旧 FK (UserMain.id) + # 通过旧 UserMain 查找 yonghuid → 新 UserUUID + try: + old_user = UserMain.objects.get(id=old_user_id) + new_uuid = uid_map.get(old_user.yonghuid) + if new_uuid: + obj.user_id = new_uuid # 更新 FK 为新 UserUUID + obj.save(update_fields=['user_id']) + count += 1 + except UserMain.DoesNotExist: + pass + print(f" {table_name}: 迁移 {count} 条") + + # ========== 第4步:迁移其他引用 yonghuid 的表 ========== + print("\n[4/6] 迁移其他表的 yonghuid 引用...") + # Tixianjilu, TixianShenheJilu, TixianAutoRecord, Xiugaijilu, RankingRecord + # 这些表使用 yonghuid 字符串字段,不需要修改(UserUID 保留相同值) + print(" 这些表使用 yonghuid 字符串字段,值不变,无需迁移") + + # ========== 第5步:迁移 backend.UserRole ========== + print("\n[5/6] 迁移 backend.UserRole...") + from backend.models import UserRole as BackendUserRole + count = 0 + for bur in BackendUserRole.objects.all(): + # account_id 是手机号,查找对应的 UserMain → 新 UserUUID + try: + um = UserMain.objects.get(phone=bur.account_id) + new_uuid = uid_map.get(um.yonghuid) + if new_uuid: + # 查找对应的新角色 + old_role = bur.role + # 尝试匹配角色名 + role_name_map = { + '客服': 'kefu', '打手': 'dashou', '商家': 'shop', + '管事': 'guanshi', '管理员': 'admin', '组长': 'zuzhang', + '审核官': 'shenheguan', '消费者': 'normal', + } + new_role = None + for ut, r in created_roles.items(): + if old_role.role_name in r.RoleName or old_role.role_code in ut: + new_role = r + break + if new_role: + UserRole.objects.get_or_create( + UserUUID=new_uuid, + RoleUUID=new_role.RoleUUID, + CompanyUUID=COMPANY_UUID, + defaults={'UserRoleUUID': uuid.uuid4().bytes} + ) + count += 1 + except UserMain.DoesNotExist: + pass + print(f" 迁移 {count} 条 backend.UserRole") + + # ========== 第6步:验证 ========== + print("\n[6/6] 验证迁移结果...") + new_user_count = User.objects.count() + new_role_count = Role.objects.filter(TenantUUID=TENANT_UUID).count() + new_perm_count = Permission.objects.filter(TenantUUID=TENANT_UUID).count() + new_ur_count = UserRole.objects.count() + + print(f" gvsdsdk.User: {new_user_count}") + print(f" Role: {new_role_count}") + print(f" Permission: {new_perm_count}") + print(f" UserRole: {new_ur_count}") + print(f" UserSensitiveData: {UserSensitiveData.objects.count()}") + + print("\n" + "=" * 60) + print("迁移完成!") + print(f"TENANT_UUID = {uuid.UUID(bytes=TENANT_UUID)}") + print(f"COMPANY_UUID = {uuid.UUID(bytes=COMPANY_UUID)}") + print("请将这两个 UUID 保存到 settings.py 中") + print("=" * 60) + + +if __name__ == '__main__': + run_migration() diff --git a/orders/migrations/0001_initial.py b/orders/migrations/0001_initial.py index 3b5e781..9b2a8d4 100644 --- a/orders/migrations/0001_initial.py +++ b/orders/migrations/0001_initial.py @@ -25,8 +25,8 @@ class Migration(migrations.Migration): ('bhliyou', models.TextField(blank=True, null=True, verbose_name='驳回处罚理由')), ('jifen', models.IntegerField(default=0, verbose_name='扣除打手积分')), ('dingdan_id', models.CharField(db_index=True, max_length=32, null=True, verbose_name='订单ID')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'db_table': 'chufajilu', @@ -39,8 +39,8 @@ class Migration(migrations.Migration): ('dingdan_id', models.CharField(db_index=True, max_length=32, null=True, verbose_name='订单ID')), ('yonghuid', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='用户ID')), ('tupian', models.CharField(blank=True, max_length=500, null=True, verbose_name='证据图片URL')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'db_table': 'chufatupian', @@ -53,8 +53,8 @@ class Migration(migrations.Migration): ('dingdan_id', models.CharField(db_index=True, max_length=32, null=True, verbose_name='订单ID')), ('dashou', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='接单打手ID')), ('tupian', models.CharField(blank=True, max_length=500, null=True, verbose_name='打手提交图片URL')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'db_table': 'dashoutupian', @@ -88,8 +88,8 @@ class Migration(migrations.Migration): ('auto_expire_at', models.DateTimeField(blank=True, null=True, verbose_name='计划过期时间')), ('pending_dispatch', models.BooleanField(db_index=True, default=False, verbose_name='待调度标记')), ('status_8_time', models.DateTimeField(blank=True, null=True, verbose_name='状态变为8的时间')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ('clkf', models.CharField(db_index=True, max_length=20, null=True, verbose_name='处理客服')), ('tkly', models.CharField(blank=True, max_length=500, null=True, verbose_name='退款/拒绝理由')), ('wechat_transaction_id', models.CharField(blank=True, db_index=True, max_length=64, null=True, verbose_name='微信支付订单号')), @@ -119,8 +119,8 @@ class Migration(migrations.Migration): ('peihu2_fenhong', models.CharField(blank=True, max_length=10, null=True, verbose_name='陪护2分红比例')), ('zhiding_peihu1', models.CharField(blank=True, max_length=32, null=True, verbose_name='指定陪护1 ID')), ('zhiding_peihu2', models.CharField(blank=True, max_length=32, null=True, verbose_name='指定陪护2 ID')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '陪护订单', @@ -180,8 +180,8 @@ class Migration(migrations.Migration): ('shensuliyou', models.TextField(blank=True, null=True, verbose_name='申诉理由')), ('tongyi_chufaliyou', models.TextField(blank=True, null=True, verbose_name='同意处罚理由')), ('bohuiliyou', models.TextField(blank=True, null=True, verbose_name='驳回理由')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ('shenqingren_shenfen', models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], default=1, verbose_name='申请处罚人身份')), ('chulizhe_shenfen', models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员')], default=2, verbose_name='处理者身份')), ('chulizhe', models.CharField(blank=True, max_length=20, null=True, verbose_name='处理者')), @@ -198,8 +198,8 @@ class Migration(migrations.Migration): fields=[ ('shenfen', models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], primary_key=True, serialize=False, verbose_name='申请处罚人身份')), ('lilv', models.DecimalField(decimal_places=4, default=0.0, max_digits=5, verbose_name='分红费率')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '罚款分红费率配置', @@ -213,8 +213,8 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('lilu', models.DecimalField(decimal_places=4, max_digits=5, null=True, verbose_name='平台分成利率,1是平台,2是商家')), ('fadanpingtai', models.CharField(max_length=10, null=True, verbose_name='订单所属平台,(商家或平台)')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'db_table': 'lilubiao', @@ -255,8 +255,8 @@ class Migration(migrations.Migration): ('jieshao', models.TextField(blank=True, null=True, verbose_name='订单介绍')), ('beizhu', models.TextField(blank=True, null=True, verbose_name='订单备注')), ('nicheng', models.CharField(blank=True, max_length=50, null=True, verbose_name='游戏昵称')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'db_table': 'tuikuanjilu', @@ -270,8 +270,8 @@ class Migration(migrations.Migration): ('dashou', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='接单打手ID')), ('zongfen', models.PositiveIntegerField(default=0, verbose_name='总分')), ('cishu', models.PositiveIntegerField(default=0, verbose_name='评分次数')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '评分表', @@ -287,7 +287,7 @@ class Migration(migrations.Migration): ('dashou_id', models.CharField(db_index=True, max_length=7, verbose_name='打手ID')), ('times', models.PositiveSmallIntegerField(verbose_name='第几次接单(从1开始)')), ('operator', models.CharField(max_length=11, verbose_name='操作客服ID')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ], options={ 'verbose_name': '订单打手变更历史', @@ -304,8 +304,8 @@ class Migration(migrations.Migration): ('user2_id', models.CharField(db_index=True, max_length=30, verbose_name='用户2标识')), ('dingdan_id', models.CharField(max_length=32, unique=True, verbose_name='订单ID')), ('zhuangtai', models.IntegerField(choices=[(1, '开启中'), (0, '已关闭')], db_index=True, default=1, verbose_name='聊天状态')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'db_table': 'liaotian', @@ -318,7 +318,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('beichufa_id', models.CharField(db_index=True, max_length=7, verbose_name='被处罚用户ID')), ('tupian_url', models.CharField(max_length=500, verbose_name='图片相对URL')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('yongtu', models.IntegerField(choices=[(1, '处罚证据图片'), (2, '申诉图片')], default=2, verbose_name='图片用途')), ('fadan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shensu_tupians', to='orders.fadan', verbose_name='关联罚单')), ], @@ -339,8 +339,8 @@ class Migration(migrations.Migration): ('fenhong_cishu', models.IntegerField(default=0, verbose_name='分红次数')), ('is_individual_rate', models.BooleanField(default=False, verbose_name='是否单独开启分红利率')), ('yingde_lilv', models.DecimalField(decimal_places=4, default=0.0, max_digits=5, verbose_name='分红应得利率')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '罚款分红', diff --git a/orders/migrations/0002_chatsession_commissionrate_escortorder_and_more.py b/orders/migrations/0002_chatsession_commissionrate_escortorder_and_more.py index 900961f..a97ac81 100644 --- a/orders/migrations/0002_chatsession_commissionrate_escortorder_and_more.py +++ b/orders/migrations/0002_chatsession_commissionrate_escortorder_and_more.py @@ -18,8 +18,8 @@ class Migration(migrations.Migration): ('User2ID', models.CharField(db_column='user2_id', db_index=True, max_length=30, verbose_name='用户2标识')), ('OrderID', models.CharField(db_column='dingdan_id', max_length=32, unique=True, verbose_name='订单ID')), ('Status', models.IntegerField(choices=[(1, '开启中'), (0, '已关闭')], db_column='zhuangtai', db_index=True, default=1, verbose_name='聊天状态')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'db_table': 'liaotian', @@ -31,8 +31,8 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('Rate', models.DecimalField(db_column='lilu', decimal_places=4, max_digits=5, null=True, verbose_name='平台分成利率')), ('Platform', models.CharField(db_column='fadanpingtai', max_length=10, null=True, verbose_name='订单所属平台')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'db_table': 'lilubiao', @@ -57,8 +57,8 @@ class Migration(migrations.Migration): ('Escort2BonusRate', models.CharField(blank=True, db_column='peihu2_fenhong', max_length=10, null=True, verbose_name='陪护2分红比例')), ('AssignedEscort1', models.CharField(blank=True, db_column='zhiding_peihu1', max_length=32, null=True, verbose_name='指定陪护1 ID')), ('AssignedEscort2', models.CharField(blank=True, db_column='zhiding_peihu2', max_length=32, null=True, verbose_name='指定陪护2 ID')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'verbose_name': '陪护订单', @@ -112,8 +112,8 @@ class Migration(migrations.Migration): ('AutoExpireAt', models.DateTimeField(blank=True, db_column='auto_expire_at', null=True, verbose_name='计划过期时间')), ('PendingDispatch', models.BooleanField(db_column='pending_dispatch', db_index=True, default=False, verbose_name='待调度标记')), ('SettlementTime', models.DateTimeField(blank=True, db_column='status_8_time', null=True, verbose_name='状态变为8的时间')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ('AssignedCS', models.CharField(db_column='clkf', db_index=True, max_length=20, null=True, verbose_name='处理客服')), ('RefundReason', models.CharField(blank=True, db_column='tkly', max_length=500, null=True, verbose_name='退款/拒绝理由')), ('WechatTransactionID', models.CharField(blank=True, db_column='wechat_transaction_id', db_index=True, max_length=64, null=True, verbose_name='微信支付订单号')), @@ -132,7 +132,7 @@ class Migration(migrations.Migration): ('PlayerID', models.CharField(db_column='dashou_id', db_index=True, max_length=7, verbose_name='打手ID')), ('Times', models.PositiveSmallIntegerField(verbose_name='第几次接单(从1开始)')), ('Operator', models.CharField(db_column='operator', max_length=11, verbose_name='操作客服ID')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), ], options={ 'verbose_name': '订单打手变更历史', @@ -155,8 +155,8 @@ class Migration(migrations.Migration): ('AppealReason', models.TextField(blank=True, db_column='shensuliyou', null=True, verbose_name='申诉理由')), ('ApproveReason', models.TextField(blank=True, db_column='tongyi_chufaliyou', null=True, verbose_name='同意处罚理由')), ('RejectReason', models.TextField(blank=True, db_column='bohuiliyou', null=True, verbose_name='驳回理由')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ('ApplicantIdentity', models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], db_column='shenqingren_shenfen', default=1, verbose_name='申请处罚人身份')), ('ProcessorIdentity', models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员')], db_column='chulizhe_shenfen', default=2, verbose_name='处理者身份')), ('ProcessorID', models.CharField(blank=True, db_column='chulizhe', max_length=20, null=True, verbose_name='处理者')), @@ -174,7 +174,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('PenalizedUserID', models.CharField(db_column='beichufa_id', db_index=True, max_length=7, verbose_name='被处罚用户ID')), ('ImageURL', models.CharField(db_column='tupian_url', max_length=500, verbose_name='图片相对URL')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), ('Purpose', models.IntegerField(choices=[(1, '处罚证据图片'), (2, '申诉图片')], db_column='yongtu', default=2, verbose_name='图片用途')), ], options={ @@ -194,8 +194,8 @@ class Migration(migrations.Migration): ('BonusCount', models.IntegerField(db_column='fenhong_cishu', default=0, verbose_name='分红次数')), ('IsIndividualRate', models.BooleanField(db_column='is_individual_rate', default=False, verbose_name='是否单独开启分红利率')), ('IndividualRate', models.DecimalField(db_column='yingde_lilv', decimal_places=4, default=0.0, max_digits=5, verbose_name='分红应得利率')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'verbose_name': '罚款分红', @@ -208,8 +208,8 @@ class Migration(migrations.Migration): fields=[ ('Identity', models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], db_column='shenfen', primary_key=True, serialize=False, verbose_name='申请处罚人身份')), ('Rate', models.DecimalField(db_column='lilv', decimal_places=4, default=0.0, max_digits=5, verbose_name='分红费率')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'verbose_name': '罚款分红费率配置', @@ -224,8 +224,8 @@ class Migration(migrations.Migration): ('OrderID', models.CharField(db_column='dingdan_id', db_index=True, max_length=32, null=True, verbose_name='订单ID')), ('UserID', models.CharField(blank=True, db_column='yonghuid', db_index=True, max_length=32, null=True, verbose_name='用户ID')), ('ImageURL', models.CharField(blank=True, db_column='tupian', max_length=500, null=True, verbose_name='证据图片URL')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'db_table': 'chufatupian', @@ -244,8 +244,8 @@ class Migration(migrations.Migration): ('RejectReason', models.TextField(blank=True, db_column='bhliyou', null=True, verbose_name='驳回处罚理由')), ('DeductedPoints', models.IntegerField(db_column='jifen', default=0, verbose_name='扣除打手积分')), ('OrderID', models.CharField(db_column='dingdan_id', db_index=True, max_length=32, null=True, verbose_name='订单ID')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'db_table': 'chufajilu', @@ -296,8 +296,8 @@ class Migration(migrations.Migration): ('OrderID', models.CharField(db_column='dingdan_id', db_index=True, max_length=32, null=True, verbose_name='订单ID')), ('PlayerID', models.CharField(blank=True, db_column='dashou', db_index=True, max_length=32, null=True, verbose_name='接单打手ID')), ('ImageURL', models.CharField(blank=True, db_column='tupian', max_length=500, null=True, verbose_name='打手提交图片URL')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'db_table': 'dashoutupian', @@ -310,8 +310,8 @@ class Migration(migrations.Migration): ('PlayerID', models.CharField(blank=True, db_column='dashou', db_index=True, max_length=32, null=True, verbose_name='接单打手ID')), ('TotalScore', models.PositiveIntegerField(db_column='zongfen', default=0, verbose_name='总分')), ('RatingCount', models.PositiveIntegerField(db_column='cishu', default=0, verbose_name='评分次数')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'verbose_name': '评分表', @@ -334,8 +334,8 @@ class Migration(migrations.Migration): ('Description', models.TextField(blank=True, db_column='jieshao', null=True, verbose_name='订单介绍')), ('Remark', models.TextField(blank=True, db_column='beizhu', null=True, verbose_name='订单备注')), ('Nickname', models.CharField(blank=True, db_column='nicheng', max_length=50, null=True, verbose_name='游戏昵称')), - ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='create_time', verbose_name='创建时间')), - ('UpdateTime', models.DateTimeField(auto_now=True, db_column='update_time', verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, db_column='CreateTime', verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, db_column='UpdateTime', verbose_name='更新时间')), ], options={ 'db_table': 'tuikuanjilu', diff --git a/orders/models.py b/orders/models.py index 921bf66..d32f023 100644 --- a/orders/models.py +++ b/orders/models.py @@ -1,1027 +1,1027 @@ -"""订单模块数据模型 - -包含订单全生命周期相关模型: - -- **Order** — 订单主表 -- **PlatformOrderExt** — 平台订单扩展表(发单平台=1 时使用) -- **MerchantOrderExt** — 商家订单扩展表(发单平台=2 时使用) -- **EscortOrder** — 陪护订单表 -- **OrderPlayerHistory** — 订单打手变更历史 -- **Penalty** — 罚单表 -- **PenaltyAppealImage** — 罚单申诉图片 -- **PenaltyBonus** — 罚单分红 -- **PenaltyBonusRate** — 罚单分红费率配置 -- **PlayerDeliveryImage** — 打手交付图片 -- **PlayerRating** — 打手评分 -- **CommissionRate** — 分成利率配置 -- **PenaltyRecord** — 处罚记录 -- **PenaltyEvidenceImage** — 处罚证据图片 -- **RefundRecord** — 退款记录 -- **PlatformCashFlow** — 平台流水统计 -- **ChatSession** — 聊天会话 -""" -from django.db import models -from gvsdsdk.model_base import QModel - - -# =========================================================================== -# 订单核心模型 -# =========================================================================== - -class Order(QModel): - """订单主表 - - 状态码含义: - 1=待抢单, 2=进行中, 3=已完成, 4=退款审核中, 5=已退款, - 7=指定中, 8=结算中, 9=未支付, 14=支付失败, 25=已废弃 - - 发单平台(fadan_pingtai): - 1=平台发单(老板微信支付), 2=商家发单(商家余额扣款) - """ - - OrderID = models.CharField( - max_length=32, unique=True, db_index=True, - db_column='dingdan_id', verbose_name='订单ID', - ) - Status = models.IntegerField( - default=1, null=True, blank=True, db_index=True, - db_column='zhuangtai', verbose_name='订单状态', - ) - Platform = models.IntegerField( - null=True, blank=True, - db_column='fadan_pingtai', verbose_name='发单平台', - ) - # ---- 金额 ---- - Amount = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='jine', verbose_name='订单金额', - ) - PlayerCommission = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='dashou_fencheng', verbose_name='打手分成', - ) - ManagerCommission = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, default=0, - db_column='guanshi_fencheng', verbose_name='管事分成', - ) - # ---- 用户与商品关联 ---- - PlayerID = models.CharField( - max_length=32, null=True, db_index=True, blank=True, - db_column='jiedan_dashou_id', verbose_name='接单打手ID', - ) - PlayerRemark = models.TextField( - null=True, blank=True, - db_column='dashou_liuyan', verbose_name='打手留言', - ) - AssignedID = models.CharField( - max_length=32, null=True, blank=True, - db_column='zhiding_id', verbose_name='指定ID', - ) - ProductID = models.IntegerField( - null=True, blank=True, - db_column='shangpin_id', verbose_name='商品ID', - ) - ProductTypeID = models.PositiveIntegerField( - null=True, blank=True, - db_column='leixing_id', verbose_name='商品类型ID', - ) - ImageURL = models.CharField( - max_length=500, null=True, blank=True, - db_column='tupian', verbose_name='商品图片URL', - ) - # ---- 抢单要求 ---- - GrabRequirement = models.PositiveIntegerField( - null=True, blank=True, - db_column='yaoqiuleixing', verbose_name='抢单要求类型', - ) - MembershipID = models.CharField( - max_length=6, null=True, blank=True, - db_column='huiyuan_id', verbose_name='会员ID', - ) - CommissionReq = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='yongjin', verbose_name='佣金要求', - ) - # ---- 描述 ---- - Description = models.TextField( - null=True, blank=True, - db_column='jieshao', verbose_name='订单介绍', - ) - Remark = models.TextField( - null=True, blank=True, - db_column='beizhu', verbose_name='订单备注', - ) - Nickname = models.CharField( - max_length=50, null=True, blank=True, - db_column='nicheng', verbose_name='游戏昵称', - ) - User1ID = models.CharField( - max_length=30, null=True, db_index=True, - db_column='user1_id', verbose_name='用户1标识', - ) - User2ID = models.CharField( - max_length=30, null=True, db_index=True, - db_column='user2_id', verbose_name='用户2打手标识', - ) - # ---- 延时任务与调度 ---- - AutoTaskID = models.CharField( - max_length=255, blank=True, default='', - db_column='auto_task_id', verbose_name='延时任务ID', - ) - AutoExpireAt = models.DateTimeField( - null=True, blank=True, - db_column='auto_expire_at', verbose_name='计划过期时间', - ) - PendingDispatch = models.BooleanField( - default=False, db_index=True, - db_column='pending_dispatch', verbose_name='待调度标记', - ) - SettlementTime = models.DateTimeField( - null=True, blank=True, - db_column='status_8_time', verbose_name='状态变为8的时间', - ) - # ---- 时间戳 ---- - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - # ---- 客服与退款 ---- - AssignedCS = models.CharField( - max_length=20, null=True, db_index=True, - db_column='clkf', verbose_name='处理客服', - ) - RefundReason = models.CharField( - max_length=500, blank=True, null=True, - db_column='tkly', verbose_name='退款/拒绝理由', - ) - WechatTransactionID = models.CharField( - max_length=64, blank=True, null=True, db_index=True, - db_column='wechat_transaction_id', verbose_name='微信支付订单号', - ) - - class Meta: - db_table = 'dingdan' - verbose_name = '订单' - verbose_name_plural = '订单' - indexes = [ - models.Index(fields=['Status']), - models.Index(fields=['OrderID']), - models.Index(fields=['PlayerID']), - models.Index(fields=['WechatTransactionID']), - ] - - def __str__(self): - return self.OrderID - - -class PlatformOrderExt(QModel): - """平台订单扩展表(当 Order.Platform=1 时使用) - - OneToOne 关联 Order,记录平台发单的额外信息: - 老板ID、平台收益、店铺收益、上级分红快照等。 - """ - - Order = models.OneToOneField( - Order, on_delete=models.CASCADE, - related_name='pingtai_kuozhan', - db_column='dingdan_id', verbose_name='订单', - ) - BossID = models.CharField( - max_length=32, null=True, blank=True, db_index=True, - db_column='laoban_id', verbose_name='老板ID', - ) - BossReview = models.TextField( - null=True, blank=True, - db_column='laoban_pingjia', verbose_name='老板评价', - ) - RejectSettlementFlag = models.IntegerField( - default=0, - db_column='jjjs_biaoshi', verbose_name='拒绝结算标记(1表示拒绝结算)', - ) - # ---- 收益 ---- - PlatformIncome = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, default=0.00, - db_column='pingtai_shouyi', verbose_name='平台收益', - ) - ShopIncome = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, default=0.00, - db_column='dianpu_shouyi', verbose_name='店铺收益', - ) - ShopID = models.PositiveIntegerField( - null=True, blank=True, db_index=True, - db_column='dianpu_id', verbose_name='所属店铺ID', - ) - # ---- 上级分红快照 ---- - ParentShopID = models.PositiveIntegerField( - null=True, blank=True, db_index=True, - db_column='shangji_dianpu_id', verbose_name='该订单的上级店铺ID', - ) - ParentBonus = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, default=0.00, - db_column='shangji_fenhong', verbose_name='上级分红金额', - ) - - class Meta: - db_table = 'dingdan_pingtai' - verbose_name = '平台订单扩展' - verbose_name_plural = '平台订单扩展' - indexes = [ - models.Index(fields=['BossID']), - models.Index(fields=['ShopID']), - ] - - def __str__(self): - return f"平台扩展-{self.Order.OrderID}" - - -class MerchantOrderExt(QModel): - """商家发单扩展表(当 Order.Platform=2 时使用) - - OneToOne 关联 Order,记录商家发单的额外信息: - 商家ID、处罚相关、上一个订单ID等。 - """ - - Order = models.OneToOneField( - Order, on_delete=models.CASCADE, - related_name='shangjia_kuozhan', - db_column='dingdan_id', verbose_name='订单', - ) - MerchantID = models.CharField( - max_length=32, null=True, blank=True, db_index=True, - db_column='shangjia_id', verbose_name='商家ID', - ) - MerchantReview = models.TextField( - null=True, blank=True, - db_column='shangjia_pingjia', verbose_name='商家评价', - ) - PenaltyReason = models.TextField( - null=True, blank=True, - db_column='cfliyou', verbose_name='商家处罚理由', - ) - PenaltyApplyStatus = models.IntegerField( - default=0, null=True, blank=True, - db_column='sqzhuangtai', verbose_name='处罚申请状态:0待处理,1成功,2驳回', - ) - RejectReason = models.TextField( - null=True, blank=True, - db_column='bhliyou', verbose_name='驳回商家处罚理由', - ) - MerchantNickname = models.CharField( - max_length=50, null=True, - db_column='sjnicheng', verbose_name='商家昵称', - ) - PrevOrderID = models.CharField( - max_length=32, null=True, blank=True, - db_column='shangyige_dingdan_id', verbose_name='上一个订单ID', - ) - - class Meta: - db_table = 'dingdan_shangjia' - verbose_name = '商家订单扩展' - verbose_name_plural = '商家订单扩展' - indexes = [ - models.Index(fields=['MerchantID']), - ] - - def __str__(self): - return f"商家扩展-{self.Order.OrderID}" - - -class EscortOrder(QModel): - """陪护订单表(一个订单最多2个陪护打手) - - 陪护订单依附于主订单,状态码: - 1=待抢单, 2=已抢待确认, 3=进行中, 4=已完成, 5=已取消, 7=指定状态 - """ - - OrderID = models.CharField( - max_length=32, unique=True, db_index=True, - db_column='dingdan_id', verbose_name='订单ID', - ) - MainPlayerID = models.CharField( - max_length=32, db_index=True, - db_column='zhudashou_id', verbose_name='主打手ID', - ) - Escort1ID = models.CharField( - max_length=32, null=True, blank=True, db_index=True, - db_column='peihu1_id', verbose_name='陪护1打手ID', - ) - Escort2ID = models.CharField( - max_length=32, null=True, blank=True, db_index=True, - db_column='peihu2_id', verbose_name='陪护2打手ID', - ) - Status = models.IntegerField( - default=1, - db_column='zhuangtai', verbose_name='陪护订单状态', - ) - # ---- 需求配置 ---- - EscortCount = models.IntegerField( - default=1, - db_column='peihu_xuqiu_shuliang', verbose_name='陪护需求数量', - ) - CommissionReq = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='yongjin_yaoqiu', verbose_name='抢单佣金要求', - ) - Escort1LevelReq = models.PositiveIntegerField( - null=True, blank=True, - db_column='peihu1_dengji_xuqiu', verbose_name='陪护1等级需求(称号ID)', - ) - Escort2LevelReq = models.PositiveIntegerField( - null=True, blank=True, - db_column='peihu2_dengji_xuqiu', verbose_name='陪护2等级需求(称号ID)', - ) - # ---- 处罚与分红比例 ---- - Escort1BombLiability = models.CharField( - max_length=10, null=True, blank=True, - db_column='peihu1_zhadan_danze', verbose_name='陪护1炸单担责比例', - ) - Escort2BombLiability = models.CharField( - max_length=10, null=True, blank=True, - db_column='peihu2_zhadan_danze', verbose_name='陪护2炸单担责比例', - ) - Escort1BonusRate = models.CharField( - max_length=10, null=True, blank=True, - db_column='peihu1_fenhong', verbose_name='陪护1分红比例', - ) - Escort2BonusRate = models.CharField( - max_length=10, null=True, blank=True, - db_column='peihu2_fenhong', verbose_name='陪护2分红比例', - ) - # ---- 指定陪护 ---- - AssignedEscort1 = models.CharField( - max_length=32, null=True, blank=True, - db_column='zhiding_peihu1', verbose_name='指定陪护1 ID', - ) - AssignedEscort2 = models.CharField( - max_length=32, null=True, blank=True, - db_column='zhiding_peihu2', verbose_name='指定陪护2 ID', - ) - # ---- 时间戳 ---- - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'dingdan_peihu' - verbose_name = '陪护订单' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['MainPlayerID']), - models.Index(fields=['Escort1ID']), - models.Index(fields=['Escort2ID']), - models.Index(fields=['Status']), - ] - - -class OrderPlayerHistory(QModel): - """订单打手变更历史表,记录订单每次更换打手的操作。""" - - OrderID = models.CharField( - max_length=32, db_index=True, - db_column='dingdan_id', verbose_name='订单ID', - ) - PlayerID = models.CharField( - max_length=7, db_index=True, - db_column='dashou_id', verbose_name='打手ID', - ) - Times = models.PositiveSmallIntegerField(verbose_name='第几次接单(从1开始)') - Operator = models.CharField( - max_length=11, - db_column='operator', verbose_name='操作客服ID', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - - class Meta: - db_table = 'order_dashou_history' - verbose_name = '订单打手变更历史' - verbose_name_plural = '订单打手变更历史' - indexes = [ - models.Index(fields=['OrderID']), - models.Index(fields=['PlayerID']), - ] - - def __str__(self): - return f"{self.OrderID} -> {self.PlayerID} 第{self.Times}次" - - -# =========================================================================== -# 罚单相关模型 -# =========================================================================== - -class Penalty(QModel): - """罚单表——平台各类处罚信息 - - 状态码:1=待缴纳, 2=已缴纳, 3=申诉中, 4=已驳回 - 被处罚者身份(shenfen):1=打手, 2=管事, 3=商家, 4=组长 - """ - - id = models.AutoField(primary_key=True, verbose_name='自增主键') - PenalizedUserID = models.CharField( - max_length=20, db_index=True, - db_column='beichufa_id', verbose_name='被处罚用户ID', - ) - ApplicantID = models.CharField( - max_length=20, blank=True, null=True, - db_column='shenqing_chufa', verbose_name='申请处罚者ID', - ) - Identity = models.IntegerField( - choices=[(1, '打手'), (2, '管事'), (3, '商家'), (4, '组长')], - default=1, - db_column='shenfen', verbose_name='被处罚者身份', - ) - Reason = models.TextField( - blank=True, null=True, - db_column='chufaliyou', verbose_name='处罚理由', - ) - FineAmount = models.DecimalField( - max_digits=10, decimal_places=2, default=0.00, - db_column='fakuanjine', verbose_name='罚款金额', - ) - RelatedOrderID = models.CharField( - max_length=32, blank=True, null=True, db_index=True, - db_column='guanliandingdan_id', verbose_name='关联订单ID', - ) - Status = models.IntegerField( - choices=[(1, '待缴纳'), (2, '已缴纳'), (3, '申诉中'), (4, '已驳回')], - default=1, db_index=True, - db_column='zhuangtai', verbose_name='罚单状态', - ) - AffectsGrabbing = models.IntegerField( - choices=[(1, '影响抢单'), (0, '不影响抢单')], - default=1, db_index=True, - db_column='yingxiang_qiangdan', verbose_name='是否影响抢单', - ) - AppealReason = models.TextField( - blank=True, null=True, - db_column='shensuliyou', verbose_name='申诉理由', - ) - ApproveReason = models.TextField( - blank=True, null=True, - db_column='tongyi_chufaliyou', verbose_name='同意处罚理由', - ) - RejectReason = models.TextField( - blank=True, null=True, - db_column='bohuiliyou', verbose_name='驳回理由', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - ApplicantIdentity = models.IntegerField( - choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], - default=1, - db_column='shenqingren_shenfen', verbose_name='申请处罚人身份', - ) - ProcessorIdentity = models.IntegerField( - choices=[(1, '客服'), (2, '售后'), (3, '管理员')], - default=2, - db_column='chulizhe_shenfen', verbose_name='处理者身份', - ) - ProcessorID = models.CharField( - max_length=20, blank=True, null=True, - db_column='chulizhe', verbose_name='处理者', - ) - ApplicantBonusAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='shenqingren_fenhong_jine', verbose_name='申请人分红金额', - ) - - class Meta: - db_table = 'fadan' - verbose_name = '罚单' - verbose_name_plural = '罚单' - indexes = [ - models.Index(fields=['PenalizedUserID', 'AffectsGrabbing', 'Status'], name='fadan_qiangdan_check'), - models.Index(fields=['RelatedOrderID'], name='fadan_dingdan_idx'), - ] - - def __str__(self): - return f"{self.PenalizedUserID} - 罚款{self.FineAmount}元 - 状态{self.Status}" - - -class PenaltyAppealImage(QModel): - """罚款申诉图片,关联罚单。 - - 用途(yongtu):1=处罚证据图片, 2=申诉图片 - """ - - Penalty = models.ForeignKey( - Penalty, on_delete=models.CASCADE, - related_name='shensu_tupians', - db_column='fadan_id', verbose_name='关联罚单', - ) - PenalizedUserID = models.CharField( - max_length=7, db_index=True, - db_column='beichufa_id', verbose_name='被处罚用户ID', - ) - ImageURL = models.CharField( - max_length=500, - db_column='tupian_url', verbose_name='图片相对URL', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - Purpose = models.IntegerField( - choices=[(1, '处罚证据图片'), (2, '申诉图片')], - default=2, - db_column='yongtu', verbose_name='图片用途', - ) - - class Meta: - db_table = 'fadan_shensu_tupian' - verbose_name = '罚款申诉图片' - verbose_name_plural = '罚款申诉图片' - indexes = [ - models.Index(fields=['Penalty']), - models.Index(fields=['PenalizedUserID']), - ] - - -class PenaltyBonus(QModel): - """罚款分红表——记录罚款产生的分红信息 - - 分红者身份(shenfen):1=商家, 2=客服, 3=平台管理者, 4=店铺, 5=售后 - """ - - id = models.AutoField(primary_key=True, verbose_name='自增主键') - BeneficiaryID = models.CharField( - max_length=20, db_index=True, - db_column='fenhongzhe_id', verbose_name='分红者ID', - ) - TotalAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='zonge', verbose_name='分红总额', - ) - WithdrawableAmount = models.DecimalField( - max_digits=12, decimal_places=2, default=0.00, - db_column='ketixian_fenhong', verbose_name='可提现分红', - ) - Identity = models.IntegerField( - choices=[(1, '商家'), (2, '客服'), (3, '平台管理者'), (4, '店铺'), (5, '售后')], - default=1, - db_column='shenfen', verbose_name='分红者身份', - ) - BonusCount = models.IntegerField( - default=0, - db_column='fenhong_cishu', verbose_name='分红次数', - ) - IsIndividualRate = models.BooleanField( - default=False, - db_column='is_individual_rate', verbose_name='是否单独开启分红利率', - ) - IndividualRate = models.DecimalField( - max_digits=5, decimal_places=4, default=0.0000, - db_column='yingde_lilv', verbose_name='分红应得利率', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'fadan_fenhong' - verbose_name = '罚款分红' - verbose_name_plural = '罚款分红' - indexes = [ - models.Index(fields=['BeneficiaryID'], name='fadan_fenhong_zhe_idx'), - models.Index(fields=['Identity'], name='fadan_fenhong_sf_idx'), - ] - - def __str__(self): - return f"{self.BeneficiaryID} - 分红{self.WithdrawableAmount}元" - - -class PenaltyBonusRate(QModel): - """罚款分红费率配置表,按身份设置分红费率。 - - 身份(shenfen):1=客服, 2=售后, 3=管理员, 4=店铺, 5=商家 - """ - - Identity = models.IntegerField( - primary_key=True, - choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], - db_column='shenfen', verbose_name='申请处罚人身份', - ) - Rate = models.DecimalField( - max_digits=5, decimal_places=4, default=0.0000, - db_column='lilv', verbose_name='分红费率', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'fadan_fenhong_lilv' - verbose_name = '罚款分红费率配置' - verbose_name_plural = verbose_name - - def __str__(self): - return f"身份{self.Identity} - 费率{self.Rate}" - - -# =========================================================================== -# 证据/图片/记录模型 -# =========================================================================== - -class PlayerDeliveryImage(QModel): - """打手交付图片表""" - - OrderID = models.CharField( - max_length=32, db_index=True, null=True, - db_column='dingdan_id', verbose_name='订单ID', - ) - PlayerID = models.CharField( - max_length=32, null=True, blank=True, db_index=True, - db_column='dashou', verbose_name='接单打手ID', - ) - ImageURL = models.CharField( - max_length=500, null=True, blank=True, - db_column='tupian', verbose_name='打手提交图片URL', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'dashoutupian' - indexes = [ - models.Index(fields=['PlayerID']), - models.Index(fields=['OrderID']), - ] - - -class PlayerRating(QModel): - """打手评分表""" - - PlayerID = models.CharField( - max_length=32, null=True, blank=True, db_index=True, - db_column='dashou', verbose_name='接单打手ID', - ) - TotalScore = models.PositiveIntegerField( - default=0, - db_column='zongfen', verbose_name='总分', - ) - RatingCount = models.PositiveIntegerField( - default=0, - db_column='cishu', verbose_name='评分次数', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'pingfenbiao' - verbose_name = '评分表' - indexes = [ - models.Index(fields=['PlayerID']), - ] - - def __str__(self): - return f"{self.PlayerID} - 平均分: {self.average_score():.2f}" - - def average_score(self) -> float: - """计算平均分""" - if self.RatingCount > 0: - return float(self.TotalScore) / self.RatingCount - return 0.0 - - -class PenaltyRecord(QModel): - """处罚记录表""" - - PlayerID = models.CharField( - max_length=7, null=True, db_index=True, - db_column='dashouid', verbose_name='被罚打手ID', - ) - ApplicantID = models.CharField( - max_length=11, null=True, db_index=True, - db_column='qingqiuid', verbose_name='请求处罚ID', - ) - ProcessorID = models.CharField( - max_length=11, null=True, db_index=True, - db_column='chuliid', verbose_name='处理处罚ID', - ) - PenaltyReason = models.TextField( - null=True, blank=True, - db_column='cfliyou', verbose_name='处罚理由', - ) - AppealReason = models.TextField( - null=True, blank=True, - db_column='ssliyou', verbose_name='打手申诉理由', - ) - ApplyStatus = models.IntegerField( - default=0, null=True, blank=True, - db_column='sqzhuangtai', verbose_name='处罚申请状态:0待处理,1成功,2驳回', - ) - RejectReason = models.TextField( - null=True, blank=True, - db_column='bhliyou', verbose_name='驳回处罚理由', - ) - DeductedPoints = models.IntegerField( - default=0, - db_column='jifen', verbose_name='扣除打手积分', - ) - OrderID = models.CharField( - max_length=32, null=True, db_index=True, - db_column='dingdan_id', verbose_name='订单ID', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'chufajilu' - indexes = [ - models.Index(fields=['PlayerID']), - models.Index(fields=['ApplicantID']), - models.Index(fields=['ProcessorID']), - models.Index(fields=['OrderID']), - ] - - -class PenaltyEvidenceImage(QModel): - """处罚证据图片表""" - - OrderID = models.CharField( - max_length=32, db_index=True, null=True, - db_column='dingdan_id', verbose_name='订单ID', - ) - UserID = models.CharField( - max_length=32, null=True, blank=True, db_index=True, - db_column='yonghuid', verbose_name='用户ID', - ) - ImageURL = models.CharField( - max_length=500, null=True, blank=True, - db_column='tupian', verbose_name='证据图片URL', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'chufatupian' - indexes = [ - models.Index(fields=['UserID']), - models.Index(fields=['OrderID']), - ] - - -class RefundRecord(QModel): - """退款记录表""" - - OrderID = models.CharField( - max_length=32, unique=True, - db_column='dingdan_id', verbose_name='订单ID', - ) - PlayerID = models.CharField( - max_length=7, null=True, - db_column='dashouid', verbose_name='接单打手ID', - ) - ApplicantID = models.CharField( - max_length=7, null=True, - db_column='qingqiuid', verbose_name='请求用户ID', - ) - ProcessorID = models.CharField( - max_length=11, null=True, db_index=True, - db_column='chuliid', verbose_name='处理人ID', - ) - Reason = models.TextField( - null=True, blank=True, - db_column='liyou', verbose_name='理由', - ) - ApplyStatus = models.IntegerField( - default=0, null=True, blank=True, db_index=True, - db_column='sqzhuangtai', verbose_name='申请状态:0待处理,1成功,2驳回', - ) - RejectReason = models.TextField( - null=True, blank=True, - db_column='bhliyou', verbose_name='驳回理由', - ) - Amount = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='jine', verbose_name='订单金额', - ) - PlayerCommission = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='dashou_fencheng', verbose_name='打手分成', - ) - Description = models.TextField( - null=True, blank=True, - db_column='jieshao', verbose_name='订单介绍', - ) - Remark = models.TextField( - null=True, blank=True, - db_column='beizhu', verbose_name='订单备注', - ) - Nickname = models.CharField( - max_length=50, null=True, blank=True, - db_column='nicheng', verbose_name='游戏昵称', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'tuikuanjilu' - indexes = [ - models.Index(fields=['ProcessorID']), - models.Index(fields=['ApplyStatus']), - ] - - -# =========================================================================== -# 配置/统计模型 -# =========================================================================== - -class CommissionRate(QModel): - """分成利率配置表 - - Platform 字段标识发单平台:1=平台, 2=商家 - """ - - Rate = models.DecimalField( - max_digits=5, decimal_places=4, null=True, - db_column='lilu', verbose_name='平台分成利率', - ) - Platform = models.CharField( - max_length=10, null=True, - db_column='fadanpingtai', verbose_name='订单所属平台', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'lilubiao' - - -class PlatformCashFlow(QModel): - """平台流水统计表""" - - TotalIncome = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='zonge', verbose_name='收取总金额', - ) - TotalPayout = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='zhichuzonge', verbose_name='支出总金额', - ) - TotalProfit = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='zongshouyi', verbose_name='总收益', - ) - DailyIncome = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='rizonge', verbose_name='日收取', - ) - DailyPayout = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='rizhichu', verbose_name='日支出', - ) - DailyProfit = models.DecimalField( - max_digits=10, decimal_places=2, null=True, blank=True, - db_column='rishouyi', verbose_name='日收益', - ) - TotalIncomeCount = models.IntegerField( - default=0, - db_column='zongshoucishu', verbose_name='总收取次数', - ) - DailyIncomeCount = models.IntegerField( - default=0, - db_column='rishoucishu', verbose_name='日收取次数', - ) - TotalPayoutCount = models.IntegerField( - default=0, - db_column='zongzhichucishu', verbose_name='总支出次数', - ) - DailyPayoutCount = models.IntegerField( - default=0, - db_column='rizhichucishu', verbose_name='日支出次数', - ) - - class Meta: - db_table = 'ptliushui' - - -class ChatSession(QModel): - """聊天会话表 - - 状态码:1=开启中, 0=已关闭 - """ - - id = models.BigAutoField(primary_key=True, verbose_name='主键ID') - User1ID = models.CharField( - max_length=30, db_index=True, - db_column='user1_id', verbose_name='用户1标识', - ) - User2ID = models.CharField( - max_length=30, db_index=True, - db_column='user2_id', verbose_name='用户2标识', - ) - OrderID = models.CharField( - max_length=32, unique=True, - db_column='dingdan_id', verbose_name='订单ID', - ) - Status = models.IntegerField( - default=1, db_index=True, - choices=[(1, '开启中'), (0, '已关闭')], - db_column='zhuangtai', verbose_name='聊天状态', - ) - CreateTime = models.DateTimeField( - auto_now_add=True, - db_column='create_time', verbose_name='创建时间', - ) - UpdateTime = models.DateTimeField( - auto_now=True, - db_column='update_time', verbose_name='更新时间', - ) - - class Meta: - db_table = 'liaotian' - indexes = [ - models.Index(fields=['User1ID']), - models.Index(fields=['User2ID']), - models.Index(fields=['Status']), - ] - - def __str__(self): - return f'{self.User1ID} <-> {self.User2ID} ({self.Status})' - - -# =========================================================================== -# 兼容别名(渐进迁移,全部迁移完成后可删除) -# =========================================================================== - -Dingdan = Order -DingdanPingtai = PlatformOrderExt -DingdanShangjia = MerchantOrderExt -DingdanPeihu = EscortOrder -Fadan = Penalty -FadanShensuTupian = PenaltyAppealImage -FadanFenhong = PenaltyBonus -FadanFenhongLilv = PenaltyBonusRate -Dashoutupian = PlayerDeliveryImage -Pingfen = PlayerRating -Lilubiao = CommissionRate -Chufajilu = PenaltyRecord -Chufatupian = PenaltyEvidenceImage -Tuikuanjilu = RefundRecord -Ptliushui = PlatformCashFlow -Liaotian = ChatSession +"""订单模块数据模型 + +包含订单全生命周期相关模型: + +- **Order** — 订单主表 +- **PlatformOrderExt** — 平台订单扩展表(发单平台=1 时使用) +- **MerchantOrderExt** — 商家订单扩展表(发单平台=2 时使用) +- **EscortOrder** — 陪护订单表 +- **OrderPlayerHistory** — 订单打手变更历史 +- **Penalty** — 罚单表 +- **PenaltyAppealImage** — 罚单申诉图片 +- **PenaltyBonus** — 罚单分红 +- **PenaltyBonusRate** — 罚单分红费率配置 +- **PlayerDeliveryImage** — 打手交付图片 +- **PlayerRating** — 打手评分 +- **CommissionRate** — 分成利率配置 +- **PenaltyRecord** — 处罚记录 +- **PenaltyEvidenceImage** — 处罚证据图片 +- **RefundRecord** — 退款记录 +- **PlatformCashFlow** — 平台流水统计 +- **ChatSession** — 聊天会话 +""" +from django.db import models +from gvsdsdk.model_base import QModel + + +# =========================================================================== +# 订单核心模型 +# =========================================================================== + +class Order(QModel): + """订单主表 + + 状态码含义: + 1=待抢单, 2=进行中, 3=已完成, 4=退款审核中, 5=已退款, + 7=指定中, 8=结算中, 9=未支付, 14=支付失败, 25=已废弃 + + 发单平台(fadan_pingtai): + 1=平台发单(老板微信支付), 2=商家发单(商家余额扣款) + """ + + OrderID = models.CharField( + max_length=32, unique=True, db_index=True, + db_column='dingdan_id', verbose_name='订单ID', + ) + Status = models.IntegerField( + default=1, null=True, blank=True, db_index=True, + db_column='zhuangtai', verbose_name='订单状态', + ) + Platform = models.IntegerField( + null=True, blank=True, + db_column='fadan_pingtai', verbose_name='发单平台', + ) + # ---- 金额 ---- + Amount = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='jine', verbose_name='订单金额', + ) + PlayerCommission = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='dashou_fencheng', verbose_name='打手分成', + ) + ManagerCommission = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, default=0, + db_column='guanshi_fencheng', verbose_name='管事分成', + ) + # ---- 用户与商品关联 ---- + PlayerID = models.CharField( + max_length=32, null=True, db_index=True, blank=True, + db_column='jiedan_dashou_id', verbose_name='接单打手ID', + ) + PlayerRemark = models.TextField( + null=True, blank=True, + db_column='dashou_liuyan', verbose_name='打手留言', + ) + AssignedID = models.CharField( + max_length=32, null=True, blank=True, + db_column='zhiding_id', verbose_name='指定ID', + ) + ProductID = models.IntegerField( + null=True, blank=True, + db_column='shangpin_id', verbose_name='商品ID', + ) + ProductTypeID = models.PositiveIntegerField( + null=True, blank=True, + db_column='leixing_id', verbose_name='商品类型ID', + ) + ImageURL = models.CharField( + max_length=500, null=True, blank=True, + db_column='tupian', verbose_name='商品图片URL', + ) + # ---- 抢单要求 ---- + GrabRequirement = models.PositiveIntegerField( + null=True, blank=True, + db_column='yaoqiuleixing', verbose_name='抢单要求类型', + ) + MembershipID = models.CharField( + max_length=6, null=True, blank=True, + db_column='huiyuan_id', verbose_name='会员ID', + ) + CommissionReq = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='yongjin', verbose_name='佣金要求', + ) + # ---- 描述 ---- + Description = models.TextField( + null=True, blank=True, + db_column='jieshao', verbose_name='订单介绍', + ) + Remark = models.TextField( + null=True, blank=True, + db_column='beizhu', verbose_name='订单备注', + ) + Nickname = models.CharField( + max_length=50, null=True, blank=True, + db_column='nicheng', verbose_name='游戏昵称', + ) + User1ID = models.CharField( + max_length=30, null=True, db_index=True, + db_column='user1_id', verbose_name='用户1标识', + ) + User2ID = models.CharField( + max_length=30, null=True, db_index=True, + db_column='user2_id', verbose_name='用户2打手标识', + ) + # ---- 延时任务与调度 ---- + AutoTaskID = models.CharField( + max_length=255, blank=True, default='', + db_column='auto_task_id', verbose_name='延时任务ID', + ) + AutoExpireAt = models.DateTimeField( + null=True, blank=True, + db_column='auto_expire_at', verbose_name='计划过期时间', + ) + PendingDispatch = models.BooleanField( + default=False, db_index=True, + db_column='pending_dispatch', verbose_name='待调度标记', + ) + SettlementTime = models.DateTimeField( + null=True, blank=True, + db_column='status_8_time', verbose_name='状态变为8的时间', + ) + # ---- 时间戳 ---- + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + # ---- 客服与退款 ---- + AssignedCS = models.CharField( + max_length=20, null=True, db_index=True, + db_column='clkf', verbose_name='处理客服', + ) + RefundReason = models.CharField( + max_length=500, blank=True, null=True, + db_column='tkly', verbose_name='退款/拒绝理由', + ) + WechatTransactionID = models.CharField( + max_length=64, blank=True, null=True, db_index=True, + db_column='wechat_transaction_id', verbose_name='微信支付订单号', + ) + + class Meta: + db_table = 'dingdan' + verbose_name = '订单' + verbose_name_plural = '订单' + indexes = [ + models.Index(fields=['Status']), + models.Index(fields=['OrderID']), + models.Index(fields=['PlayerID']), + models.Index(fields=['WechatTransactionID']), + ] + + def __str__(self): + return self.OrderID + + +class PlatformOrderExt(QModel): + """平台订单扩展表(当 Order.Platform=1 时使用) + + OneToOne 关联 Order,记录平台发单的额外信息: + 老板ID、平台收益、店铺收益、上级分红快照等。 + """ + + Order = models.OneToOneField( + Order, on_delete=models.CASCADE, + related_name='pingtai_kuozhan', + db_column='dingdan_id', verbose_name='订单', + ) + BossID = models.CharField( + max_length=32, null=True, blank=True, db_index=True, + db_column='laoban_id', verbose_name='老板ID', + ) + BossReview = models.TextField( + null=True, blank=True, + db_column='laoban_pingjia', verbose_name='老板评价', + ) + RejectSettlementFlag = models.IntegerField( + default=0, + db_column='jjjs_biaoshi', verbose_name='拒绝结算标记(1表示拒绝结算)', + ) + # ---- 收益 ---- + PlatformIncome = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, default=0.00, + db_column='pingtai_shouyi', verbose_name='平台收益', + ) + ShopIncome = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, default=0.00, + db_column='dianpu_shouyi', verbose_name='店铺收益', + ) + ShopID = models.PositiveIntegerField( + null=True, blank=True, db_index=True, + db_column='dianpu_id', verbose_name='所属店铺ID', + ) + # ---- 上级分红快照 ---- + ParentShopID = models.PositiveIntegerField( + null=True, blank=True, db_index=True, + db_column='shangji_dianpu_id', verbose_name='该订单的上级店铺ID', + ) + ParentBonus = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, default=0.00, + db_column='shangji_fenhong', verbose_name='上级分红金额', + ) + + class Meta: + db_table = 'dingdan_pingtai' + verbose_name = '平台订单扩展' + verbose_name_plural = '平台订单扩展' + indexes = [ + models.Index(fields=['BossID']), + models.Index(fields=['ShopID']), + ] + + def __str__(self): + return f"平台扩展-{self.Order.OrderID}" + + +class MerchantOrderExt(QModel): + """商家发单扩展表(当 Order.Platform=2 时使用) + + OneToOne 关联 Order,记录商家发单的额外信息: + 商家ID、处罚相关、上一个订单ID等。 + """ + + Order = models.OneToOneField( + Order, on_delete=models.CASCADE, + related_name='shangjia_kuozhan', + db_column='dingdan_id', verbose_name='订单', + ) + MerchantID = models.CharField( + max_length=32, null=True, blank=True, db_index=True, + db_column='shangjia_id', verbose_name='商家ID', + ) + MerchantReview = models.TextField( + null=True, blank=True, + db_column='shangjia_pingjia', verbose_name='商家评价', + ) + PenaltyReason = models.TextField( + null=True, blank=True, + db_column='cfliyou', verbose_name='商家处罚理由', + ) + PenaltyApplyStatus = models.IntegerField( + default=0, null=True, blank=True, + db_column='sqzhuangtai', verbose_name='处罚申请状态:0待处理,1成功,2驳回', + ) + RejectReason = models.TextField( + null=True, blank=True, + db_column='bhliyou', verbose_name='驳回商家处罚理由', + ) + MerchantNickname = models.CharField( + max_length=50, null=True, + db_column='sjnicheng', verbose_name='商家昵称', + ) + PrevOrderID = models.CharField( + max_length=32, null=True, blank=True, + db_column='shangyige_dingdan_id', verbose_name='上一个订单ID', + ) + + class Meta: + db_table = 'dingdan_shangjia' + verbose_name = '商家订单扩展' + verbose_name_plural = '商家订单扩展' + indexes = [ + models.Index(fields=['MerchantID']), + ] + + def __str__(self): + return f"商家扩展-{self.Order.OrderID}" + + +class EscortOrder(QModel): + """陪护订单表(一个订单最多2个陪护打手) + + 陪护订单依附于主订单,状态码: + 1=待抢单, 2=已抢待确认, 3=进行中, 4=已完成, 5=已取消, 7=指定状态 + """ + + OrderID = models.CharField( + max_length=32, unique=True, db_index=True, + db_column='dingdan_id', verbose_name='订单ID', + ) + MainPlayerID = models.CharField( + max_length=32, db_index=True, + db_column='zhudashou_id', verbose_name='主打手ID', + ) + Escort1ID = models.CharField( + max_length=32, null=True, blank=True, db_index=True, + db_column='peihu1_id', verbose_name='陪护1打手ID', + ) + Escort2ID = models.CharField( + max_length=32, null=True, blank=True, db_index=True, + db_column='peihu2_id', verbose_name='陪护2打手ID', + ) + Status = models.IntegerField( + default=1, + db_column='zhuangtai', verbose_name='陪护订单状态', + ) + # ---- 需求配置 ---- + EscortCount = models.IntegerField( + default=1, + db_column='peihu_xuqiu_shuliang', verbose_name='陪护需求数量', + ) + CommissionReq = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='yongjin_yaoqiu', verbose_name='抢单佣金要求', + ) + Escort1LevelReq = models.PositiveIntegerField( + null=True, blank=True, + db_column='peihu1_dengji_xuqiu', verbose_name='陪护1等级需求(称号ID)', + ) + Escort2LevelReq = models.PositiveIntegerField( + null=True, blank=True, + db_column='peihu2_dengji_xuqiu', verbose_name='陪护2等级需求(称号ID)', + ) + # ---- 处罚与分红比例 ---- + Escort1BombLiability = models.CharField( + max_length=10, null=True, blank=True, + db_column='peihu1_zhadan_danze', verbose_name='陪护1炸单担责比例', + ) + Escort2BombLiability = models.CharField( + max_length=10, null=True, blank=True, + db_column='peihu2_zhadan_danze', verbose_name='陪护2炸单担责比例', + ) + Escort1BonusRate = models.CharField( + max_length=10, null=True, blank=True, + db_column='peihu1_fenhong', verbose_name='陪护1分红比例', + ) + Escort2BonusRate = models.CharField( + max_length=10, null=True, blank=True, + db_column='peihu2_fenhong', verbose_name='陪护2分红比例', + ) + # ---- 指定陪护 ---- + AssignedEscort1 = models.CharField( + max_length=32, null=True, blank=True, + db_column='zhiding_peihu1', verbose_name='指定陪护1 ID', + ) + AssignedEscort2 = models.CharField( + max_length=32, null=True, blank=True, + db_column='zhiding_peihu2', verbose_name='指定陪护2 ID', + ) + # ---- 时间戳 ---- + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'dingdan_peihu' + verbose_name = '陪护订单' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['MainPlayerID']), + models.Index(fields=['Escort1ID']), + models.Index(fields=['Escort2ID']), + models.Index(fields=['Status']), + ] + + +class OrderPlayerHistory(QModel): + """订单打手变更历史表,记录订单每次更换打手的操作。""" + + OrderID = models.CharField( + max_length=32, db_index=True, + db_column='dingdan_id', verbose_name='订单ID', + ) + PlayerID = models.CharField( + max_length=7, db_index=True, + db_column='dashou_id', verbose_name='打手ID', + ) + Times = models.PositiveSmallIntegerField(verbose_name='第几次接单(从1开始)') + Operator = models.CharField( + max_length=11, + db_column='operator', verbose_name='操作客服ID', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + + class Meta: + db_table = 'order_dashou_history' + verbose_name = '订单打手变更历史' + verbose_name_plural = '订单打手变更历史' + indexes = [ + models.Index(fields=['OrderID']), + models.Index(fields=['PlayerID']), + ] + + def __str__(self): + return f"{self.OrderID} -> {self.PlayerID} 第{self.Times}次" + + +# =========================================================================== +# 罚单相关模型 +# =========================================================================== + +class Penalty(QModel): + """罚单表——平台各类处罚信息 + + 状态码:1=待缴纳, 2=已缴纳, 3=申诉中, 4=已驳回 + 被处罚者身份(shenfen):1=打手, 2=管事, 3=商家, 4=组长 + """ + + id = models.AutoField(primary_key=True, verbose_name='自增主键') + PenalizedUserID = models.CharField( + max_length=20, db_index=True, + db_column='beichufa_id', verbose_name='被处罚用户ID', + ) + ApplicantID = models.CharField( + max_length=20, blank=True, null=True, + db_column='shenqing_chufa', verbose_name='申请处罚者ID', + ) + Identity = models.IntegerField( + choices=[(1, '打手'), (2, '管事'), (3, '商家'), (4, '组长')], + default=1, + db_column='shenfen', verbose_name='被处罚者身份', + ) + Reason = models.TextField( + blank=True, null=True, + db_column='chufaliyou', verbose_name='处罚理由', + ) + FineAmount = models.DecimalField( + max_digits=10, decimal_places=2, default=0.00, + db_column='fakuanjine', verbose_name='罚款金额', + ) + RelatedOrderID = models.CharField( + max_length=32, blank=True, null=True, db_index=True, + db_column='guanliandingdan_id', verbose_name='关联订单ID', + ) + Status = models.IntegerField( + choices=[(1, '待缴纳'), (2, '已缴纳'), (3, '申诉中'), (4, '已驳回')], + default=1, db_index=True, + db_column='zhuangtai', verbose_name='罚单状态', + ) + AffectsGrabbing = models.IntegerField( + choices=[(1, '影响抢单'), (0, '不影响抢单')], + default=1, db_index=True, + db_column='yingxiang_qiangdan', verbose_name='是否影响抢单', + ) + AppealReason = models.TextField( + blank=True, null=True, + db_column='shensuliyou', verbose_name='申诉理由', + ) + ApproveReason = models.TextField( + blank=True, null=True, + db_column='tongyi_chufaliyou', verbose_name='同意处罚理由', + ) + RejectReason = models.TextField( + blank=True, null=True, + db_column='bohuiliyou', verbose_name='驳回理由', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + ApplicantIdentity = models.IntegerField( + choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], + default=1, + db_column='shenqingren_shenfen', verbose_name='申请处罚人身份', + ) + ProcessorIdentity = models.IntegerField( + choices=[(1, '客服'), (2, '售后'), (3, '管理员')], + default=2, + db_column='chulizhe_shenfen', verbose_name='处理者身份', + ) + ProcessorID = models.CharField( + max_length=20, blank=True, null=True, + db_column='chulizhe', verbose_name='处理者', + ) + ApplicantBonusAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='shenqingren_fenhong_jine', verbose_name='申请人分红金额', + ) + + class Meta: + db_table = 'fadan' + verbose_name = '罚单' + verbose_name_plural = '罚单' + indexes = [ + models.Index(fields=['PenalizedUserID', 'AffectsGrabbing', 'Status'], name='fadan_qiangdan_check'), + models.Index(fields=['RelatedOrderID'], name='fadan_dingdan_idx'), + ] + + def __str__(self): + return f"{self.PenalizedUserID} - 罚款{self.FineAmount}元 - 状态{self.Status}" + + +class PenaltyAppealImage(QModel): + """罚款申诉图片,关联罚单。 + + 用途(yongtu):1=处罚证据图片, 2=申诉图片 + """ + + Penalty = models.ForeignKey( + Penalty, on_delete=models.CASCADE, + related_name='shensu_tupians', + db_column='fadan_id', verbose_name='关联罚单', + ) + PenalizedUserID = models.CharField( + max_length=7, db_index=True, + db_column='beichufa_id', verbose_name='被处罚用户ID', + ) + ImageURL = models.CharField( + max_length=500, + db_column='tupian_url', verbose_name='图片相对URL', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + Purpose = models.IntegerField( + choices=[(1, '处罚证据图片'), (2, '申诉图片')], + default=2, + db_column='yongtu', verbose_name='图片用途', + ) + + class Meta: + db_table = 'fadan_shensu_tupian' + verbose_name = '罚款申诉图片' + verbose_name_plural = '罚款申诉图片' + indexes = [ + models.Index(fields=['Penalty']), + models.Index(fields=['PenalizedUserID']), + ] + + +class PenaltyBonus(QModel): + """罚款分红表——记录罚款产生的分红信息 + + 分红者身份(shenfen):1=商家, 2=客服, 3=平台管理者, 4=店铺, 5=售后 + """ + + id = models.AutoField(primary_key=True, verbose_name='自增主键') + BeneficiaryID = models.CharField( + max_length=20, db_index=True, + db_column='fenhongzhe_id', verbose_name='分红者ID', + ) + TotalAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='zonge', verbose_name='分红总额', + ) + WithdrawableAmount = models.DecimalField( + max_digits=12, decimal_places=2, default=0.00, + db_column='ketixian_fenhong', verbose_name='可提现分红', + ) + Identity = models.IntegerField( + choices=[(1, '商家'), (2, '客服'), (3, '平台管理者'), (4, '店铺'), (5, '售后')], + default=1, + db_column='shenfen', verbose_name='分红者身份', + ) + BonusCount = models.IntegerField( + default=0, + db_column='fenhong_cishu', verbose_name='分红次数', + ) + IsIndividualRate = models.BooleanField( + default=False, + db_column='is_individual_rate', verbose_name='是否单独开启分红利率', + ) + IndividualRate = models.DecimalField( + max_digits=5, decimal_places=4, default=0.0000, + db_column='yingde_lilv', verbose_name='分红应得利率', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'fadan_fenhong' + verbose_name = '罚款分红' + verbose_name_plural = '罚款分红' + indexes = [ + models.Index(fields=['BeneficiaryID'], name='fadan_fenhong_zhe_idx'), + models.Index(fields=['Identity'], name='fadan_fenhong_sf_idx'), + ] + + def __str__(self): + return f"{self.BeneficiaryID} - 分红{self.WithdrawableAmount}元" + + +class PenaltyBonusRate(QModel): + """罚款分红费率配置表,按身份设置分红费率。 + + 身份(shenfen):1=客服, 2=售后, 3=管理员, 4=店铺, 5=商家 + """ + + Identity = models.IntegerField( + primary_key=True, + choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], + db_column='shenfen', verbose_name='申请处罚人身份', + ) + Rate = models.DecimalField( + max_digits=5, decimal_places=4, default=0.0000, + db_column='lilv', verbose_name='分红费率', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'fadan_fenhong_lilv' + verbose_name = '罚款分红费率配置' + verbose_name_plural = verbose_name + + def __str__(self): + return f"身份{self.Identity} - 费率{self.Rate}" + + +# =========================================================================== +# 证据/图片/记录模型 +# =========================================================================== + +class PlayerDeliveryImage(QModel): + """打手交付图片表""" + + OrderID = models.CharField( + max_length=32, db_index=True, null=True, + db_column='dingdan_id', verbose_name='订单ID', + ) + PlayerID = models.CharField( + max_length=32, null=True, blank=True, db_index=True, + db_column='dashou', verbose_name='接单打手ID', + ) + ImageURL = models.CharField( + max_length=500, null=True, blank=True, + db_column='tupian', verbose_name='打手提交图片URL', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'dashoutupian' + indexes = [ + models.Index(fields=['PlayerID']), + models.Index(fields=['OrderID']), + ] + + +class PlayerRating(QModel): + """打手评分表""" + + PlayerID = models.CharField( + max_length=32, null=True, blank=True, db_index=True, + db_column='dashou', verbose_name='接单打手ID', + ) + TotalScore = models.PositiveIntegerField( + default=0, + db_column='zongfen', verbose_name='总分', + ) + RatingCount = models.PositiveIntegerField( + default=0, + db_column='cishu', verbose_name='评分次数', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'pingfenbiao' + verbose_name = '评分表' + indexes = [ + models.Index(fields=['PlayerID']), + ] + + def __str__(self): + return f"{self.PlayerID} - 平均分: {self.average_score():.2f}" + + def average_score(self) -> float: + """计算平均分""" + if self.RatingCount > 0: + return float(self.TotalScore) / self.RatingCount + return 0.0 + + +class PenaltyRecord(QModel): + """处罚记录表""" + + PlayerID = models.CharField( + max_length=7, null=True, db_index=True, + db_column='dashouid', verbose_name='被罚打手ID', + ) + ApplicantID = models.CharField( + max_length=11, null=True, db_index=True, + db_column='qingqiuid', verbose_name='请求处罚ID', + ) + ProcessorID = models.CharField( + max_length=11, null=True, db_index=True, + db_column='chuliid', verbose_name='处理处罚ID', + ) + PenaltyReason = models.TextField( + null=True, blank=True, + db_column='cfliyou', verbose_name='处罚理由', + ) + AppealReason = models.TextField( + null=True, blank=True, + db_column='ssliyou', verbose_name='打手申诉理由', + ) + ApplyStatus = models.IntegerField( + default=0, null=True, blank=True, + db_column='sqzhuangtai', verbose_name='处罚申请状态:0待处理,1成功,2驳回', + ) + RejectReason = models.TextField( + null=True, blank=True, + db_column='bhliyou', verbose_name='驳回处罚理由', + ) + DeductedPoints = models.IntegerField( + default=0, + db_column='jifen', verbose_name='扣除打手积分', + ) + OrderID = models.CharField( + max_length=32, null=True, db_index=True, + db_column='dingdan_id', verbose_name='订单ID', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'chufajilu' + indexes = [ + models.Index(fields=['PlayerID']), + models.Index(fields=['ApplicantID']), + models.Index(fields=['ProcessorID']), + models.Index(fields=['OrderID']), + ] + + +class PenaltyEvidenceImage(QModel): + """处罚证据图片表""" + + OrderID = models.CharField( + max_length=32, db_index=True, null=True, + db_column='dingdan_id', verbose_name='订单ID', + ) + UserID = models.CharField( + max_length=32, null=True, blank=True, db_index=True, + db_column='yonghuid', verbose_name='用户ID', + ) + ImageURL = models.CharField( + max_length=500, null=True, blank=True, + db_column='tupian', verbose_name='证据图片URL', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'chufatupian' + indexes = [ + models.Index(fields=['UserID']), + models.Index(fields=['OrderID']), + ] + + +class RefundRecord(QModel): + """退款记录表""" + + OrderID = models.CharField( + max_length=32, unique=True, + db_column='dingdan_id', verbose_name='订单ID', + ) + PlayerID = models.CharField( + max_length=7, null=True, + db_column='dashouid', verbose_name='接单打手ID', + ) + ApplicantID = models.CharField( + max_length=7, null=True, + db_column='qingqiuid', verbose_name='请求用户ID', + ) + ProcessorID = models.CharField( + max_length=11, null=True, db_index=True, + db_column='chuliid', verbose_name='处理人ID', + ) + Reason = models.TextField( + null=True, blank=True, + db_column='liyou', verbose_name='理由', + ) + ApplyStatus = models.IntegerField( + default=0, null=True, blank=True, db_index=True, + db_column='sqzhuangtai', verbose_name='申请状态:0待处理,1成功,2驳回', + ) + RejectReason = models.TextField( + null=True, blank=True, + db_column='bhliyou', verbose_name='驳回理由', + ) + Amount = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='jine', verbose_name='订单金额', + ) + PlayerCommission = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='dashou_fencheng', verbose_name='打手分成', + ) + Description = models.TextField( + null=True, blank=True, + db_column='jieshao', verbose_name='订单介绍', + ) + Remark = models.TextField( + null=True, blank=True, + db_column='beizhu', verbose_name='订单备注', + ) + Nickname = models.CharField( + max_length=50, null=True, blank=True, + db_column='nicheng', verbose_name='游戏昵称', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'tuikuanjilu' + indexes = [ + models.Index(fields=['ProcessorID']), + models.Index(fields=['ApplyStatus']), + ] + + +# =========================================================================== +# 配置/统计模型 +# =========================================================================== + +class CommissionRate(QModel): + """分成利率配置表 + + Platform 字段标识发单平台:1=平台, 2=商家 + """ + + Rate = models.DecimalField( + max_digits=5, decimal_places=4, null=True, + db_column='lilu', verbose_name='平台分成利率', + ) + Platform = models.CharField( + max_length=10, null=True, + db_column='fadanpingtai', verbose_name='订单所属平台', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'lilubiao' + + +class PlatformCashFlow(QModel): + """平台流水统计表""" + + TotalIncome = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='zonge', verbose_name='收取总金额', + ) + TotalPayout = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='zhichuzonge', verbose_name='支出总金额', + ) + TotalProfit = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='zongshouyi', verbose_name='总收益', + ) + DailyIncome = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='rizonge', verbose_name='日收取', + ) + DailyPayout = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='rizhichu', verbose_name='日支出', + ) + DailyProfit = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True, + db_column='rishouyi', verbose_name='日收益', + ) + TotalIncomeCount = models.IntegerField( + default=0, + db_column='zongshoucishu', verbose_name='总收取次数', + ) + DailyIncomeCount = models.IntegerField( + default=0, + db_column='rishoucishu', verbose_name='日收取次数', + ) + TotalPayoutCount = models.IntegerField( + default=0, + db_column='zongzhichucishu', verbose_name='总支出次数', + ) + DailyPayoutCount = models.IntegerField( + default=0, + db_column='rizhichucishu', verbose_name='日支出次数', + ) + + class Meta: + db_table = 'ptliushui' + + +class ChatSession(QModel): + """聊天会话表 + + 状态码:1=开启中, 0=已关闭 + """ + + id = models.BigAutoField(primary_key=True, verbose_name='主键ID') + User1ID = models.CharField( + max_length=30, db_index=True, + db_column='user1_id', verbose_name='用户1标识', + ) + User2ID = models.CharField( + max_length=30, db_index=True, + db_column='user2_id', verbose_name='用户2标识', + ) + OrderID = models.CharField( + max_length=32, unique=True, + db_column='dingdan_id', verbose_name='订单ID', + ) + Status = models.IntegerField( + default=1, db_index=True, + choices=[(1, '开启中'), (0, '已关闭')], + db_column='zhuangtai', verbose_name='聊天状态', + ) + CreateTime = models.DateTimeField( + auto_now_add=True, + db_column='CreateTime', verbose_name='创建时间', + ) + UpdateTime = models.DateTimeField( + auto_now=True, + db_column='UpdateTime', verbose_name='更新时间', + ) + + class Meta: + db_table = 'liaotian' + indexes = [ + models.Index(fields=['User1ID']), + models.Index(fields=['User2ID']), + models.Index(fields=['Status']), + ] + + def __str__(self): + return f'{self.User1ID} <-> {self.User2ID} ({self.Status})' + + +# =========================================================================== +# 兼容别名(渐进迁移,全部迁移完成后可删除) +# =========================================================================== + +Dingdan = Order +DingdanPingtai = PlatformOrderExt +DingdanShangjia = MerchantOrderExt +DingdanPeihu = EscortOrder +Fadan = Penalty +FadanShensuTupian = PenaltyAppealImage +FadanFenhong = PenaltyBonus +FadanFenhongLilv = PenaltyBonusRate +Dashoutupian = PlayerDeliveryImage +Pingfen = PlayerRating +Lilubiao = CommissionRate +Chufajilu = PenaltyRecord +Chufatupian = PenaltyEvidenceImage +Tuikuanjilu = RefundRecord +Ptliushui = PlatformCashFlow +Liaotian = ChatSession diff --git a/orders/views.py b/orders/views.py index 6ee8544..08aa7e2 100644 --- a/orders/views.py +++ b/orders/views.py @@ -1497,7 +1497,7 @@ class DingdanXiangqingView2(APIView): zhuangtai = order.Status or 0 jiedan_dashou_id = order.PlayerID or '' dashou_liuyan = order.PlayerRemark or '' - update_time = order.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if order.UpdateTime else '' + UpdateTime = order.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if order.UpdateTime else '' # 交付图片 dashoujiaofu_list = list( @@ -1551,7 +1551,7 @@ class DingdanXiangqingView2(APIView): 'dashou_touxiang': dashou_touxiang, 'dashouliuyan': dashou_liuyan, 'dashoujiaofu': dashoujiaofu_list, - 'update_time': update_time, + 'UpdateTime': UpdateTime, 'jiedan_time': jiedan_time, 'tijiao_time': tijiao_time, 'laoban_pingjia': laoban_pingjia, @@ -2128,7 +2128,7 @@ class ShangjiaDingdanHuoquView(APIView): 'leixing_id': order.ProductTypeID, 'tupian': order.ImageURL or '', 'jiedan_dashou_id': order.PlayerID or '', - 'create_time': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '' + 'CreateTime': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '' } order_list.append(order_data) @@ -2246,8 +2246,8 @@ class ShangjiaDingdanXiangqingView(APIView): 'shensuliyou': fadan_obj.AppealReason or '', 'tongyi_chufaliyou': fadan_obj.ApproveReason or '', 'bohuiliyou': fadan_obj.RejectReason or '', - 'create_time': fadan_obj.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.CreateTime else '', - 'update_time': fadan_obj.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.UpdateTime else '', + 'CreateTime': fadan_obj.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.CreateTime else '', + 'UpdateTime': fadan_obj.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.UpdateTime else '', } # 7. 商家自己的分红利率(用于罚款分红计算) @@ -2285,7 +2285,7 @@ class ShangjiaDingdanXiangqingView(APIView): 'jieshao': order.Description or '', 'beizhu': order.Remark or '', 'nicheng': order.Nickname or '', - 'create_time': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '', + 'CreateTime': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '', # 打手信息 'dashou_yonghuid': dashou_yonghuid, @@ -3774,7 +3774,7 @@ class DashouDingdanHuoquView1(APIView): 'jieshao': order.Description or '', 'tupian': order.ImageURL or '', 'nicheng': order.Nickname or '', - 'create_time': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '' + 'CreateTime': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '' } order_list.append(order_data) @@ -4306,7 +4306,7 @@ class DashouDingdanXiangqingView(APIView): 'tupian': dingdan.ImageURL or '', 'nicheng': dingdan.Nickname or '', 'beizhu': dingdan.Remark or '', - 'create_time': dingdan.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if dingdan.CreateTime else '', + 'CreateTime': dingdan.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if dingdan.CreateTime else '', 'tuikuan_liyou':dingdan.RefundReason or '', 'dashou_liuyan': dingdan.PlayerRemark or '', 'fuwudashou_id': dingdan.PlayerID or '', diff --git a/products/migrations/0001_initial.py b/products/migrations/0001_initial.py index 33235d9..e2017bc 100644 --- a/products/migrations/0001_initial.py +++ b/products/migrations/0001_initial.py @@ -22,8 +22,8 @@ class Migration(migrations.Migration): ('jine', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='订单金额')), ('leixing', models.IntegerField(blank=True, null=True, verbose_name='充值类型,1:会员,2:押金,3:积分,4:商家')), ('shuoming', models.CharField(blank=True, max_length=100, null=True, verbose_name='订单说明')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '充值记录订单', @@ -40,8 +40,8 @@ class Migration(migrations.Migration): ('cishu', models.PositiveIntegerField(verbose_name='分红次数')), ('guanshi_fenhong', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='管事分红金额')), ('zuzhang_fenhong', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='组长分红金额')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '多次分红配置', @@ -64,8 +64,8 @@ class Migration(migrations.Migration): ('zuzhang_fenhong', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='组长分红金额')), ('fenhong_leixing', models.PositiveSmallIntegerField(choices=[(1, '会员分红'), (2, '押金分红'), (3, '商家订单分红')], default=1, verbose_name='分红类型')), ('huiyuan_id', models.CharField(blank=True, max_length=6, null=True, verbose_name='会员ID')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '管事分红记录', @@ -84,8 +84,8 @@ class Migration(migrations.Migration): ('guanshifc', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='管事分成')), ('zuzhangfc', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='组长分成')), ('goumai_cishu', models.PositiveIntegerField(default=0, verbose_name='购买次数')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '会员', @@ -102,8 +102,8 @@ class Migration(migrations.Migration): ('huiyuan_zhuangtai', models.PositiveIntegerField(default=1, verbose_name='会员状态')), ('jieshao', models.CharField(max_length=30, null=True, verbose_name='会员介绍')), ('daoqi_time', models.DateTimeField(verbose_name='会员到期时间')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '用户购买会员', @@ -131,8 +131,8 @@ class Migration(migrations.Migration): ('kaioi_ewai_dashou_fencheng', models.BooleanField(default=False, verbose_name='是否开启打手额外分成')), ('ewai_dashou_fencheng', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='打手额外分成金额')), ('tupian_url', models.CharField(blank=True, max_length=500, null=True, verbose_name='商品图片URL')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更改时间')), ('paixu', models.IntegerField(default=0, verbose_name='排序权重')), ('shenhezhuangtai', models.IntegerField(default=1, help_text='1=正常, 2=审核专用', verbose_name='审核状态')), ('shangjia_zhuangtai', models.BooleanField(default=True, verbose_name='是否上架')), @@ -179,8 +179,8 @@ class Migration(migrations.Migration): ('tuikuan_zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='退款总额')), ('pingtai_shouyi', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='平台收益')), ('dianpu_zongshouyi', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='店铺总收益')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ], options={ 'verbose_name': '商品每日统计', diff --git a/products/models.py b/products/models.py index d986c26..7bd031e 100644 --- a/products/models.py +++ b/products/models.py @@ -1,364 +1,364 @@ -from django.db import models -from gvsdsdk.model_base import QModel -from django.utils import timezone - - -class Shangpin(QModel): - """商品表 - 100-1000条数据""" - id = models.AutoField(primary_key=True, verbose_name='商品ID') - biaoqian = models.CharField(max_length=45,null=True, blank=True, verbose_name='商品标题') # 15汉字 - jiage = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='商品价格') - kucun = models.PositiveIntegerField(default=0, verbose_name='商品库存') - leixing_id = models.PositiveIntegerField(null=True, blank=True,verbose_name='商品类型ID') # 直接存数字 - zhuanqu_id = models.PositiveIntegerField(null=True, blank=True,verbose_name='商品专区ID') # 直接存数字 - zhenshi_xiaoliang = models.PositiveIntegerField(default=0, verbose_name='商品真实销量') - duiwai_xiaoliang = models.PositiveIntegerField(default=0, verbose_name='商品对外销量') - jieshao = models.TextField(null=True, blank=True, verbose_name='商品介绍') - - # 新增字段 - xiadan_xuzhi = models.TextField(null=True, blank=True, verbose_name='下单须知') # 支持500字,可换行 - guize_tupian = models.CharField(max_length=500, null=True, blank=True, verbose_name='规则图片URL') - - yaoqiuleixing = models.PositiveIntegerField(null=True, blank=True, verbose_name='抢单要求类型') - huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') - yongjin = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True,verbose_name='佣金要求') - - # 🔥 新增:是否开启打手额外分成 - kaioi_ewai_dashou_fencheng = models.BooleanField(default=False, verbose_name='是否开启打手额外分成') - # 🔥 新增:打手额外分成金额(开启时使用此值,否则按利率计算) - ewai_dashou_fencheng = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True,verbose_name='打手额外分成金额') - - # 原有图片URL - tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='商品图片URL') - - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更改时间') - paixu = models.IntegerField(default=0, verbose_name='排序权重') - shenhezhuangtai = models.IntegerField(default=1, verbose_name='审核状态', help_text='1=正常, 2=审核专用') - - dianpu = models.ForeignKey( - 'shop.Dianpu', # 🔥 跨应用完整路径 - on_delete=models.CASCADE, - null=True, - blank=True, - db_index=True, - verbose_name='所属店铺' - ) - dianpu_leixing = models.ForeignKey( - 'shop.ShangpinLeixingDianpu', - on_delete=models.SET_NULL, - null=True, - blank=True, - db_index=True, - verbose_name='店铺商品类型' - ) - shangjia_zhuangtai = models.BooleanField(default=True, verbose_name='是否上架') - fengjin_zhuangtai = models.BooleanField(default=True, verbose_name='是否封禁') - shenhe_zhuangtai = models.BooleanField(default=False, verbose_name='平台审核状态',help_text='True=审核通过,False=未审核') - - class Meta: - db_table = 'shangpin' - verbose_name = '商品' - verbose_name_plural = '商品' - # 不要索引!数据量太小 - - def __str__(self): - return f"商品{self.id}:{self.biaoqian}" - - - - -class ShangpinLeixing(QModel): - """商品类型表 - <10条数据""" - id = models.AutoField(primary_key=True, verbose_name='类型ID') - yaoqiuleixing = models.PositiveIntegerField(null=True, blank=True, verbose_name='抢单要求类型') - huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') - yongjin = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='佣金要求') - jieshao = models.CharField(max_length=45, null=True, blank=True, verbose_name='类型介绍') # 15汉字 - tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='类型图片URL') # 用CharField - paixu = models.IntegerField(default=0, verbose_name='排序权重') - shenhezhuangtai = models.IntegerField(default=1, verbose_name='审核状态', help_text='1=正常, 2=审核专用') - bankuai = models.ForeignKey( - 'rank.Bankuai', - on_delete=models.SET_NULL, - null=True, - blank=True, - verbose_name='所属板块' - ) - - class Meta: - db_table = 'shangpin_leixing' - verbose_name = '商品类型' - verbose_name_plural = '商品类型' - - def __str__(self): - return f"{self.jieshao}" - - -class ShangpinZhuanqu(QModel): - """商品专区表 - <50条数据""" - id = models.AutoField(primary_key=True, verbose_name='专区ID') - mingzi = models.CharField(max_length=45, null=True, blank=True, verbose_name='专区名称') # 15汉字 - leixing_id = models.PositiveIntegerField(verbose_name='类型ID') # 直接存数字,不关联 - paixu = models.IntegerField(default=0, verbose_name='排序权重') - shenhezhuangtai = models.IntegerField(default=1, verbose_name='审核状态', help_text='1=正常, 2=审核专用') - - dianpu = models.ForeignKey( - 'shop.Dianpu', # 🔥 跨应用完整路径 - on_delete=models.CASCADE, - null=True, - blank=True, - db_index=True, - verbose_name='所属店铺' - ) - dianpu_leixing = models.ForeignKey( - 'shop.ShangpinLeixingDianpu', - on_delete=models.SET_NULL, - null=True, - blank=True, - db_index=True, - verbose_name='店铺商品类型' - ) - shangjia_zhuangtai = models.BooleanField(default=True, verbose_name='是否上架') - fengjin_zhuangtai = models.BooleanField(default=True, verbose_name='是否封禁') - - - class Meta: - db_table = 'shangpin_zhuanqu' - verbose_name = '商品专区' - verbose_name_plural = '商品专区' - - def __str__(self): - return f"{self.mingzi}" - - -class Huiyuan(QModel): - huiyuan_id = models.CharField(max_length=6, unique=True, verbose_name='会员ID') - jieshao = models.CharField(max_length=30, verbose_name='会员介绍') - jtjieshao = models.TextField(null=True, blank=True, verbose_name='会员规则介绍') - jiage = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='会员价格') - guanshifc = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='管事分成') - - zuzhangfc = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='组长分成') - goumai_cishu = models.PositiveIntegerField(default=0, verbose_name='购买次数') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - bankuai = models.ForeignKey( - 'rank.Bankuai', - on_delete=models.SET_NULL, - null=True, - blank=True, - verbose_name='所属板块' - ) - - class Meta: - db_table = 'huiyuan' - verbose_name = '会员' - verbose_name_plural = '会员' - - def __str__(self): - return f"会员{self.huiyuan_id}:{self.jieshao}" - - -class Huiyuangoumai(QModel): - id = models.AutoField(primary_key=True, verbose_name='购买记录ID') - huiyuan_id = models.CharField(max_length=6, db_index=True, verbose_name='会员ID') - yonghu_id = models.CharField(max_length=32, db_index=True, verbose_name='用户ID') - huiyuan_zhuangtai = models.PositiveIntegerField(default=1, verbose_name='会员状态') - jieshao = models.CharField(max_length=30,null=True,verbose_name='会员介绍') - daoqi_time = models.DateTimeField(verbose_name='会员到期时间') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'huiyuangoumai' - verbose_name = '用户购买会员' - verbose_name_plural = '用户购买会员' - # 添加这个联合唯一约束 - unique_together = [['yonghu_id', 'huiyuan_id']] - - # 顺便优化索引(可选的,建议加) - indexes = [ - models.Index(fields=['yonghu_id']), - models.Index(fields=['huiyuan_id']), - models.Index(fields=['yonghu_id', 'huiyuan_id']), # 复合索引加速查询 - ] - - def __str__(self): - return f"用户{self.yonghu_id}-会员{self.huiyuan_id}" - - def jiance_shifou_daoqi(self): - # 使用Django的timezone,它会自动处理时区 - now = timezone.now() - if now > self.daoqi_time: - # 过期了 - if self.huiyuan_zhuangtai != 0: - self.huiyuan_zhuangtai = 0 - self.save() - return True - else: - # 没过期 - if self.huiyuan_zhuangtai != 1: - self.huiyuan_zhuangtai = 1 - self.save() - return False - - -class Czjilu(QModel): - dingdan_id = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='订单ID') - huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') - zhuangtai = models.IntegerField(null=True, blank=True, verbose_name='订单状态') - yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') - jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='订单金额') - leixing = models.IntegerField(null=True, blank=True, verbose_name='充值类型,1:会员,2:押金,3:积分,4:商家') - shuoming = models.CharField(max_length=100, null=True, blank=True, verbose_name='订单说明') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - class Meta: - db_table = 'czjilu' - verbose_name = '充值记录订单' - verbose_name_plural = '充值记录订单' - # 为高频查询字段添加索引 - indexes = [ - models.Index(fields=['dingdan_id']), - models.Index(fields=['yonghuid']), - - - ] - - - - -class Gsfenhong(QModel): - dingdan_id = models.CharField(max_length=32, unique=True, verbose_name='订单ID') - guanshi = models.CharField(max_length=7, db_index=True, verbose_name='管事ID') - dashouid = models.CharField(max_length=7, db_index=True,verbose_name='打手ID') - shuoming = models.CharField(max_length=100, null=True, blank=True, verbose_name='订单说明') - fenhong = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='订单分红金额') - avatar = models.CharField(max_length=500, null=True, blank=True, verbose_name='打手头像') - nicheng = models.CharField(max_length=50, null=True, verbose_name='打手昵称') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - zuzhang_id = models.CharField(max_length=7, null=True, blank=True, db_index=True, verbose_name='组长ID') - zuzhang_fenhong = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True,verbose_name='组长分红金额') - fenhong_leixing = models.PositiveSmallIntegerField( - default=1, - choices=[(1, '会员分红'), (2, '押金分红'), (3, '商家订单分红')], - verbose_name='分红类型' - ) - - huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'gsfenhong' - verbose_name = '管事分红记录' - verbose_name_plural = '管事分红记录' - # 为高频查询字段添加索引 - indexes = [ - models.Index(fields=['guanshi']), - models.Index(fields=['dashouid']), - - ] - - - - - - -class DuociFenhong(QModel): - """ - 多次分红配置表 - 用于配置不同次数下管事和组长的分红金额 - """ - huiyuan = models.CharField(max_length=10, verbose_name='会员ID') - yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='管事ID') - - cishu = models.PositiveIntegerField(verbose_name='分红次数') - guanshi_fenhong = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='管事分红金额') - zuzhang_fenhong = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='组长分红金额') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'duoci_fenhong' - verbose_name = '多次分红配置' - verbose_name_plural = '多次分红配置' - unique_together = ('huiyuan', 'yonghuid', 'cishu') # 同一管事、同一会员、同一次数唯一 - indexes = [ - models.Index(fields=['yonghuid']), - - ] - - def __str__(self): - return f"会员{self.huiyuan_id} 用户{self.yonghuid} 次数{self.cishu}" - - - - - - - -class ShangpinMeiriTongji(QModel): - """ - 商品每日统计表 - 按商品 + 日期维度记录点单、成交、退款及收益数据 - 日期精确到天,冗余年月日字段便于分组查询 - """ - shangpin = models.ForeignKey( - 'products.Shangpin', - on_delete=models.CASCADE, - db_index=True, - verbose_name='商品ID' - ) - riqi = models.DateField(db_index=True, verbose_name='统计日期') - nian = models.PositiveSmallIntegerField(verbose_name='年份') - yue = models.PositiveSmallIntegerField(verbose_name='月份') - ri = models.PositiveSmallIntegerField(verbose_name='日') - - # 点单统计(下单即累加) - diandan_zongliang = models.PositiveIntegerField(default=0, verbose_name='点单总量') - diandan_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='点单总额') - - # 成交统计(订单完成时累加) - chengjiao_zongliang = models.PositiveIntegerField(default=0, verbose_name='成交总量') - chengjiao_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='成交总额') - - # 退款统计 - tuikuan_zongliang = models.PositiveIntegerField(default=0, verbose_name='退款总量') - tuikuan_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='退款总额') - - # 收益统计(新增) - pingtai_shouyi = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='平台收益') - dianpu_zongshouyi = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='店铺总收益') - - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'shangpin_meiri_tongji' - verbose_name = '商品每日统计' - verbose_name_plural = verbose_name - # 每天每个商品唯一记录 - unique_together = [['shangpin', 'riqi']] - indexes = [ - models.Index(fields=['shangpin', 'riqi']), - models.Index(fields=['nian', 'yue', 'ri']), - ] - - def __str__(self): - return f"{self.shangpin.biaoqian} - {self.riqi}" - - def clean(self): - """保存前自动填充年月日冗余字段""" - if self.riqi: - self.nian = self.riqi.year - self.yue = self.riqi.month - self.ri = self.riqi.day - super().clean() - - def save(self, *args, **kwargs): - self.clean() +from django.db import models +from gvsdsdk.model_base import QModel +from django.utils import timezone + + +class Shangpin(QModel): + """商品表 - 100-1000条数据""" + id = models.AutoField(primary_key=True, verbose_name='商品ID') + biaoqian = models.CharField(max_length=45,null=True, blank=True, verbose_name='商品标题') # 15汉字 + jiage = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='商品价格') + kucun = models.PositiveIntegerField(default=0, verbose_name='商品库存') + leixing_id = models.PositiveIntegerField(null=True, blank=True,verbose_name='商品类型ID') # 直接存数字 + zhuanqu_id = models.PositiveIntegerField(null=True, blank=True,verbose_name='商品专区ID') # 直接存数字 + zhenshi_xiaoliang = models.PositiveIntegerField(default=0, verbose_name='商品真实销量') + duiwai_xiaoliang = models.PositiveIntegerField(default=0, verbose_name='商品对外销量') + jieshao = models.TextField(null=True, blank=True, verbose_name='商品介绍') + + # 新增字段 + xiadan_xuzhi = models.TextField(null=True, blank=True, verbose_name='下单须知') # 支持500字,可换行 + guize_tupian = models.CharField(max_length=500, null=True, blank=True, verbose_name='规则图片URL') + + yaoqiuleixing = models.PositiveIntegerField(null=True, blank=True, verbose_name='抢单要求类型') + huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') + yongjin = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True,verbose_name='佣金要求') + + # 🔥 新增:是否开启打手额外分成 + kaioi_ewai_dashou_fencheng = models.BooleanField(default=False, verbose_name='是否开启打手额外分成') + # 🔥 新增:打手额外分成金额(开启时使用此值,否则按利率计算) + ewai_dashou_fencheng = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True,verbose_name='打手额外分成金额') + + # 原有图片URL + tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='商品图片URL') + + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更改时间') + paixu = models.IntegerField(default=0, verbose_name='排序权重') + shenhezhuangtai = models.IntegerField(default=1, verbose_name='审核状态', help_text='1=正常, 2=审核专用') + + dianpu = models.ForeignKey( + 'shop.Dianpu', # 🔥 跨应用完整路径 + on_delete=models.CASCADE, + null=True, + blank=True, + db_index=True, + verbose_name='所属店铺' + ) + dianpu_leixing = models.ForeignKey( + 'shop.ShangpinLeixingDianpu', + on_delete=models.SET_NULL, + null=True, + blank=True, + db_index=True, + verbose_name='店铺商品类型' + ) + shangjia_zhuangtai = models.BooleanField(default=True, verbose_name='是否上架') + fengjin_zhuangtai = models.BooleanField(default=True, verbose_name='是否封禁') + shenhe_zhuangtai = models.BooleanField(default=False, verbose_name='平台审核状态',help_text='True=审核通过,False=未审核') + + class Meta: + db_table = 'shangpin' + verbose_name = '商品' + verbose_name_plural = '商品' + # 不要索引!数据量太小 + + def __str__(self): + return f"商品{self.id}:{self.biaoqian}" + + + + +class ShangpinLeixing(QModel): + """商品类型表 - <10条数据""" + id = models.AutoField(primary_key=True, verbose_name='类型ID') + yaoqiuleixing = models.PositiveIntegerField(null=True, blank=True, verbose_name='抢单要求类型') + huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') + yongjin = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='佣金要求') + jieshao = models.CharField(max_length=45, null=True, blank=True, verbose_name='类型介绍') # 15汉字 + tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='类型图片URL') # 用CharField + paixu = models.IntegerField(default=0, verbose_name='排序权重') + shenhezhuangtai = models.IntegerField(default=1, verbose_name='审核状态', help_text='1=正常, 2=审核专用') + bankuai = models.ForeignKey( + 'rank.Bankuai', + on_delete=models.SET_NULL, + null=True, + blank=True, + verbose_name='所属板块' + ) + + class Meta: + db_table = 'shangpin_leixing' + verbose_name = '商品类型' + verbose_name_plural = '商品类型' + + def __str__(self): + return f"{self.jieshao}" + + +class ShangpinZhuanqu(QModel): + """商品专区表 - <50条数据""" + id = models.AutoField(primary_key=True, verbose_name='专区ID') + mingzi = models.CharField(max_length=45, null=True, blank=True, verbose_name='专区名称') # 15汉字 + leixing_id = models.PositiveIntegerField(verbose_name='类型ID') # 直接存数字,不关联 + paixu = models.IntegerField(default=0, verbose_name='排序权重') + shenhezhuangtai = models.IntegerField(default=1, verbose_name='审核状态', help_text='1=正常, 2=审核专用') + + dianpu = models.ForeignKey( + 'shop.Dianpu', # 🔥 跨应用完整路径 + on_delete=models.CASCADE, + null=True, + blank=True, + db_index=True, + verbose_name='所属店铺' + ) + dianpu_leixing = models.ForeignKey( + 'shop.ShangpinLeixingDianpu', + on_delete=models.SET_NULL, + null=True, + blank=True, + db_index=True, + verbose_name='店铺商品类型' + ) + shangjia_zhuangtai = models.BooleanField(default=True, verbose_name='是否上架') + fengjin_zhuangtai = models.BooleanField(default=True, verbose_name='是否封禁') + + + class Meta: + db_table = 'shangpin_zhuanqu' + verbose_name = '商品专区' + verbose_name_plural = '商品专区' + + def __str__(self): + return f"{self.mingzi}" + + +class Huiyuan(QModel): + huiyuan_id = models.CharField(max_length=6, unique=True, verbose_name='会员ID') + jieshao = models.CharField(max_length=30, verbose_name='会员介绍') + jtjieshao = models.TextField(null=True, blank=True, verbose_name='会员规则介绍') + jiage = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='会员价格') + guanshifc = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='管事分成') + + zuzhangfc = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='组长分成') + goumai_cishu = models.PositiveIntegerField(default=0, verbose_name='购买次数') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + bankuai = models.ForeignKey( + 'rank.Bankuai', + on_delete=models.SET_NULL, + null=True, + blank=True, + verbose_name='所属板块' + ) + + class Meta: + db_table = 'huiyuan' + verbose_name = '会员' + verbose_name_plural = '会员' + + def __str__(self): + return f"会员{self.huiyuan_id}:{self.jieshao}" + + +class Huiyuangoumai(QModel): + id = models.AutoField(primary_key=True, verbose_name='购买记录ID') + huiyuan_id = models.CharField(max_length=6, db_index=True, verbose_name='会员ID') + yonghu_id = models.CharField(max_length=32, db_index=True, verbose_name='用户ID') + huiyuan_zhuangtai = models.PositiveIntegerField(default=1, verbose_name='会员状态') + jieshao = models.CharField(max_length=30,null=True,verbose_name='会员介绍') + daoqi_time = models.DateTimeField(verbose_name='会员到期时间') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'huiyuangoumai' + verbose_name = '用户购买会员' + verbose_name_plural = '用户购买会员' + # 添加这个联合唯一约束 + unique_together = [['yonghu_id', 'huiyuan_id']] + + # 顺便优化索引(可选的,建议加) + indexes = [ + models.Index(fields=['yonghu_id']), + models.Index(fields=['huiyuan_id']), + models.Index(fields=['yonghu_id', 'huiyuan_id']), # 复合索引加速查询 + ] + + def __str__(self): + return f"用户{self.yonghu_id}-会员{self.huiyuan_id}" + + def jiance_shifou_daoqi(self): + # 使用Django的timezone,它会自动处理时区 + now = timezone.now() + if now > self.daoqi_time: + # 过期了 + if self.huiyuan_zhuangtai != 0: + self.huiyuan_zhuangtai = 0 + self.save() + return True + else: + # 没过期 + if self.huiyuan_zhuangtai != 1: + self.huiyuan_zhuangtai = 1 + self.save() + return False + + +class Czjilu(QModel): + dingdan_id = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='订单ID') + huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') + zhuangtai = models.IntegerField(null=True, blank=True, verbose_name='订单状态') + yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') + jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='订单金额') + leixing = models.IntegerField(null=True, blank=True, verbose_name='充值类型,1:会员,2:押金,3:积分,4:商家') + shuoming = models.CharField(max_length=100, null=True, blank=True, verbose_name='订单说明') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + class Meta: + db_table = 'czjilu' + verbose_name = '充值记录订单' + verbose_name_plural = '充值记录订单' + # 为高频查询字段添加索引 + indexes = [ + models.Index(fields=['dingdan_id']), + models.Index(fields=['yonghuid']), + + + ] + + + + +class Gsfenhong(QModel): + dingdan_id = models.CharField(max_length=32, unique=True, verbose_name='订单ID') + guanshi = models.CharField(max_length=7, db_index=True, verbose_name='管事ID') + dashouid = models.CharField(max_length=7, db_index=True,verbose_name='打手ID') + shuoming = models.CharField(max_length=100, null=True, blank=True, verbose_name='订单说明') + fenhong = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='订单分红金额') + avatar = models.CharField(max_length=500, null=True, blank=True, verbose_name='打手头像') + nicheng = models.CharField(max_length=50, null=True, verbose_name='打手昵称') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + zuzhang_id = models.CharField(max_length=7, null=True, blank=True, db_index=True, verbose_name='组长ID') + zuzhang_fenhong = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True,verbose_name='组长分红金额') + fenhong_leixing = models.PositiveSmallIntegerField( + default=1, + choices=[(1, '会员分红'), (2, '押金分红'), (3, '商家订单分红')], + verbose_name='分红类型' + ) + + huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'gsfenhong' + verbose_name = '管事分红记录' + verbose_name_plural = '管事分红记录' + # 为高频查询字段添加索引 + indexes = [ + models.Index(fields=['guanshi']), + models.Index(fields=['dashouid']), + + ] + + + + + + +class DuociFenhong(QModel): + """ + 多次分红配置表 + 用于配置不同次数下管事和组长的分红金额 + """ + huiyuan = models.CharField(max_length=10, verbose_name='会员ID') + yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='管事ID') + + cishu = models.PositiveIntegerField(verbose_name='分红次数') + guanshi_fenhong = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='管事分红金额') + zuzhang_fenhong = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='组长分红金额') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'duoci_fenhong' + verbose_name = '多次分红配置' + verbose_name_plural = '多次分红配置' + unique_together = ('huiyuan', 'yonghuid', 'cishu') # 同一管事、同一会员、同一次数唯一 + indexes = [ + models.Index(fields=['yonghuid']), + + ] + + def __str__(self): + return f"会员{self.huiyuan_id} 用户{self.yonghuid} 次数{self.cishu}" + + + + + + + +class ShangpinMeiriTongji(QModel): + """ + 商品每日统计表 + 按商品 + 日期维度记录点单、成交、退款及收益数据 + 日期精确到天,冗余年月日字段便于分组查询 + """ + shangpin = models.ForeignKey( + 'products.Shangpin', + on_delete=models.CASCADE, + db_index=True, + verbose_name='商品ID' + ) + riqi = models.DateField(db_index=True, verbose_name='统计日期') + nian = models.PositiveSmallIntegerField(verbose_name='年份') + yue = models.PositiveSmallIntegerField(verbose_name='月份') + ri = models.PositiveSmallIntegerField(verbose_name='日') + + # 点单统计(下单即累加) + diandan_zongliang = models.PositiveIntegerField(default=0, verbose_name='点单总量') + diandan_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='点单总额') + + # 成交统计(订单完成时累加) + chengjiao_zongliang = models.PositiveIntegerField(default=0, verbose_name='成交总量') + chengjiao_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='成交总额') + + # 退款统计 + tuikuan_zongliang = models.PositiveIntegerField(default=0, verbose_name='退款总量') + tuikuan_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='退款总额') + + # 收益统计(新增) + pingtai_shouyi = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='平台收益') + dianpu_zongshouyi = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='店铺总收益') + + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'shangpin_meiri_tongji' + verbose_name = '商品每日统计' + verbose_name_plural = verbose_name + # 每天每个商品唯一记录 + unique_together = [['shangpin', 'riqi']] + indexes = [ + models.Index(fields=['shangpin', 'riqi']), + models.Index(fields=['nian', 'yue', 'ri']), + ] + + def __str__(self): + return f"{self.shangpin.biaoqian} - {self.riqi}" + + def clean(self): + """保存前自动填充年月日冗余字段""" + if self.riqi: + self.nian = self.riqi.year + self.yue = self.riqi.month + self.ri = self.riqi.day + super().clean() + + def save(self, *args, **kwargs): + self.clean() super().save(*args, **kwargs) \ No newline at end of file diff --git a/products/views.py b/products/views.py index 60d709a..df73fd2 100644 --- a/products/views.py +++ b/products/views.py @@ -2904,8 +2904,8 @@ class GuanshiFenhongListAPI(APIView): # 查询当前页数据,按创建时间倒序排列 fenhong_list = Gsfenhong.query.filter(query) \ - .order_by('-create_time') \ - .values('dashouid', 'avatar', 'nicheng', 'fenhong', 'create_time')[offset:offset + page_size] + .order_by('-CreateTime') \ + .values('dashouid', 'avatar', 'nicheng', 'fenhong', 'CreateTime')[offset:offset + page_size] # 格式化数据 formatted_list = [] @@ -2915,7 +2915,7 @@ class GuanshiFenhongListAPI(APIView): 'avatar': item['avatar'] or '', # 打手头像 'nicheng': item['nicheng'] or '未知用户', # 打手昵称 'fenhong': str(item['fenhong']), # 分红金额 - 'create_time': item['create_time'].strftime('%Y-%m-%d %H:%M:%S') if item['create_time'] else '' + 'CreateTime': item['CreateTime'].strftime('%Y-%m-%d %H:%M:%S') if item['CreateTime'] else '' }) # 返回数据 @@ -3904,7 +3904,7 @@ class AdHuiyuanHqView(APIView): # === 2. 查询会员表数据 === # 查询所有会员数据,按创建时间倒序排列 - huiyuan_queryset = Huiyuan.query.all().order_by('-create_time') + huiyuan_queryset = Huiyuan.query.all().order_by('-CreateTime') # 构建会员数据列表 huiyuan_list = [] @@ -3916,9 +3916,9 @@ class AdHuiyuanHqView(APIView): 'jiage': str(huiyuan.jiage), # 会员价格(转换为字符串,避免Decimal序列化问题) 'guanshifc': str(huiyuan.guanshifc), # 管事分成(转换为字符串) 'goumai_cishu': huiyuan.goumai_cishu, # 购买次数(销量) - 'create_time': huiyuan.create_time.strftime('%Y-%m-%d %H:%M:%S') if huiyuan.create_time else None, + 'CreateTime': huiyuan.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if huiyuan.CreateTime else None, # 创建时间 - 'update_time': huiyuan.update_time.strftime('%Y-%m-%d %H:%M:%S') if huiyuan.update_time else None + 'UpdateTime': huiyuan.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if huiyuan.UpdateTime else None # 更新时间 } huiyuan_list.append(huiyuan_data) @@ -4112,10 +4112,10 @@ class HuiyuanXiugaiView(APIView): 'jiage': str(huiyuan_obj.jiage), 'guanshifc': str(huiyuan_obj.guanshifc), 'goumai_cishu': huiyuan_obj.goumai_cishu, - 'create_time': huiyuan_obj.create_time.strftime( - '%Y-%m-%d %H:%M:%S') if huiyuan_obj.create_time else None, - 'update_time': huiyuan_obj.update_time.strftime( - '%Y-%m-%d %H:%M:%S') if huiyuan_obj.update_time else None + 'CreateTime': huiyuan_obj.CreateTime.strftime( + '%Y-%m-%d %H:%M:%S') if huiyuan_obj.CreateTime else None, + 'UpdateTime': huiyuan_obj.UpdateTime.strftime( + '%Y-%m-%d %H:%M:%S') if huiyuan_obj.UpdateTime else None } # 返回成功响应 @@ -4292,10 +4292,10 @@ class AdHuiyuanFbView(APIView): 'jiage': str(huiyuan_obj.jiage), 'guanshifc': str(huiyuan_obj.guanshifc), 'goumai_cishu': huiyuan_obj.goumai_cishu, - 'create_time': huiyuan_obj.create_time.strftime( - '%Y-%m-%d %H:%M:%S') if huiyuan_obj.create_time else None, - 'update_time': huiyuan_obj.update_time.strftime( - '%Y-%m-%d %H:%M:%S') if huiyuan_obj.update_time else None + 'CreateTime': huiyuan_obj.CreateTime.strftime( + '%Y-%m-%d %H:%M:%S') if huiyuan_obj.CreateTime else None, + 'UpdateTime': huiyuan_obj.UpdateTime.strftime( + '%Y-%m-%d %H:%M:%S') if huiyuan_obj.UpdateTime else None } # 返回成功响应 @@ -5110,10 +5110,10 @@ class AdShangpinNeiRongHuoQu(APIView): 'guizetupian_url': shangpin_obj.guize_tupian or '', # 规则图片相对URL 'yajin': str(shangpin_obj.yajin) if hasattr(shangpin_obj, 'yajin') and shangpin_obj.yajin else None, # 押金(可能不存在) - 'chuangjianshijian': shangpin_obj.create_time.strftime( - '%Y-%m-%d %H:%M:%S') if shangpin_obj.create_time else '', - 'genggaishijian': shangpin_obj.update_time.strftime( - '%Y-%m-%d %H:%M:%S') if shangpin_obj.update_time else '', + 'chuangjianshijian': shangpin_obj.CreateTime.strftime( + '%Y-%m-%d %H:%M:%S') if shangpin_obj.CreateTime else '', + 'genggaishijian': shangpin_obj.UpdateTime.strftime( + '%Y-%m-%d %H:%M:%S') if shangpin_obj.UpdateTime else '', # 额外字段,前端可能不需要但可以保留 'leixing_id': shangpin_obj.leixing_id, 'zhuanqu_id': shangpin_obj.zhuanqu_id, @@ -5381,8 +5381,8 @@ class AdShangpinXiuGai(APIView): 'duiwaixiaoliang': shangpin_obj.duiwai_xiaoliang, 'xiadanxuzhi': shangpin_obj.xiadan_xuzhi or '', 'yajin': str(shangpin_obj.yongjin) if shangpin_obj.yongjin else None, - 'genggaishijian': shangpin_obj.update_time.strftime( - '%Y-%m-%d %H:%M:%S') if shangpin_obj.update_time else '', + 'genggaishijian': shangpin_obj.UpdateTime.strftime( + '%Y-%m-%d %H:%M:%S') if shangpin_obj.UpdateTime else '', } return Response({ @@ -5589,7 +5589,7 @@ class HuiyuanTypeListView(APIView): }, status=status.HTTP_401_UNAUTHORIZED) # 🔥 2. 获取所有会员类型 - huiyuan_list = Huiyuan.query.all().order_by('create_time') + huiyuan_list = Huiyuan.query.all().order_by('CreateTime') # 🔥 3. 构建返回数据 huiyuan_data = [] @@ -5599,7 +5599,7 @@ class HuiyuanTypeListView(APIView): 'jieshao': huiyuan.jieshao, # 会员介绍 'jiage': str(huiyuan.jiage), # 价格 'goumai_cishu': huiyuan.goumai_cishu, # 购买次数 - 'create_time': huiyuan.create_time.strftime('%Y-%m-%d %H:%M:%S') if huiyuan.create_time else None + 'CreateTime': huiyuan.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if huiyuan.CreateTime else None }) return Response({ @@ -5710,7 +5710,7 @@ class HuiyuanRecordListView(APIView): hg.huiyuan_id, hg.jieshao, hg.daoqi_time, - hg.create_time, + hg.CreateTime, um.avatar, ud.nicheng FROM huiyuangoumai hg @@ -5728,7 +5728,7 @@ class HuiyuanRecordListView(APIView): record_sql += " AND hg.daoqi_time <= %s" record_params.append(now) - record_sql += " ORDER BY hg.create_time DESC LIMIT %s OFFSET %s" + record_sql += " ORDER BY hg.CreateTime DESC LIMIT %s OFFSET %s" record_params.extend([pagesize, offset]) cursor.execute(record_sql, record_params) @@ -5743,7 +5743,7 @@ class HuiyuanRecordListView(APIView): 'huiyuan_id': record[2], 'jieshao': record[3], 'daoqi_time': record[4].strftime('%Y-%m-%d %H:%M:%S') if record[4] else None, - 'create_time': record[5].strftime('%Y-%m-%d %H:%M:%S') if record[5] else None, + 'CreateTime': record[5].strftime('%Y-%m-%d %H:%M:%S') if record[5] else None, 'touxiang': record[6], # 头像相对URL 'nicheng': record[7] or record[1], # 昵称或用户ID 'yonghu_type': 2 # 🔥 默认打手类型(实际应该根据user_type判断) @@ -5874,7 +5874,7 @@ class ZuzhangFenhongView(APIView): yaoqing_guanshi = UserGuanshi.query.filter(yaoqingren=current_user.UserUID).count() # 3.3 分红次数(总记录数) - base_queryset = Gsfenhong.query.filter(zuzhang_id=current_user.UserUID).order_by('-create_time') + base_queryset = Gsfenhong.query.filter(zuzhang_id=current_user.UserUID).order_by('-CreateTime') fenhong_cishu = base_queryset.count() # 4. 分页查询 @@ -5907,7 +5907,7 @@ class ZuzhangFenhongView(APIView): fenhong_str = str(fenhong.quantize(decimal.Decimal('0.00'))) # 时间格式化 - create_time = rec.create_time.strftime('%Y-%m-%d %H:%M:%S') if rec.create_time else '' + CreateTime = rec.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if rec.CreateTime else '' list_data.append({ 'dashou_avatar': dashou_avatar, @@ -5917,7 +5917,7 @@ class ZuzhangFenhongView(APIView): 'guanshi_nicheng': guanshi_nicheng, 'guanshi_yonghuid': guanshi_id, 'fenhong': fenhong_str, - 'create_time': create_time, + 'CreateTime': CreateTime, }) # 7. 是否有更多数据 diff --git a/rank/migrations/0001_initial.py b/rank/migrations/0001_initial.py index da2a971..b586d23 100644 --- a/rank/migrations/0001_initial.py +++ b/rank/migrations/0001_initial.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): fields=[ ('bankuai_id', models.AutoField(primary_key=True, serialize=False, verbose_name='板块ID')), ('mingcheng', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='板块名称')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ], options={ 'verbose_name': '板块', @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ('leixing', models.CharField(choices=[('dashou', '打手'), ('boss', '老板'), ('shangjia', '商家'), ('guanshi', '管事'), ('zuzhang', '组长')], max_length=10, verbose_name='称号类型')), ('tubiao_url', models.CharField(blank=True, max_length=200, null=True, verbose_name='称号图标相对URL')), ('texiao_miaoshu', models.TextField(blank=True, null=True, verbose_name='特效描述(JSON)')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('kaohe_guize', models.TextField(blank=True, null=True, verbose_name='考核规则')), ('kaioi_jinpai', models.BooleanField(default=False, verbose_name='是否同时开启金牌')), ('bankuai', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='rank.bankuai', verbose_name='所属板块(空=全板块通用)')), @@ -61,7 +61,7 @@ class Migration(migrations.Migration): ('shenheguan_id', models.CharField(blank=True, max_length=7, verbose_name='指定考核官ID')), ('cishu', models.IntegerField(verbose_name='本次考核次数(预计算)')), ('fee', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='本次费用(分账单位)')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ], options={ 'verbose_name': '考核支付临时表', @@ -78,8 +78,8 @@ class Migration(migrations.Migration): ('zhuangtai', models.IntegerField(choices=[(0, '审核中'), (1, '通过'), (2, '未通过'), (3, '待审核')], default=0, verbose_name='审核状态')), ('cishu', models.IntegerField(default=1, verbose_name='第几次审核')), ('jiaofei_jine', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='缴纳费用')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ('jilu_id', models.CharField(db_index=True, max_length=32, unique=True, verbose_name='记录ID')), ('guize_neirong', models.TextField(blank=True, null=True, verbose_name='规则内容')), ('shenheguan_youxi_id', models.CharField(blank=True, max_length=50, null=True, verbose_name='审核官游戏ID')), @@ -101,7 +101,7 @@ class Migration(migrations.Migration): ('cishu', models.IntegerField(verbose_name='对应次数')), ('shenheguan_id', models.CharField(max_length=7, verbose_name='考核官用户ID')), ('yuanyin', models.TextField(verbose_name='拒绝原因')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('jilu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rank.shenhejilu', to_field='jilu_id', verbose_name='审核记录')), ], options={ @@ -162,8 +162,8 @@ class Migration(migrations.Migration): ('taidu_jingyan', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='服务态度经验')), ('zonghe_jingyan', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='综合经验')), ('dingdan_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='关联订单ID')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), ('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='rank.bankuai', verbose_name='所属板块')), ], options={ @@ -176,7 +176,7 @@ class Migration(migrations.Migration): name='DingdanBiaoqian', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rank.chenghao', verbose_name='标签')), ('dingdan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='orders.dingdan', to_field='dingdan_id', verbose_name='订单')), ], @@ -194,7 +194,7 @@ class Migration(migrations.Migration): ('shangxian_sudu', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='上线速度评价(1-5)')), ('fuwu_taidu', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='服务态度评价(1-5)')), ('fanche_cishu', models.PositiveIntegerField(blank=True, null=True, verbose_name='翻车次数')), - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('jiedan_time', models.DateTimeField(blank=True, null=True, verbose_name='接单时间')), ('tijiao_time', models.DateTimeField(blank=True, null=True, verbose_name='提交时间')), ('bankuai', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='rank.bankuai', verbose_name='所属板块')), @@ -256,7 +256,7 @@ class Migration(migrations.Migration): ), migrations.AddIndex( model_name='jingyanjilu', - index=models.Index(fields=['yonghu_id', 'bankuai', 'create_time'], name='jingyan_jil_yonghu__786992_idx'), + index=models.Index(fields=['yonghu_id', 'bankuai', 'CreateTime'], name='jingyan_jil_yonghu__786992_idx'), ), migrations.AddIndex( model_name='dingdanbiaoqian', @@ -268,7 +268,7 @@ class Migration(migrations.Migration): ), migrations.AddIndex( model_name='dashoubiaoxian', - index=models.Index(fields=['dashou_id', 'bankuai', 'create_time'], name='dashou_biao_dashou__36e1c3_idx'), + index=models.Index(fields=['dashou_id', 'bankuai', 'CreateTime'], name='dashou_biao_dashou__36e1c3_idx'), ), migrations.AddIndex( model_name='chenghao', diff --git a/rank/migrations/0003_rename_dashou_biao_dashou__36e1c3_idx_dashou_biao_dashou__502414_idx_and_more.py b/rank/migrations/0003_rename_dashou_biao_dashou__36e1c3_idx_dashou_biao_dashou__502414_idx_and_more.py new file mode 100644 index 0000000..62f172a --- /dev/null +++ b/rank/migrations/0003_rename_dashou_biao_dashou__36e1c3_idx_dashou_biao_dashou__502414_idx_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.27 on 2026-06-19 19:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('rank', '0002_alter_dashoubiaoxian_dingdan_and_more'), + ] + + operations = [ + migrations.RenameIndex( + model_name='dashoubiaoxian', + new_name='dashou_biao_dashou__502414_idx', + old_name='dashou_biao_dashou__36e1c3_idx', + ), + migrations.RenameIndex( + model_name='jingyanjilu', + new_name='jingyan_jil_yonghu__51d881_idx', + old_name='jingyan_jil_yonghu__786992_idx', + ), + ] diff --git a/rank/models.py b/rank/models.py index 1da761e..b03736e 100644 --- a/rank/models.py +++ b/rank/models.py @@ -1,224 +1,224 @@ -from django.db import models -from gvsdsdk.model_base import QModel -from django.conf import settings -from orders.models import Order - -# ==================== 板块表 ==================== -class Bankuai(QModel): - bankuai_id = models.AutoField(primary_key=True, verbose_name='板块ID') - mingcheng = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='板块名称') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'bankuai' - verbose_name = '板块' - verbose_name_plural = verbose_name - - def __str__(self): - return self.mingcheng - - -# ==================== 打手订单表现记录 ==================== -class DashouBiaoxian(QModel): - dingdan = models.ForeignKey('orders.Order', to_field='OrderID', on_delete=models.CASCADE, related_name='biaoxian', verbose_name='关联订单', db_index=True) - - dashou_id = models.CharField(max_length=32, db_index=True, verbose_name='打手ID') - bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', verbose_name='所属板块', db_index=True,null=True) - shangxian_sudu = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name='上线速度评价(1-5)') - fuwu_taidu = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name='服务态度评价(1-5)') - fanche_cishu = models.PositiveIntegerField(null=True, blank=True, verbose_name='翻车次数') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - jiedan_time = models.DateTimeField(null=True, blank=True, verbose_name='接单时间') - tijiao_time = models.DateTimeField(null=True, blank=True, verbose_name='提交时间') - - class Meta: - db_table = 'dashou_biaoxian' - verbose_name = '打手订单表现' - verbose_name_plural = verbose_name - indexes = [models.Index(fields=['dashou_id', 'bankuai', 'create_time'])] - - -# ==================== 经验变动记录 ==================== -class JingyanJilu(QModel): - yonghu_id = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') - yonghu_leixing = models.CharField(max_length=10, choices=[('dashou','打手'),('boss','老板'),('shangjia','商家'),('guanshi','管事'),('zuzhang','组长')], verbose_name='用户类型') - bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', verbose_name='所属板块', db_index=True) - sudu_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='上线速度经验') - shili_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='打手实力经验') - taidu_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='服务态度经验') - zonghe_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='综合经验') - dingdan_id = models.CharField(max_length=32, null=True, blank=True, db_index=True, verbose_name='关联订单ID') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - - class Meta: - db_table = 'jingyan_jilu' - verbose_name = '经验变动记录' - verbose_name_plural = verbose_name - indexes = [models.Index(fields=['yonghu_id', 'bankuai', 'create_time'])] - - -# ==================== 称号主表 ==================== -class Chenghao(QModel): - mingcheng = models.CharField(max_length=50, unique=True, verbose_name='称号名称') - leixing = models.CharField(max_length=10, choices=[('dashou','打手'),('boss','老板'),('shangjia','商家'),('guanshi','管事'),('zuzhang','组长')], verbose_name='称号类型') - bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', null=True, blank=True, verbose_name='所属板块(空=全板块通用)') - tubiao_url = models.CharField(max_length=200, null=True, blank=True, verbose_name='称号图标相对URL') - texiao_miaoshu = models.TextField(null=True, blank=True, verbose_name='特效描述(JSON)') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - kaohe_guize = models.TextField(null=True, blank=True, verbose_name='考核规则') # 新增 - kaioi_jinpai = models.BooleanField(default=False, verbose_name='是否同时开启金牌') - - class Meta: - db_table = 'chenghao' - verbose_name = '称号' - verbose_name_plural = verbose_name - indexes = [models.Index(fields=['leixing', 'bankuai'])] - - -# ==================== 用户 - 称号关联 ==================== -class YonghuChenghao(QModel): - yonghu = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chenghao_list', verbose_name='用户', db_index=True) - chenghao = models.ForeignKey(Chenghao, on_delete=models.CASCADE, verbose_name='称号') - huode_time = models.DateTimeField(auto_now_add=True, verbose_name='获得时间') - - class Meta: - db_table = 'yonghu_chenghao' - verbose_name = '用户称号关联' - verbose_name_plural = verbose_name - unique_together = ('yonghu', 'chenghao') - # 修复:只索引本表字段 - indexes = [models.Index(fields=['yonghu', 'chenghao'])] - - - - - -class DingdanBiaoqian(QModel): - """订单需求标签关联表""" - dingdan = models.ForeignKey( - Order, - to_field='OrderID', # 关联到字符串字段 OrderID - on_delete=models.CASCADE, - verbose_name='订单', - db_index=True - ) - chenghao = models.ForeignKey('rank.Chenghao', on_delete=models.CASCADE, verbose_name='标签') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'dingdan_biaoqian' - verbose_name = '订单需求标签' - verbose_name_plural = verbose_name - unique_together = ('dingdan', 'chenghao') - indexes = [models.Index(fields=['dingdan'])] - - - - -class ShenheJilu(QModel): - """审核记录表""" - shenqingren_id = models.CharField(max_length=7, verbose_name='申请人用户ID') - shenheguan_id = models.CharField(max_length=7, null = True,verbose_name='审核官用户ID') - zhuangtai = models.IntegerField(choices=[(0, '审核中'), (1, '通过'), (2, '未通过'), (3, '待审核')], default=0, verbose_name='审核状态') - bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', null=True, blank=True, verbose_name='审核板块') - chenghao = models.ForeignKey(Chenghao, on_delete=models.PROTECT, null=True, blank=True, verbose_name='审核标签') - cishu = models.IntegerField(default=1, verbose_name='第几次审核') - jiaofei_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='缴纳费用') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - jilu_id = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='记录ID') # 新增 - guize_neirong = models.TextField(null=True, blank=True, verbose_name='规则内容') - shenheguan_youxi_id = models.CharField(max_length=50, null=True, blank=True, verbose_name='审核官游戏ID') - dashou_youxi_id = models.CharField(max_length=50, null=True, blank=True, verbose_name='打手游戏ID') - dashou_beizhu = models.TextField(null=True, blank=True, verbose_name='打手备注') - - class Meta: - db_table = 'shenhe_jilu' - verbose_name = '审核记录' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['shenqingren_id', 'zhuangtai']), - models.Index(fields=['shenheguan_id']), - ] - - - -class KaohePayTemp(QModel): - """考核支付临时上下文(仅用于支付成功后回调取参,不删除数据)""" - dingdan_id = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='订单ID') - yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') - tag_id = models.IntegerField(verbose_name='标签ID') - game_nick = models.CharField(max_length=100, verbose_name='游戏昵称') - remark = models.TextField(blank=True, verbose_name='备注') - reapply = models.BooleanField(default=False, verbose_name='是否再次申请') - jilu_id = models.CharField(max_length=32, null=True, blank=True, verbose_name='原记录ID(再次申请时)') - shenheguan_id = models.CharField(max_length=7, blank=True, verbose_name='指定考核官ID') - cishu = models.IntegerField(verbose_name='本次考核次数(预计算)') - fee = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='本次费用(分账单位)') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'kaohe_pay_temp' - verbose_name = '考核支付临时表' - verbose_name_plural = verbose_name - - - - - -class KaoheCishuFeiyong(QModel): - """各标签各考核次数所需缴纳金额""" - chenghao = models.ForeignKey(Chenghao, on_delete=models.CASCADE, verbose_name='标签') - cishu = models.IntegerField(verbose_name='第几次考核') - feiyong = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='需缴纳金额') - - class Meta: - db_table = 'kaohe_cishu_feiyong' - verbose_name = '考核次数费用' - verbose_name_plural = verbose_name - unique_together = ('chenghao', 'cishu') - indexes = [models.Index(fields=['chenghao', 'cishu'])] - - -class KaoheJujueJilu(QModel): - """考核拒绝原因记录""" - jilu = models.ForeignKey(ShenheJilu, on_delete=models.CASCADE, to_field='jilu_id', verbose_name='审核记录') - cishu = models.IntegerField(verbose_name='对应次数') - shenheguan_id = models.CharField(max_length=7, verbose_name='考核官用户ID') - yuanyin = models.TextField(verbose_name='拒绝原因') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'kaohe_jujue_jilu' - verbose_name = '拒绝原因记录' - verbose_name_plural = verbose_name - indexes = [models.Index(fields=['jilu_id', 'cishu'])] - - -class KaoheJiluFeiyong(QModel): - """每次考核记录实际缴纳的费用明细""" - jilu = models.ForeignKey(ShenheJilu, on_delete=models.CASCADE, to_field='jilu_id', verbose_name='考核记录') - cishu = models.IntegerField(verbose_name='第几次考核') - jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='缴纳金额') - - class Meta: - db_table = 'kaohe_jilu_feiyong' - verbose_name = '考核记录费用' - verbose_name_plural = verbose_name - unique_together = ('jilu', 'cishu') - - - -class KaoheguanBankuai(QModel): - """考核官与板块多对多关联表""" - kaoheguan_id = models.CharField(max_length=7, db_index=True, verbose_name='考核官用户ID') - bankuai = models.ForeignKey(Bankuai, on_delete=models.CASCADE, to_field='bankuai_id', verbose_name='所属板块') - - class Meta: - db_table = 'kaoheguan_bankuai' - verbose_name = '考核官板块关联' - verbose_name_plural = verbose_name - unique_together = ('kaoheguan_id', 'bankuai') +from django.db import models +from gvsdsdk.model_base import QModel +from django.conf import settings +from orders.models import Order + +# ==================== 板块表 ==================== +class Bankuai(QModel): + bankuai_id = models.AutoField(primary_key=True, verbose_name='板块ID') + mingcheng = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='板块名称') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'bankuai' + verbose_name = '板块' + verbose_name_plural = verbose_name + + def __str__(self): + return self.mingcheng + + +# ==================== 打手订单表现记录 ==================== +class DashouBiaoxian(QModel): + dingdan = models.ForeignKey('orders.Order', to_field='OrderID', on_delete=models.CASCADE, related_name='biaoxian', verbose_name='关联订单', db_index=True) + + dashou_id = models.CharField(max_length=32, db_index=True, verbose_name='打手ID') + bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', verbose_name='所属板块', db_index=True,null=True) + shangxian_sudu = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name='上线速度评价(1-5)') + fuwu_taidu = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name='服务态度评价(1-5)') + fanche_cishu = models.PositiveIntegerField(null=True, blank=True, verbose_name='翻车次数') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + jiedan_time = models.DateTimeField(null=True, blank=True, verbose_name='接单时间') + tijiao_time = models.DateTimeField(null=True, blank=True, verbose_name='提交时间') + + class Meta: + db_table = 'dashou_biaoxian' + verbose_name = '打手订单表现' + verbose_name_plural = verbose_name + indexes = [models.Index(fields=['dashou_id', 'bankuai', 'CreateTime'])] + + +# ==================== 经验变动记录 ==================== +class JingyanJilu(QModel): + yonghu_id = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') + yonghu_leixing = models.CharField(max_length=10, choices=[('dashou','打手'),('boss','老板'),('shangjia','商家'),('guanshi','管事'),('zuzhang','组长')], verbose_name='用户类型') + bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', verbose_name='所属板块', db_index=True) + sudu_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='上线速度经验') + shili_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='打手实力经验') + taidu_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='服务态度经验') + zonghe_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='综合经验') + dingdan_id = models.CharField(max_length=32, null=True, blank=True, db_index=True, verbose_name='关联订单ID') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + + class Meta: + db_table = 'jingyan_jilu' + verbose_name = '经验变动记录' + verbose_name_plural = verbose_name + indexes = [models.Index(fields=['yonghu_id', 'bankuai', 'CreateTime'])] + + +# ==================== 称号主表 ==================== +class Chenghao(QModel): + mingcheng = models.CharField(max_length=50, unique=True, verbose_name='称号名称') + leixing = models.CharField(max_length=10, choices=[('dashou','打手'),('boss','老板'),('shangjia','商家'),('guanshi','管事'),('zuzhang','组长')], verbose_name='称号类型') + bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', null=True, blank=True, verbose_name='所属板块(空=全板块通用)') + tubiao_url = models.CharField(max_length=200, null=True, blank=True, verbose_name='称号图标相对URL') + texiao_miaoshu = models.TextField(null=True, blank=True, verbose_name='特效描述(JSON)') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + kaohe_guize = models.TextField(null=True, blank=True, verbose_name='考核规则') # 新增 + kaioi_jinpai = models.BooleanField(default=False, verbose_name='是否同时开启金牌') + + class Meta: + db_table = 'chenghao' + verbose_name = '称号' + verbose_name_plural = verbose_name + indexes = [models.Index(fields=['leixing', 'bankuai'])] + + +# ==================== 用户 - 称号关联 ==================== +class YonghuChenghao(QModel): + yonghu = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chenghao_list', verbose_name='用户', db_index=True) + chenghao = models.ForeignKey(Chenghao, on_delete=models.CASCADE, verbose_name='称号') + huode_time = models.DateTimeField(auto_now_add=True, verbose_name='获得时间') + + class Meta: + db_table = 'yonghu_chenghao' + verbose_name = '用户称号关联' + verbose_name_plural = verbose_name + unique_together = ('yonghu', 'chenghao') + # 修复:只索引本表字段 + indexes = [models.Index(fields=['yonghu', 'chenghao'])] + + + + + +class DingdanBiaoqian(QModel): + """订单需求标签关联表""" + dingdan = models.ForeignKey( + Order, + to_field='OrderID', # 关联到字符串字段 OrderID + on_delete=models.CASCADE, + verbose_name='订单', + db_index=True + ) + chenghao = models.ForeignKey('rank.Chenghao', on_delete=models.CASCADE, verbose_name='标签') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'dingdan_biaoqian' + verbose_name = '订单需求标签' + verbose_name_plural = verbose_name + unique_together = ('dingdan', 'chenghao') + indexes = [models.Index(fields=['dingdan'])] + + + + +class ShenheJilu(QModel): + """审核记录表""" + shenqingren_id = models.CharField(max_length=7, verbose_name='申请人用户ID') + shenheguan_id = models.CharField(max_length=7, null = True,verbose_name='审核官用户ID') + zhuangtai = models.IntegerField(choices=[(0, '审核中'), (1, '通过'), (2, '未通过'), (3, '待审核')], default=0, verbose_name='审核状态') + bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', null=True, blank=True, verbose_name='审核板块') + chenghao = models.ForeignKey(Chenghao, on_delete=models.PROTECT, null=True, blank=True, verbose_name='审核标签') + cishu = models.IntegerField(default=1, verbose_name='第几次审核') + jiaofei_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='缴纳费用') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') + jilu_id = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='记录ID') # 新增 + guize_neirong = models.TextField(null=True, blank=True, verbose_name='规则内容') + shenheguan_youxi_id = models.CharField(max_length=50, null=True, blank=True, verbose_name='审核官游戏ID') + dashou_youxi_id = models.CharField(max_length=50, null=True, blank=True, verbose_name='打手游戏ID') + dashou_beizhu = models.TextField(null=True, blank=True, verbose_name='打手备注') + + class Meta: + db_table = 'shenhe_jilu' + verbose_name = '审核记录' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['shenqingren_id', 'zhuangtai']), + models.Index(fields=['shenheguan_id']), + ] + + + +class KaohePayTemp(QModel): + """考核支付临时上下文(仅用于支付成功后回调取参,不删除数据)""" + dingdan_id = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='订单ID') + yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') + tag_id = models.IntegerField(verbose_name='标签ID') + game_nick = models.CharField(max_length=100, verbose_name='游戏昵称') + remark = models.TextField(blank=True, verbose_name='备注') + reapply = models.BooleanField(default=False, verbose_name='是否再次申请') + jilu_id = models.CharField(max_length=32, null=True, blank=True, verbose_name='原记录ID(再次申请时)') + shenheguan_id = models.CharField(max_length=7, blank=True, verbose_name='指定考核官ID') + cishu = models.IntegerField(verbose_name='本次考核次数(预计算)') + fee = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='本次费用(分账单位)') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'kaohe_pay_temp' + verbose_name = '考核支付临时表' + verbose_name_plural = verbose_name + + + + + +class KaoheCishuFeiyong(QModel): + """各标签各考核次数所需缴纳金额""" + chenghao = models.ForeignKey(Chenghao, on_delete=models.CASCADE, verbose_name='标签') + cishu = models.IntegerField(verbose_name='第几次考核') + feiyong = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='需缴纳金额') + + class Meta: + db_table = 'kaohe_cishu_feiyong' + verbose_name = '考核次数费用' + verbose_name_plural = verbose_name + unique_together = ('chenghao', 'cishu') + indexes = [models.Index(fields=['chenghao', 'cishu'])] + + +class KaoheJujueJilu(QModel): + """考核拒绝原因记录""" + jilu = models.ForeignKey(ShenheJilu, on_delete=models.CASCADE, to_field='jilu_id', verbose_name='审核记录') + cishu = models.IntegerField(verbose_name='对应次数') + shenheguan_id = models.CharField(max_length=7, verbose_name='考核官用户ID') + yuanyin = models.TextField(verbose_name='拒绝原因') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'kaohe_jujue_jilu' + verbose_name = '拒绝原因记录' + verbose_name_plural = verbose_name + indexes = [models.Index(fields=['jilu_id', 'cishu'])] + + +class KaoheJiluFeiyong(QModel): + """每次考核记录实际缴纳的费用明细""" + jilu = models.ForeignKey(ShenheJilu, on_delete=models.CASCADE, to_field='jilu_id', verbose_name='考核记录') + cishu = models.IntegerField(verbose_name='第几次考核') + jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='缴纳金额') + + class Meta: + db_table = 'kaohe_jilu_feiyong' + verbose_name = '考核记录费用' + verbose_name_plural = verbose_name + unique_together = ('jilu', 'cishu') + + + +class KaoheguanBankuai(QModel): + """考核官与板块多对多关联表""" + kaoheguan_id = models.CharField(max_length=7, db_index=True, verbose_name='考核官用户ID') + bankuai = models.ForeignKey(Bankuai, on_delete=models.CASCADE, to_field='bankuai_id', verbose_name='所属板块') + + class Meta: + db_table = 'kaoheguan_bankuai' + verbose_name = '考核官板块关联' + verbose_name_plural = verbose_name + unique_together = ('kaoheguan_id', 'bankuai') indexes = [models.Index(fields=['kaoheguan_id'])] \ No newline at end of file diff --git a/rank/views.py b/rank/views.py index bbae71e..2c90d3e 100644 --- a/rank/views.py +++ b/rank/views.py @@ -244,7 +244,7 @@ class ShenheDatingDataView(APIView): total = query.count() offset = (page - 1) * page_size - jilu_list = query.select_related('bankuai', 'chenghao').order_by('-create_time')[ + jilu_list = query.select_related('bankuai', 'chenghao').order_by('-CreateTime')[ offset:offset + page_size] records = [] @@ -274,7 +274,7 @@ class ShenheDatingDataView(APIView): 'chenghao_texiao_json': texiao_json, 'guize_neirong': jl.guize_neirong or '', 'kaohe_feiyong': kaohe_feiyong, # 新增 - 'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S') if jl.create_time else '', + 'CreateTime': jl.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if jl.CreateTime else '', }) has_more = (page * page_size) < total @@ -416,7 +416,7 @@ class ShenheXiangqingView(APIView): last_fail = KaoheJujueJilu.objects.filter( jilu=jl, cishu=jl.cishu - 1 - ).order_by('-create_time').first() + ).order_by('-CreateTime').first() if last_fail: last_fail_reason = last_fail.yuanyin @@ -435,7 +435,7 @@ class ShenheXiangqingView(APIView): 'daishou_fei': daishou_fei, # 新增:待收考核费 'last_fail_reason': last_fail_reason, 'is_jinpai': is_jinpai, - 'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S') if jl.create_time else '', + 'CreateTime': jl.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if jl.CreateTime else '', }) has_more = (page * page_size) < total @@ -685,7 +685,7 @@ class KaoheJiluView(APIView): ) total = query.count() - records = query.order_by('-create_time')[(page-1)*page_size : page*page_size] + records = query.order_by('-CreateTime')[(page-1)*page_size : page*page_size] data = [] for jl in records: @@ -695,7 +695,7 @@ class KaoheJiluView(APIView): )['total'] or 0.00 # 上次拒绝原因(最近一条) - last_fail = KaoheJujueJilu.objects.filter(jilu=jl).order_by('-create_time').first() + last_fail = KaoheJujueJilu.objects.filter(jilu=jl).order_by('-CreateTime').first() fail_reason = last_fail.yuanyin if last_fail else '' # 考核官头像 @@ -716,7 +716,7 @@ class KaoheJiluView(APIView): 'cishu': jl.cishu, 'total_fee': float(total_fee), 'last_fail_reason': fail_reason, - 'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S'), + 'CreateTime': jl.CreateTime.strftime('%Y-%m-%d %H:%M:%S'), # 为再次申请预留标签对象数据(前端可能用到) 'tag_obj': { 'id': jl.chenghao_id, @@ -849,7 +849,7 @@ class KaoheJiluListView(APIView): # 4. 计数与分页 total = base_query.count() offset = (page - 1) * page_size - jilu_list = base_query.order_by('-create_time')[offset:offset + page_size] + jilu_list = base_query.order_by('-CreateTime')[offset:offset + page_size] # 5. 预取关联数据,减少查询次数 jilu_list = jilu_list.select_related('bankuai', 'chenghao') @@ -875,7 +875,7 @@ class KaoheJiluListView(APIView): reject_dict[rej.jilu_id].append({ 'cishu': rej.cishu, 'yuanyin': rej.yuanyin, - 'create_time': rej.create_time.strftime('%Y-%m-%d %H:%M:%S') if rej.create_time else '' + 'CreateTime': rej.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if rej.CreateTime else '' }) # 6. 批量获取申请人信息(昵称和头像) @@ -927,8 +927,8 @@ class KaoheJiluListView(APIView): 'dashou_beizhu': jl.dashou_beizhu or '', 'shenheguan_youxi_id': jl.shenheguan_youxi_id or '', 'guize_neirong': jl.guize_neirong or '', - 'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S') if jl.create_time else '', - 'update_time': jl.update_time.strftime('%Y-%m-%d %H:%M:%S') if jl.update_time else '', + 'CreateTime': jl.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if jl.CreateTime else '', + 'UpdateTime': jl.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if jl.UpdateTime else '', 'fee_list': fee_items, 'reject_list': reject_dict.get(jl.jilu_id, []), }) diff --git a/refactor_replace.py b/refactor_replace.py index d482e46..b750aa6 100644 --- a/refactor_replace.py +++ b/refactor_replace.py @@ -1,122 +1,122 @@ -import os -import re - -ROOT_DIR = r"d:\ALONGF\Django" -EXCLUDE_DIRS = {"migrations", "__pycache__", ".git", "node_modules", ".venv", "idea"} -EXCLUDE_FILES = {"migrate_to_gvsdsdk_user.py", "rp.py", "refactor_replace.py"} - -SAFE_REPLACEMENTS = { - "shoujihao_renzheng": "PhoneVerified", - "last_login_time": "UserLastLoginDate", - "openid": "OpenID", - "user_type": "UserType", - "dashou_profile": "DashouProfile", - "shop_profile": "ShopProfile", - "guanshi_profile": "GuanshiProfile", - "boss_profile": "BossProfile", - "admin_profile": "AdminProfile", - "kefu_profile": "KefuProfile", - "zuzhang_profile": "ZuzhangProfile", - "shenheguan_profile": "ShenheguanProfile", - "has_role": "HasRole", -} - -UNSAFE_REPLACEMENTS = { - "yonghuid": "UserUID", - "avatar": "Avatar", - "phone": "Phone", - "ip": "IP", - "create_time": "UserCreateTime", - "zhifu": "PaymentQRCode", - "skzhanghao": "PaymentAccount", -} - -NON_USER_VARS = { - "record", "tixian_record", "tixian", "audit", - "item", "items", - "shangjia", "product", "guanshi", "zuzhang", - "bk", "ch", "sg", "p", "m", - "huiyuan", "huiyuan_obj", "shangbin_obj", "shangpin_obj", - "moban", "link", "dingdan_obj", - "jl", "rej", "dianpu", "shop", - "r", "rec", "chufa", "auto_record", - "um", "hg", "xjl", "Dingdan", - "obj", "tixian_obj", - "sensitive_data", "self", - "boss_profile", "dashou_profile", "guanshi_profile", "shop_profile", - "kefu_profile", "admin_profile", "zuzhang_profile", "shenheguan_profile", - "BossProfile", "DashouProfile", "GuanshiProfile", "ShopProfile", - "KefuProfile", "AdminProfile", "ZuzhangProfile", "ShenheguanProfile", - "shangjia_profile", - "dingdan", "order", "tixian_shenhe", -} - - -def should_skip_dir(dirpath): - for excl in EXCLUDE_DIRS: - if excl in dirpath: - return True - return False - - -def process_content(content): - # 1. Safe replacements: global .old -> .new - for old, new in SAFE_REPLACEMENTS.items(): - pattern = r"\." + re.escape(old) + r"\b" - content = re.sub(pattern, "." + new, content) - - # 2. Unsafe replacements: targeted, only on User instance variables - for old, new in UNSAFE_REPLACEMENTS.items(): - def make_replacer(new_name): - def replacer(m): - var = m.group(1) - if var in NON_USER_VARS: - return m.group(0) - return var + "." + new_name - return replacer - pattern = r"(? .new + for old, new in SAFE_REPLACEMENTS.items(): + pattern = r"\." + re.escape(old) + r"\b" + content = re.sub(pattern, "." + new, content) + + # 2. Unsafe replacements: targeted, only on User instance variables + for old, new in UNSAFE_REPLACEMENTS.items(): + def make_replacer(new_name): + def replacer(m): + var = m.group(1) + if var in NON_USER_VARS: + return m.group(0) + return var + "." + new_name + return replacer + pattern = r"(?0 时按状态筛选 if zhuangtai_filter > 0: qs = qs.filter(zhuangtai=zhuangtai_filter) - qs = qs.order_by('-create_time') + qs = qs.order_by('-CreateTime') records_queryset = qs.values( - 'id', 'leixing', 'jine', 'zhuangtai', 'create_time', 'fangshi', + 'id', 'leixing', 'jine', 'zhuangtai', 'CreateTime', 'fangshi', 'phone', 'skzhanghao', 'zhifu', 'bhliyou', 'shenhe_jilu_id', 'shenhe_danhao', )[offset:offset + limit + 1] @@ -2202,7 +2202,7 @@ class TixianJiluHuoquViewV2(APIView): if danhao_list: for ar in TixianAutoRecord.query.filter( shenhe_danhao__in=danhao_list, zhuangtai=2 - ).order_by('-create_time').values('shenhe_danhao', 'fail_reason'): + ).order_by('-CreateTime').values('shenhe_danhao', 'fail_reason'): if ar['shenhe_danhao'] not in auto_fail_map: auto_fail_map[ar['shenhe_danhao']] = ar['fail_reason'] @@ -2227,7 +2227,7 @@ class TixianJiluHuoquViewV2(APIView): 'leixing': record['leixing'], 'jine': jine_val, 'zhuangtai': record['zhuangtai'], - 'create': record['create_time'].strftime('%Y-%m-%d %H:%M:%S'), + 'create': record['CreateTime'].strftime('%Y-%m-%d %H:%M:%S'), 'fangshi': record['fangshi'], 'txdianhua': record['phone'] or '', 'txzh': record['skzhanghao'] or '', @@ -2484,7 +2484,7 @@ class AdminFinancialDataView(APIView): # 如果需要汇总多条记录,可以使用aggregate进行聚合查询 # 方法1:获取最新的一条记录(假设表中只有一条汇总记录) - shouzhijilu = Szjilu.query.order_by('-create_time').first() + shouzhijilu = Szjilu.query.order_by('-CreateTime').first() # 方法2:如果表中可能有多条记录,需要汇总,可以使用以下方式: # from django.db.models Sum @@ -3150,7 +3150,7 @@ class AdGuanLiYongHu(APIView): total_count = queryset.count() # 获取当前页的数据 - users = list(queryset.order_by('-create_time')[offset:offset + limit]) + users = list(queryset.order_by('-CreateTime')[offset:offset + limit]) # 计算是否有更多数据 current_count = len(users) @@ -3193,7 +3193,7 @@ class AdGuanLiYongHu(APIView): total_count = queryset.count() # 获取当前页的数据 - dashou_list = list(queryset.order_by('-create_time')[offset:offset + limit]) + dashou_list = list(queryset.order_by('-CreateTime')[offset:offset + limit]) # 计算是否有更多数据 current_count = len(dashou_list) @@ -3237,7 +3237,7 @@ class AdGuanLiYongHu(APIView): total_count = queryset.count() # 获取当前页的数据 - guanshi_list = list(queryset.order_by('-create_time')[offset:offset + limit]) + guanshi_list = list(queryset.order_by('-CreateTime')[offset:offset + limit]) # 计算是否有更多数据 current_count = len(guanshi_list) @@ -3280,7 +3280,7 @@ class AdGuanLiYongHu(APIView): total_count = queryset.count() # 获取当前页的数据 - shangjia_list = list(queryset.order_by('-create_time')[offset:offset + limit]) + shangjia_list = list(queryset.order_by('-CreateTime')[offset:offset + limit]) # 计算是否有更多数据 current_count = len(shangjia_list) @@ -3487,8 +3487,8 @@ class CfGuanLi(APIView): 'bhliyou': chufa.RejectReason, 'jifen': chufa.DeductedPoints, 'dingdan_id': chufa.OrderID, - 'create_time': chufa.create_time, - 'update_time': chufa.update_time, + 'CreateTime': chufa.CreateTime, + 'UpdateTime': chufa.UpdateTime, # 🔴【新增】申诉理由字段 'ssliyou': chufa.AppealReason or '', # 🔴【新增】商家证据图片 @@ -3863,8 +3863,8 @@ class AdckyhxqView(APIView): 'avatar': user_main.Avatar, 'phone': user_main.Phone, 'ip': user_main.IP, - 'create_time': user_main.UserCreateTime, - 'update_time': user_main.UserCreateTime, + 'CreateTime': user_main.UserCreateTime, + 'UpdateTime': user_main.UserCreateTime, 'user_type': user_type } @@ -3879,7 +3879,7 @@ class AdckyhxqView(APIView): 'zonge': boss_profile.zonge, 'alldingdan': boss_profile.alldingdan, 'alltui': boss_profile.alltui, - 'create_time_boss': boss_profile.create_time + 'CreateTime_boss': boss_profile.CreateTime }) elif user_type == 2: # 打手 @@ -3910,14 +3910,14 @@ class AdckyhxqView(APIView): 'jinyueshouyi': dashou_profile.jinyueshouyi, 'jifen': dashou_profile.jifen, 'yajin': dashou_profile.yajin, - 'create_time_dashou': dashou_profile.create_time + 'CreateTime_dashou': dashou_profile.CreateTime }) # 查询打手的会员信息(只返回未过期的) # 使用select_related预取huiyuan信息(如果有关联的话) member_records = Huiyuangoumai.query.filter( yonghu_id=uid - ).order_by('-create_time') + ).order_by('-CreateTime') member_list = [] for record in member_records: @@ -3936,7 +3936,7 @@ class AdckyhxqView(APIView): 'huiyuan_name': huiyuan.jieshao, 'daoqi_time': record.daoqi_time, 'is_active': record.huiyuan_zhuangtai == 1, - 'create_time': record.create_time + 'CreateTime': record.CreateTime }) response_data['member_list'] = member_list @@ -3957,7 +3957,7 @@ class AdckyhxqView(APIView): 'jinyuechongzhi': guanshi_profile.jinyuechongzhi, 'chongzhifenrun': guanshi_profile.chongzhifenrun, 'yue': guanshi_profile.yue, # 管事可提现余额 - 'create_time_guanshi': guanshi_profile.create_time + 'CreateTime_guanshi': guanshi_profile.CreateTime }) elif user_type == 4: # 商家 @@ -3978,7 +3978,7 @@ class AdckyhxqView(APIView): 'jinriliushui': shop_profile.jinriliushui, 'jinyuedingdan': shop_profile.jinyuedingdan, 'jinyueliushui': shop_profile.jinyueliushui, - 'create_time_shop': shop_profile.create_time + 'CreateTime_shop': shop_profile.CreateTime }) # 获取所有会员列表(用于前端下拉选择) @@ -4092,7 +4092,7 @@ class AdcjxgView(APIView): 'xiugaiid': zhanghao, 'leixing': user_type, 'qitashuoming': '', - 'create_time': timezone.now() + 'CreateTime': timezone.now() } # 标记是否有敏感字段被修改 @@ -4499,7 +4499,7 @@ class AddtxshView(APIView): query &= Q(yonghuid=search_uid) # 🔴【修改】排序改为按申请时间升序(最早申请排最前) - queryset = Tixianjilu.query.filter(query).order_by('create_time') + queryset = Tixianjilu.query.filter(query).order_by('CreateTime') # 🔴【修改】分页逻辑:如果是搜索模式,不分页,返回所有匹配记录 if search_uid: @@ -4541,8 +4541,8 @@ class AddtxshView(APIView): 'fangshi': record.fangshi, 'shenheid': record.shenheid, 'bhliyou': record.bhliyou, - 'create_time': record.create_time, - 'update_time': record.update_time + 'CreateTime': record.CreateTime, + 'UpdateTime': record.UpdateTime }) return Response({ @@ -4628,7 +4628,7 @@ class AdtixianqkView(APIView): if result == 3: # 拒绝 withdrawal.bhliyou = reason - withdrawal.update_time = timezone.now() + withdrawal.UpdateTime = timezone.now() withdrawal.save() # 🟢 新增:如果同意提现(result=2),更新收支记录表 @@ -4646,7 +4646,7 @@ class AdtixianqkView(APIView): szjilu_record.TotalIncome -= tixian_jine # 总收益减去提现金额 szjilu_record.TotalExpense += tixian_jine # 总支出加上提现金额 szjilu_record.DailyExpense += tixian_jine # 今日支出加上提现金额 - szjilu_record.update_time = timezone.now() + szjilu_record.UpdateTime = timezone.now() szjilu_record.save() except Szjilu.DoesNotExist: @@ -4668,7 +4668,7 @@ class AdtixianqkView(APIView): 'id': withdrawal.id, 'zhuangtai': withdrawal.zhuangtai, 'shenheid': withdrawal.shenheid, - 'update_time': withdrawal.update_time + 'UpdateTime': withdrawal.UpdateTime } }, status=status.HTTP_200_OK) @@ -6224,8 +6224,8 @@ class ChufaJiluHuoquView(APIView): 'sqzhuangtai': record.ApplyStatus or 0, 'ssliyou': record.AppealReason or '', 'jifen': record.DeductedPoints or 0, - 'create_time': record.create_time.strftime('%Y-%m-%d %H:%M:%S') if record.create_time else '', - 'update_time': record.update_time.strftime('%Y-%m-%d %H:%M:%S') if record.update_time else '', + 'CreateTime': record.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if record.CreateTime else '', + 'UpdateTime': record.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if record.UpdateTime else '', 'zhengju_tupian': zhengju_tupian, # 商家上传的证据图片 'shensu_tupian': shensu_tupian, # 打手上传的申诉图片 } @@ -6402,8 +6402,8 @@ class DashouShensuView(APIView): 'shensu_liyou': shensu_liyou, 'shensu_tupian_urls': saved_tupian_urls, 'sqzhuangtai': chufa_record.ApplyStatus, - 'update_time': chufa_record.update_time.strftime( - '%Y-%m-%d %H:%M:%S') if chufa_record.update_time else '' + 'UpdateTime': chufa_record.UpdateTime.strftime( + '%Y-%m-%d %H:%M:%S') if chufa_record.UpdateTime else '' } # 记录操作日志 @@ -6652,8 +6652,8 @@ class ShangjiaChufaJiluHuoquView(APIView): 'sqzhuangtai': record.ApplyStatus or 0, 'ssliyou': record.AppealReason or '', 'jifen': record.DeductedPoints or 0, - 'create_time': record.create_time.strftime('%Y-%m-%d %H:%M:%S') if record.create_time else '', - 'update_time': record.update_time.strftime('%Y-%m-%d %H:%M:%S') if record.update_time else '', + 'CreateTime': record.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if record.CreateTime else '', + 'UpdateTime': record.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if record.UpdateTime else '', 'zhengju_tupian': zhengju_tupian, # 商家自己上传的证据图片 'shensu_tupian': shensu_tupian, # 打手上传的申诉图片 } @@ -8481,7 +8481,7 @@ class KefuGetDashouListView(APIView): # 4. 计算总数并分页 total = queryset.count() offset = (page - 1) * page_size - dashou_list = queryset.order_by('-create_time')[offset:offset + page_size] + dashou_list = queryset.order_by('-CreateTime')[offset:offset + page_size] # 5. 构建返回数据(只取需要的字段) result = [] @@ -8584,7 +8584,7 @@ class KefuGetDashouDetailView(APIView): 'dianhua': '', 'wechat': '', 'phone': user_main.Phone or '', - 'create_time': user_main.UserCreateTime, + 'CreateTime': user_main.UserCreateTime, 'ip': user_main.IP or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, @@ -8620,7 +8620,7 @@ class KefuGetDashouDetailView(APIView): 'dianhua': dashou.dianhua or '', 'wechat': dashou.wechat or '', 'phone': user_main.Phone or '', - 'create_time': user_main.UserCreateTime, + 'CreateTime': user_main.UserCreateTime, 'ip': user_main.IP or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, @@ -8639,7 +8639,7 @@ class KefuGetDashouDetailView(APIView): # 5. 查询打手会员信息(仅未过期) member_records = Huiyuangoumai.query.filter( yonghu_id=uid - ).order_by('-create_time') + ).order_by('-CreateTime') member_list = [] for record in member_records: @@ -9047,8 +9047,8 @@ class KefuPunishmentListView(APIView): 'qingqiuid': item.ApplicantID, 'cfliyou': item.PenaltyReason, 'sqzhuangtai': item.ApplyStatus, - 'create_time': item.CreateTime, - 'update_time': item.UpdateTime, + 'CreateTime': item.CreateTime, + 'UpdateTime': item.UpdateTime, # 其他字段按需添加 }) @@ -9282,8 +9282,8 @@ class KefuPunishmentDetailView(APIView): 'ssliyou': record.AppealReason or '', 'zhengju_tupian': zhengju_images, 'shensu_tupian': shensu_images, - 'create_time': record.CreateTime, - 'update_time': record.UpdateTime, + 'CreateTime': record.CreateTime, + 'UpdateTime': record.UpdateTime, } return Response({'code': 0, 'data': data}) @@ -9619,7 +9619,7 @@ if not kefu_txsh_list_logger.handlers: _TIXIAN_LIST_BASE_FIELDS = ( 'id', 'yonghuid', 'avatar', 'phone', 'nicheng', 'leixing', 'zhifu', 'skzhanghao', 'jine', 'zhuangtai', 'fangshi', - 'shenheid', 'bhliyou', 'create_time', 'update_time', + 'shenheid', 'bhliyou', 'CreateTime', 'UpdateTime', ) _TIXIAN_LIST_AUDIT_FIELDS = ('shenhe_jilu_id', 'shenhe_danhao') @@ -9773,7 +9773,7 @@ class KefuWithdrawListView(APIView): q &= Q(yonghuid=search_uid) if date_filter: - q &= Q(update_time__date=date_filter) + q &= Q(UpdateTime__date=date_filter) if min_amount is not None and str(min_amount).strip() != '': try: @@ -9821,7 +9821,7 @@ class KefuWithdrawListView(APIView): 'total_amount': str(round(float(total_amount), 2)), } - queryset = base_qs.order_by('create_time') + queryset = base_qs.order_by('CreateTime') total = queryset.count() records = list(queryset[(page - 1) * page_size: page * page_size]) @@ -9865,8 +9865,8 @@ class KefuWithdrawListView(APIView): 'dakuan_mode': self._resolve_row_dakuan_mode(item, audit_mode_map, shenhe_field_ok), 'shenheid': item.shenheid or '', 'bhliyou': item.bhliyou or '', - 'create_time': item.create_time.strftime('%Y-%m-%d %H:%M:%S') if item.create_time else '', - 'update_time': item.update_time.strftime('%Y-%m-%d %H:%M:%S') if item.update_time else '', + 'CreateTime': item.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if item.CreateTime else '', + 'UpdateTime': item.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if item.UpdateTime else '', }) return Response({ @@ -9973,8 +9973,8 @@ class KefuWithdrawDetailView(APIView): 'fangshi': record.fangshi, 'shenheid': record.shenheid or '', 'bhliyou': record.bhliyou or '', - 'create_time': record.create_time, - 'update_time': record.update_time, + 'CreateTime': record.CreateTime, + 'UpdateTime': record.UpdateTime, } # 根据提现类型添加扩展信息 @@ -10145,11 +10145,11 @@ class KefuWithdrawActionView(APIView): refund_balance(user, audit.leixing, audit.shijidaozhang) tixian.shenheid = kefu_user.UserUID tixian.bhliyou = limit_msg - tixian.save(update_fields=['shenheid', 'bhliyou', 'update_time']) + tixian.save(update_fields=['shenheid', 'bhliyou', 'UpdateTime']) raise ValueError(f'提现记录{tixian_id}:{limit_msg},已自动驳回并退款') sync_audit_and_jilu_status(audit, 6, shenhe_ren_id=kefu_user.UserUID) tixian.shenheid = kefu_user.UserUID - tixian.save(update_fields=['shenheid', 'update_time']) + tixian.save(update_fields=['shenheid', 'UpdateTime']) else: sync_audit_and_jilu_status( audit, 5, @@ -10160,7 +10160,7 @@ class KefuWithdrawActionView(APIView): refund_balance(user, audit.leixing, audit.shijidaozhang) tixian.shenheid = kefu_user.UserUID tixian.bhliyou = reason - tixian.save(update_fields=['shenheid', 'bhliyou', 'update_time']) + tixian.save(update_fields=['shenheid', 'bhliyou', 'UpdateTime']) def _process_manual_item(self, item, action, reason, phone): """手动打款单条处理(原逻辑,须在 transaction.atomic 内调用)""" @@ -10195,7 +10195,7 @@ class KefuWithdrawActionView(APIView): szjilu.TotalIncome -= tixian.jine szjilu.TotalExpense += tixian.jine szjilu.DailyExpense += tixian.jine - szjilu.update_time = timezone.now() + szjilu.UpdateTime = timezone.now() szjilu.save() except Exception as e: logger.error(f'更新收支记录失败: {e}') @@ -10208,7 +10208,7 @@ class KefuWithdrawActionView(APIView): tixian.bhliyou = reason tixian.shenheid = phone - tixian.update_time = timezone.now() + tixian.UpdateTime = timezone.now() tixian.save() def post(self, request): @@ -11482,7 +11482,7 @@ class DaShouFaKuanLieBiaoView(APIView): yingxiang_qiangdan, shensuliyou, bohuiliyou, zhengju_tupian: [...], // 证据图片(yongtu=1) shensu_tupian: [...], // 申诉图片(yongtu=2) - create_time, update_time + CreateTime, UpdateTime }], has_more, page, page_size } @@ -11565,8 +11565,8 @@ class DaShouFaKuanLieBiaoView(APIView): 'bohuiliyou': r.RejectReason or '', 'zhengju_tupian': zhengju_map.get(rid, []), # 证据图片 'shensu_tupian': shensu_map.get(rid, []), # 申诉图片 - 'create_time': r.create_time.strftime('%Y-%m-%d %H:%M:%S') if r.create_time else '', - 'update_time': r.update_time.strftime('%Y-%m-%d %H:%M:%S') if r.update_time else '', + 'CreateTime': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '', + 'UpdateTime': r.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if r.UpdateTime else '', } chufa_list.append(item)