Compare commits
200 Commits
2
...
backup/bef
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5be696db9d | ||
|
|
3a1aaa9b61 | ||
|
|
54f8e23488 | ||
|
|
65477592af | ||
|
|
a6d32f848e | ||
|
|
ee7867b36f | ||
|
|
374dd26855 | ||
|
|
d19b6250ae | ||
|
|
e2e568bcda | ||
|
|
318e3e3216 | ||
|
|
93bf2193d2 | ||
|
|
fdecf2fe3c | ||
|
|
2445b39f3b | ||
|
|
8f21491c72 | ||
|
|
c8b67a2fc8 | ||
|
|
3083096cd7 | ||
|
|
cf0882575e | ||
|
|
fc63836cc7 | ||
|
|
992f4f4f18 | ||
|
|
aaf14264d8 | ||
|
|
0b202722e6 | ||
|
|
1aa8c2dc87 | ||
|
|
85531da9eb | ||
|
|
34e79aa753 | ||
|
|
65c5e6572e | ||
|
|
ff5b13e717 | ||
|
|
2b9ea6ccec | ||
|
|
41fa98e8b7 | ||
|
|
8884159edc | ||
|
|
1e5de2061a | ||
|
|
095a8e89e4 | ||
|
|
24949b1cb2 | ||
|
|
141f17859a | ||
|
|
01d73c5fae | ||
|
|
78c5442381 | ||
|
|
550061435e | ||
|
|
5ebcb56b65 | ||
|
|
af2578573c | ||
|
|
f6b99f96a4 | ||
|
|
d25c2dd862 | ||
|
|
a7e51aac3f | ||
|
|
f38f1216ea | ||
|
|
eb1fc6d2ae | ||
|
|
2f0838e552 | ||
|
|
deb1688d0c | ||
|
|
93e68fe20f | ||
|
|
815f344d19 | ||
|
|
a69c008f96 | ||
|
|
8ee7814a45 | ||
|
|
2bd45f8654 | ||
|
|
59f5a695d8 | ||
|
|
9575edcfc4 | ||
|
|
d1dc65e13b | ||
|
|
ff1e1a73dd | ||
|
|
7a2723fef7 | ||
|
|
0c4b28ea45 | ||
|
|
cca53bbc96 | ||
|
|
4714e2259b | ||
|
|
d56c85bca9 | ||
|
|
49482830f9 | ||
|
|
77e1d3f293 | ||
|
|
65613af195 | ||
|
|
8154b2639d | ||
|
|
7f2f0caaa1 | ||
|
|
8e5db24bad | ||
|
|
79f2d4f020 | ||
|
|
8c271f639a | ||
|
|
b1fbc3cd8f | ||
|
|
84665e0c5d | ||
|
|
3b7c539a56 | ||
|
|
600188a99a | ||
|
|
a6068f7e2b | ||
|
|
0827da594c | ||
|
|
a693e35177 | ||
|
|
08fc8f50f3 | ||
|
|
8e5662c56f | ||
|
|
45123d5b6f | ||
|
|
9cd7b6920a | ||
|
|
83519ac67b | ||
|
|
04c54d0733 | ||
|
|
0ac56c5b92 | ||
|
|
48bf50c340 | ||
|
|
467bb71736 | ||
|
|
bfc38c2d43 | ||
|
|
5ae01a9ff2 | ||
|
|
b33bd3eb18 | ||
|
|
c42f979082 | ||
|
|
32486a6e77 | ||
|
|
5c4663c8ec | ||
|
|
a8b6e46f61 | ||
|
|
574fe5e860 | ||
|
|
dcc4936428 | ||
|
|
ce9b09f096 | ||
|
|
98daa93abd | ||
|
|
8dae04c32c | ||
|
|
3f4712bb44 | ||
|
|
6e37477e4f | ||
|
|
bdebbb9348 | ||
|
|
45af7e1649 | ||
|
|
a07bd3d388 | ||
|
|
1ef7c62ab4 | ||
|
|
a1d5a2c3a3 | ||
|
|
24cd2ccea3 | ||
|
|
d19bcb844a | ||
|
|
86b4641558 | ||
|
|
da4bb47ee5 | ||
|
|
4dd3c1bbf0 | ||
|
|
2ce2aad956 | ||
|
|
4f6c06a488 | ||
|
|
7890d0992a | ||
|
|
a30bd69bf2 | ||
|
|
42af45f92c | ||
|
|
d68a1da2c2 | ||
|
|
b2520726dc | ||
|
|
c70b2ee7be | ||
|
|
993c0c54b0 | ||
|
|
4b704d48fa | ||
|
|
470546ae82 | ||
|
|
1d4d054c75 | ||
|
|
92704a86ca | ||
|
|
1ac05494d6 | ||
|
|
6fa0e1638d | ||
|
|
89632228cc | ||
|
|
7dc3064a88 | ||
|
|
0cf5341d73 | ||
|
|
e233ee4bea | ||
|
|
3fecb9390f | ||
|
|
4ded4ea50a | ||
|
|
157ac05632 | ||
|
|
1ce023552c | ||
|
|
e656760441 | ||
|
|
f851dbdefd | ||
|
|
a055131ec7 | ||
| f890369684 | |||
| a7f43a4e74 | |||
| a06202d297 | |||
| 0b3beca147 | |||
| f212e7203e | |||
| 212840d4ab | |||
| 52bd2dfd85 | |||
| c1118fc335 | |||
| 54ee08b3c5 | |||
| ef922b2b90 | |||
| 479fd8bbd8 | |||
| 7f5e76551c | |||
| 33eda43664 | |||
| e64697b64a | |||
| 90e0bde7f1 | |||
| 1a8348cf4b | |||
| ee42d80d93 | |||
| fcd2a5e8a9 | |||
| 8aa36e3764 | |||
| 0ff82d1bac | |||
| 15381c3051 | |||
| e717fc1763 | |||
| df927e141d | |||
| a699037826 | |||
| c3819ccd65 | |||
| 0cd20850e0 | |||
| 4963b06e21 | |||
| ce0fb79b39 | |||
| 46e5a5eeee | |||
| 67872f823c | |||
| c9bc811bc1 | |||
| 7df8a1a1d2 | |||
| 6f6af7553a | |||
| 854767a2c2 | |||
| d71a81f279 | |||
| 5b9436b994 | |||
| d3903c913b | |||
| 38fcfd730c | |||
| e672ac8d3f | |||
| b7f987f2c8 | |||
| f169b6569a | |||
| d93d3b3c6d | |||
| 05498904cf | |||
| 5c8b82df15 | |||
| d769dec2ec | |||
| 5ac9c785f9 | |||
| bb73a492cc | |||
| 5ebded322f | |||
| c093fa46c1 | |||
| c368b6cb73 | |||
| 53049d1588 | |||
|
|
e67bc17fa2 | ||
|
|
16845d0ce2 | ||
|
|
177d082d80 | ||
|
|
d96381fd4e | ||
| 854c832c07 | |||
| 54cc658f0b | |||
| f80ce51859 | |||
| 7ce545fedb | |||
| f9da920e6b | |||
| ca3a109c41 | |||
| 4aca437d2b | |||
| d00f0d08e5 | |||
| 72770ad73b | |||
| 61ddf81fb0 | |||
| 004012b59f | |||
| 461643079b |
@@ -14,6 +14,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
SECRET_KEY = app_secrets.SECRET_KEY
|
||||
DEBUG = False
|
||||
# 详细错误日志开关:True=在 except 块中输出完整堆栈到日志;False=仅记录错误摘要
|
||||
DEBUG_DETAIL_LOG = True
|
||||
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'www.abas.asia']
|
||||
|
||||
# ==================== 应用配置 ====================
|
||||
@@ -38,10 +40,15 @@ INSTALLED_APPS = [
|
||||
'utils',
|
||||
'shop',
|
||||
'rank',
|
||||
'merchant_ops',
|
||||
'jituan',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'utils.ip_filter_middleware.IPFilterMiddleware',
|
||||
'utils.debug_middleware.DebugExceptionMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'jituan.middleware.ClubContextMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
@@ -51,6 +58,45 @@ MIDDLEWARE = [
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
# 日志配置 — 500 错误堆栈输出到 console,受 DEBUG_DETAIL_LOG 开关控制
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '[%(asctime)s] %(levelname)s %(name)s: %(message)s'
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
# 业务模块日志 — DEBUG_DETAIL_LOG=True 时输出 DEBUG 级别(含完整堆栈)
|
||||
'users': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG' if DEBUG_DETAIL_LOG else 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'orders': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG' if DEBUG_DETAIL_LOG else 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'utils.debug_middleware': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ROOT_URLCONF = 'a_long_dianjing.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
@@ -119,8 +165,8 @@ SIMPLE_JWT = {
|
||||
'LEEWAY': 0,
|
||||
'AUTH_HEADER_TYPES': ('Bearer',),
|
||||
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
|
||||
'USER_ID_FIELD': 'yonghuid',
|
||||
'USER_ID_CLAIM': 'yonghuid',
|
||||
'USER_ID_FIELD': 'UserUID',
|
||||
'USER_ID_CLAIM': 'UserUID',
|
||||
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
|
||||
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
|
||||
'TOKEN_TYPE_CLAIM': 'token_type',
|
||||
@@ -132,12 +178,13 @@ AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
]
|
||||
|
||||
AUTH_USER_MODEL = 'users.UserMain'
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
|
||||
# ==================== DRF 配置 ====================
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
'gvsdsdk.auth.jwt_auth.CompatibleJWTAuthentication',
|
||||
'gvsdsdk.auth.elt_auth.ELTAuthentication',
|
||||
),
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
@@ -177,8 +224,28 @@ CORS_ALLOW_METHODS = ("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
|
||||
CORS_ALLOW_HEADERS = [
|
||||
"accept", "accept-encoding", "authorization", "content-type",
|
||||
"dnt", "origin", "user-agent", "x-csrftoken", "x-requested-with",
|
||||
"x-club-id", "x-club-scope",
|
||||
]
|
||||
|
||||
# ==================== IP 访问控制 ====================
|
||||
# 访问模式:
|
||||
# production — 生产模式(默认):放行所有 IP
|
||||
# whitelist — 白名单模式:仅允许白名单内 IP 访问,其余返回 403
|
||||
# blacklist — 黑名单模式:禁止黑名单内 IP 访问,其余放行
|
||||
IP_ACCESS_MODE = 'production'
|
||||
# 白名单(whitelist 模式下生效)— 支持单 IP 和 CIDR 网段
|
||||
IP_WHITELIST = [
|
||||
'111.32.64.171',
|
||||
'43.142.0.208',
|
||||
'223.104.7.77',
|
||||
'112.7.200.17'
|
||||
]
|
||||
# 黑名单(blacklist 模式下生效)— 支持单 IP 和 CIDR 网段
|
||||
IP_BLACKLIST = []
|
||||
|
||||
# 免过滤路径(任何模式下都放行,如管理后台、健康检查)
|
||||
IP_FILTER_EXEMPT_PATHS = []
|
||||
|
||||
# ==================== 腾讯云 COS ====================
|
||||
COS_SECRET_ID = app_secrets.COS_SECRET_ID
|
||||
COS_SECRET_KEY = app_secrets.COS_SECRET_KEY
|
||||
|
||||
@@ -24,6 +24,7 @@ urlpatterns = [
|
||||
path('houtai/', include('backend.urls')),
|
||||
path('shangdian/', include('shop.urls')),
|
||||
path('dengji/', include('rank.urls')),
|
||||
path('jituan/', include('jituan.urls')),
|
||||
]
|
||||
|
||||
# 重要说明:
|
||||
|
||||
@@ -1,296 +1,303 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-06 23:31
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="AbnormalUserLog",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"account_id",
|
||||
models.CharField(
|
||||
db_index=True, max_length=20, verbose_name="账号ID(phone)"
|
||||
),
|
||||
),
|
||||
(
|
||||
"ip_address",
|
||||
models.GenericIPAddressField(
|
||||
blank=True, null=True, verbose_name="IP地址"
|
||||
),
|
||||
),
|
||||
(
|
||||
"real_ip",
|
||||
models.GenericIPAddressField(
|
||||
blank=True, null=True, verbose_name="真实IP(穿透代理后)"
|
||||
),
|
||||
),
|
||||
(
|
||||
"x_forwarded_for",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
default="",
|
||||
max_length=255,
|
||||
verbose_name="X-Forwarded-For",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_agent",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
default="",
|
||||
max_length=500,
|
||||
verbose_name="User-Agent",
|
||||
),
|
||||
),
|
||||
(
|
||||
"request_method",
|
||||
models.CharField(
|
||||
blank=True, default="", max_length=10, verbose_name="请求方法"
|
||||
),
|
||||
),
|
||||
(
|
||||
"request_path",
|
||||
models.CharField(
|
||||
blank=True, default="", max_length=500, 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(
|
||||
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="创建时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "异常用户日志",
|
||||
"verbose_name_plural": "异常用户日志",
|
||||
"db_table": "abnormal_user_log",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Permission",
|
||||
fields=[
|
||||
(
|
||||
"perm_id",
|
||||
models.AutoField(
|
||||
primary_key=True, serialize=False, verbose_name="权限ID"
|
||||
),
|
||||
),
|
||||
(
|
||||
"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="更新时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "权限",
|
||||
"verbose_name_plural": "权限",
|
||||
"db_table": "permission",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Role",
|
||||
fields=[
|
||||
(
|
||||
"role_id",
|
||||
models.AutoField(
|
||||
primary_key=True, serialize=False, verbose_name="角色ID"
|
||||
),
|
||||
),
|
||||
(
|
||||
"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="更新时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "角色",
|
||||
"verbose_name_plural": "角色",
|
||||
"db_table": "role",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="UserRole",
|
||||
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="更新时间"),
|
||||
),
|
||||
(
|
||||
"role",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="backend.role",
|
||||
verbose_name="角色",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "用户角色关联",
|
||||
"verbose_name_plural": "用户角色关联",
|
||||
"db_table": "user_role",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="RolePermission",
|
||||
fields=[
|
||||
("id", models.AutoField(primary_key=True, serialize=False)),
|
||||
(
|
||||
"create_time",
|
||||
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="角色",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "角色权限关联",
|
||||
"verbose_name_plural": "角色权限关联",
|
||||
"db_table": "role_permission",
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="role",
|
||||
index=models.Index(fields=["role_code"], name="role_role_co_b9f497_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="permission",
|
||||
index=models.Index(
|
||||
fields=["perm_code"], name="permission_perm_co_56cbe3_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="abnormaluserlog",
|
||||
index=models.Index(
|
||||
fields=["account_id"], name="abnormal_us_account_425899_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="abnormaluserlog",
|
||||
index=models.Index(
|
||||
fields=["ip_address"], name="abnormal_us_ip_addr_3d612b_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="abnormaluserlog",
|
||||
index=models.Index(
|
||||
fields=["create_time"], name="abnormal_us_create__ee16da_idx"
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="userrole",
|
||||
unique_together={("account_id", "role")},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="rolepermission",
|
||||
unique_together={("role", "permission")},
|
||||
),
|
||||
]
|
||||
# Generated by Django 4.2.27 on 2026-06-18 03:51
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AbnormalUserLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('account_id', models.CharField(db_index=True, max_length=20, verbose_name='账号ID(phone)')),
|
||||
('ip_address', models.GenericIPAddressField(blank=True, null=True, verbose_name='IP地址')),
|
||||
('real_ip', models.GenericIPAddressField(blank=True, null=True, verbose_name='真实IP(穿透代理后)')),
|
||||
('x_forwarded_for', models.CharField(blank=True, default='', max_length=255, verbose_name='X-Forwarded-For')),
|
||||
('user_agent', models.CharField(blank=True, default='', max_length=500, verbose_name='User-Agent')),
|
||||
('request_method', models.CharField(blank=True, default='', max_length=10, verbose_name='请求方法')),
|
||||
('request_path', models.CharField(blank=True, default='', max_length=500, 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(blank=True, default='', max_length=50, verbose_name='攻击类型')),
|
||||
('detail', models.TextField(blank=True, default='', verbose_name='详细描述')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '异常用户日志',
|
||||
'verbose_name_plural': '异常用户日志',
|
||||
'db_table': 'abnormal_user_log',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LeaderDailyStats',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('LeaderID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='组长用户ID')),
|
||||
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||
('InvitedManagerCount', models.IntegerField(db_column='yaoqing_guanshi_shu', default=0, verbose_name='邀请管事总数')),
|
||||
('TotalIncome', models.DecimalField(db_column='shouru_zonge', decimal_places=2, default=0.0, max_digits=12, verbose_name='收入总额(分佣)')),
|
||||
('CommissionAmount', models.DecimalField(db_column='fenyong_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='分佣金额')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '组长每日统计',
|
||||
'db_table': 'zuzhang_ri_tongji',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ManagerDailyStats',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('ManagerID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='管事用户ID')),
|
||||
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||
('InvitedPlayerCount', models.IntegerField(db_column='yaoqing_dashou_shu', default=0, verbose_name='邀请打手总数')),
|
||||
('RechargedPlayerCount', models.IntegerField(db_column='chongzhi_dashou_shu', default=0, verbose_name='充值打手数量')),
|
||||
('TotalIncome', models.DecimalField(db_column='shouru_zonge', decimal_places=2, default=0.0, max_digits=12, verbose_name='收入总额')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '管事每日统计',
|
||||
'db_table': 'guanshi_ri_tongji',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ManagerRenewalDailyStats',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('ManagerID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='管事用户ID')),
|
||||
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||
('RenewalTotal', models.IntegerField(db_column='xufei_zongshu', default=0, verbose_name='今日续费总数')),
|
||||
('RenewalRevenue', models.DecimalField(db_column='xufei_shouyi', decimal_places=2, default=0.0, max_digits=12, verbose_name='续费收益金额')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '管事续费每日统计',
|
||||
'db_table': 'guanshi_xufei_ri_tongji',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MerchantDailyStats',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('MerchantID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='商家用户ID')),
|
||||
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||
('AssignedOrderCount', models.IntegerField(db_column='paifa_dingdan_shu', default=0, verbose_name='派发订单总数')),
|
||||
('AssignedAmount', models.DecimalField(db_column='paifa_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='派发订单总金额')),
|
||||
('SettledOrderCount', models.IntegerField(db_column='jiesuan_dingdan_shu', default=0, verbose_name='结算订单总数')),
|
||||
('SettledAmount', models.DecimalField(db_column='jiesuan_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='结算总金额')),
|
||||
('RefundOrderCount', models.IntegerField(db_column='tuikuan_dingdan_shu', default=0, verbose_name='退款订单总数')),
|
||||
('RefundAmount', models.DecimalField(db_column='tuikuan_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='退款总金额')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商家每日统计',
|
||||
'db_table': 'shangjia_ri_tongji',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Permission',
|
||||
fields=[
|
||||
('perm_id', models.AutoField(primary_key=True, serialize=False, verbose_name='权限ID')),
|
||||
('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='权限描述')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '权限',
|
||||
'verbose_name_plural': '权限',
|
||||
'db_table': 'permission',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PlayerDailyStats',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('PlayerID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='打手用户ID')),
|
||||
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||
('AcceptedOrderTotal', models.IntegerField(db_column='jiedan_zongliang', default=0, verbose_name='接单总量')),
|
||||
('CompletedOrderTotal', models.IntegerField(db_column='chengjiao_zongliang', default=0, verbose_name='成交单总量')),
|
||||
('AcceptedAmount', models.DecimalField(db_column='jiedan_zonge', decimal_places=2, default=0.0, max_digits=12, verbose_name='接单总金额')),
|
||||
('CompletedAmount', models.DecimalField(db_column='chengjiao_zonge', decimal_places=2, default=0.0, max_digits=12, verbose_name='成交总金额')),
|
||||
('RefundCount', models.IntegerField(db_column='tuikuan_liang', default=0, verbose_name='退款订单量')),
|
||||
('RefundAmount', models.DecimalField(db_column='tuikuan_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='退款总金额')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '打手每日统计',
|
||||
'db_table': 'dashou_ri_tongji',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Role',
|
||||
fields=[
|
||||
('role_id', models.AutoField(primary_key=True, serialize=False, verbose_name='角色ID')),
|
||||
('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='角色描述')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '角色',
|
||||
'verbose_name_plural': '角色',
|
||||
'db_table': 'role',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WithdrawalDailyStats',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||
('WithdrawalType', models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '组长'), (4, '考核官'), (5, '打手押金'), (6, '商家余额')], db_column='leixing', verbose_name='用户类型')),
|
||||
('total_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='当天提现总额(元)')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '每日提现统计',
|
||||
'db_table': 'tixian_ri_tongji',
|
||||
'indexes': [models.Index(fields=['Date', 'WithdrawalType'], name='tixian_ri_t_riqi_c18bb2_idx')],
|
||||
'unique_together': {('Date', 'WithdrawalType')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserRole',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('account_id', models.CharField(db_index=True, max_length=11, verbose_name='账号ID')),
|
||||
('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={
|
||||
'verbose_name': '用户角色关联',
|
||||
'verbose_name_plural': '用户角色关联',
|
||||
'db_table': 'user_role',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RolePermission',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('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='角色')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '角色权限关联',
|
||||
'verbose_name_plural': '角色权限关联',
|
||||
'db_table': 'role_permission',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='role',
|
||||
index=models.Index(fields=['role_code'], name='role_role_co_b9f497_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='playerdailystats',
|
||||
index=models.Index(fields=['Date', '-AcceptedOrderTotal'], name='dashou_ri_t_riqi_95a785_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='playerdailystats',
|
||||
index=models.Index(fields=['Date', '-CompletedOrderTotal'], name='dashou_ri_t_riqi_ad9c1a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='playerdailystats',
|
||||
index=models.Index(fields=['Date', '-AcceptedAmount'], name='dashou_ri_t_riqi_5da703_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='playerdailystats',
|
||||
index=models.Index(fields=['Date', '-CompletedAmount'], name='dashou_ri_t_riqi_0a3156_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='playerdailystats',
|
||||
unique_together={('PlayerID', 'Date')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='permission',
|
||||
index=models.Index(fields=['perm_code'], name='permission_perm_co_56cbe3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='merchantdailystats',
|
||||
index=models.Index(fields=['Date', '-AssignedAmount'], name='shangjia_ri_riqi_d00f7f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='merchantdailystats',
|
||||
index=models.Index(fields=['Date', '-SettledAmount'], name='shangjia_ri_riqi_25772a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='merchantdailystats',
|
||||
index=models.Index(fields=['Date', '-AssignedOrderCount'], name='shangjia_ri_riqi_e05c54_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='merchantdailystats',
|
||||
unique_together={('MerchantID', 'Date')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='managerrenewaldailystats',
|
||||
index=models.Index(fields=['Date', '-RenewalTotal'], name='guanshi_xuf_riqi_7c128f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='managerrenewaldailystats',
|
||||
index=models.Index(fields=['Date', '-RenewalRevenue'], name='guanshi_xuf_riqi_8f09b5_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='managerrenewaldailystats',
|
||||
unique_together={('ManagerID', 'Date')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='managerdailystats',
|
||||
index=models.Index(fields=['Date', '-InvitedPlayerCount'], name='guanshi_ri__riqi_075e1a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='managerdailystats',
|
||||
index=models.Index(fields=['Date', '-RechargedPlayerCount'], name='guanshi_ri__riqi_78a79d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='managerdailystats',
|
||||
index=models.Index(fields=['Date', '-TotalIncome'], name='guanshi_ri__riqi_334f52_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='managerdailystats',
|
||||
unique_together={('ManagerID', 'Date')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='leaderdailystats',
|
||||
index=models.Index(fields=['Date', '-InvitedManagerCount'], name='zuzhang_ri__riqi_87169e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='leaderdailystats',
|
||||
index=models.Index(fields=['Date', '-TotalIncome'], name='zuzhang_ri__riqi_1d7e51_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='leaderdailystats',
|
||||
unique_together={('LeaderID', 'Date')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='abnormaluserlog',
|
||||
index=models.Index(fields=['account_id'], name='abnormal_us_account_425899_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='abnormaluserlog',
|
||||
index=models.Index(fields=['ip_address'], name='abnormal_us_ip_addr_3d612b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='abnormaluserlog',
|
||||
index=models.Index(fields=['CreateTime'], name='abnormal_us_create__ee16da_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='userrole',
|
||||
unique_together={('account_id', 'role')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='rolepermission',
|
||||
unique_together={('role', 'permission')},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -1,339 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-09 14:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("backend", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ZuzhangRiTongji",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"yonghuid",
|
||||
models.CharField(
|
||||
db_index=True, max_length=7, verbose_name="组长用户ID"
|
||||
),
|
||||
),
|
||||
("riqi", models.DateField(db_index=True, verbose_name="日期")),
|
||||
(
|
||||
"yaoqing_guanshi_shu",
|
||||
models.IntegerField(default=0, verbose_name="邀请管事总数"),
|
||||
),
|
||||
(
|
||||
"shouru_zonge",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="收入总额(分佣)",
|
||||
),
|
||||
),
|
||||
(
|
||||
"fenyong_jine",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="分佣金额",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "组长每日统计",
|
||||
"db_table": "zuzhang_ri_tongji",
|
||||
"indexes": [
|
||||
models.Index(
|
||||
fields=["riqi", "-yaoqing_guanshi_shu"],
|
||||
name="zuzhang_ri__riqi_87169e_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["riqi", "-shouru_zonge"],
|
||||
name="zuzhang_ri__riqi_1d7e51_idx",
|
||||
),
|
||||
],
|
||||
"unique_together": {("yonghuid", "riqi")},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ShangjiaRiTongji",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"yonghuid",
|
||||
models.CharField(
|
||||
db_index=True, max_length=7, verbose_name="商家用户ID"
|
||||
),
|
||||
),
|
||||
("riqi", models.DateField(db_index=True, verbose_name="日期")),
|
||||
(
|
||||
"paifa_dingdan_shu",
|
||||
models.IntegerField(default=0, verbose_name="派发订单总数"),
|
||||
),
|
||||
(
|
||||
"paifa_jine",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="派发订单总金额",
|
||||
),
|
||||
),
|
||||
(
|
||||
"jiesuan_dingdan_shu",
|
||||
models.IntegerField(default=0, verbose_name="结算订单总数"),
|
||||
),
|
||||
(
|
||||
"jiesuan_jine",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="结算总金额",
|
||||
),
|
||||
),
|
||||
(
|
||||
"tuikuan_dingdan_shu",
|
||||
models.IntegerField(default=0, verbose_name="退款订单总数"),
|
||||
),
|
||||
(
|
||||
"tuikuan_jine",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="退款总金额",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "商家每日统计",
|
||||
"db_table": "shangjia_ri_tongji",
|
||||
"indexes": [
|
||||
models.Index(
|
||||
fields=["riqi", "-paifa_jine"],
|
||||
name="shangjia_ri_riqi_d00f7f_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["riqi", "-jiesuan_jine"],
|
||||
name="shangjia_ri_riqi_25772a_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["riqi", "-paifa_dingdan_shu"],
|
||||
name="shangjia_ri_riqi_e05c54_idx",
|
||||
),
|
||||
],
|
||||
"unique_together": {("yonghuid", "riqi")},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="GuanshiXufeiRiTongji",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"yonghuid",
|
||||
models.CharField(
|
||||
db_index=True, max_length=7, verbose_name="管事用户ID"
|
||||
),
|
||||
),
|
||||
("riqi", models.DateField(db_index=True, verbose_name="日期")),
|
||||
(
|
||||
"xufei_zongshu",
|
||||
models.IntegerField(default=0, verbose_name="今日续费总数"),
|
||||
),
|
||||
(
|
||||
"xufei_shouyi",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="续费收益金额",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "管事续费每日统计",
|
||||
"db_table": "guanshi_xufei_ri_tongji",
|
||||
"indexes": [
|
||||
models.Index(
|
||||
fields=["riqi", "-xufei_zongshu"],
|
||||
name="guanshi_xuf_riqi_7c128f_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["riqi", "-xufei_shouyi"],
|
||||
name="guanshi_xuf_riqi_8f09b5_idx",
|
||||
),
|
||||
],
|
||||
"unique_together": {("yonghuid", "riqi")},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="GuanshiRiTongji",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"yonghuid",
|
||||
models.CharField(
|
||||
db_index=True, max_length=7, verbose_name="管事用户ID"
|
||||
),
|
||||
),
|
||||
("riqi", models.DateField(db_index=True, verbose_name="日期")),
|
||||
(
|
||||
"yaoqing_dashou_shu",
|
||||
models.IntegerField(default=0, verbose_name="邀请打手总数"),
|
||||
),
|
||||
(
|
||||
"chongzhi_dashou_shu",
|
||||
models.IntegerField(default=0, verbose_name="充值打手数量"),
|
||||
),
|
||||
(
|
||||
"shouru_zonge",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="收入总额",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "管事每日统计",
|
||||
"db_table": "guanshi_ri_tongji",
|
||||
"indexes": [
|
||||
models.Index(
|
||||
fields=["riqi", "-yaoqing_dashou_shu"],
|
||||
name="guanshi_ri__riqi_075e1a_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["riqi", "-chongzhi_dashou_shu"],
|
||||
name="guanshi_ri__riqi_78a79d_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["riqi", "-shouru_zonge"],
|
||||
name="guanshi_ri__riqi_334f52_idx",
|
||||
),
|
||||
],
|
||||
"unique_together": {("yonghuid", "riqi")},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="DashouRiTongji",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"yonghuid",
|
||||
models.CharField(
|
||||
db_index=True, max_length=7, verbose_name="打手用户ID"
|
||||
),
|
||||
),
|
||||
("riqi", models.DateField(db_index=True, verbose_name="日期")),
|
||||
(
|
||||
"jiedan_zongliang",
|
||||
models.IntegerField(default=0, verbose_name="接单总量"),
|
||||
),
|
||||
(
|
||||
"chengjiao_zongliang",
|
||||
models.IntegerField(default=0, verbose_name="成交单总量"),
|
||||
),
|
||||
(
|
||||
"jiedan_zonge",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="接单总金额",
|
||||
),
|
||||
),
|
||||
(
|
||||
"chengjiao_zonge",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="成交总金额",
|
||||
),
|
||||
),
|
||||
(
|
||||
"tuikuan_liang",
|
||||
models.IntegerField(default=0, verbose_name="退款订单量"),
|
||||
),
|
||||
(
|
||||
"tuikuan_jine",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="退款总金额",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "打手每日统计",
|
||||
"db_table": "dashou_ri_tongji",
|
||||
"indexes": [
|
||||
models.Index(
|
||||
fields=["riqi", "-jiedan_zongliang"],
|
||||
name="dashou_ri_t_riqi_95a785_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["riqi", "-chengjiao_zongliang"],
|
||||
name="dashou_ri_t_riqi_ad9c1a_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["riqi", "-jiedan_zonge"],
|
||||
name="dashou_ri_t_riqi_5da703_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["riqi", "-chengjiao_zonge"],
|
||||
name="dashou_ri_t_riqi_0a3156_idx",
|
||||
),
|
||||
],
|
||||
"unique_together": {("yonghuid", "riqi")},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,54 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-10 18:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("backend", "0002_zuzhangritongji_shangjiaritongji_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="TixianRiTongji",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("riqi", models.DateField(db_index=True, verbose_name="日期")),
|
||||
(
|
||||
"leixing",
|
||||
models.IntegerField(
|
||||
choices=[(1, "打手"), (2, "管事"), (3, "组长")],
|
||||
verbose_name="用户类型",
|
||||
),
|
||||
),
|
||||
(
|
||||
"total_amount",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=12,
|
||||
verbose_name="当天提现总额(元)",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "每日提现统计",
|
||||
"db_table": "tixian_ri_tongji",
|
||||
"indexes": [
|
||||
models.Index(
|
||||
fields=["riqi", "leixing"], name="tixian_ri_t_riqi_c18bb2_idx"
|
||||
)
|
||||
],
|
||||
"unique_together": {("riqi", "leixing")},
|
||||
},
|
||||
),
|
||||
]
|
||||
40
backend/migrations/0003_withdrawaldailystats_club_id.py
Normal file
40
backend/migrations/0003_withdrawaldailystats_club_id.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from django.db import migrations, models
|
||||
from django.db.models import Max
|
||||
|
||||
|
||||
def _dedupe_withdrawal_daily(apps, schema_editor):
|
||||
Model = apps.get_model('backend', 'WithdrawalDailyStats')
|
||||
for row in Model.objects.values('club_id', 'Date', 'WithdrawalType').annotate(max_id=Max('id')):
|
||||
Model.objects.filter(
|
||||
club_id=row['club_id'],
|
||||
Date=row['Date'],
|
||||
WithdrawalType=row['WithdrawalType'],
|
||||
).exclude(id=row['max_id']).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('backend', '0002_rename_abnormal_us_create__ee16da_idx_abnormal_us_createt_38f96c_idx'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='withdrawaldailystats',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='withdrawaldailystats',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.RunPython(_dedupe_withdrawal_daily, migrations.RunPython.noop),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='withdrawaldailystats',
|
||||
unique_together={('club_id', 'Date', 'WithdrawalType')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='withdrawaldailystats',
|
||||
index=models.Index(fields=['club_id', 'Date', 'WithdrawalType'], name='tixian_ri_club_date_type_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,34 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-06-15 01:42
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('backend', '0003_tixianritongji'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='rolepermission',
|
||||
name='permission',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.permission', verbose_name='权限'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rolepermission',
|
||||
name='role',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.role', verbose_name='角色'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tixianritongji',
|
||||
name='leixing',
|
||||
field=models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '组长'), (4, '考核官'), (5, '打手押金'), (6, '商家余额')], verbose_name='用户类型'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userrole',
|
||||
name='role',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.role', verbose_name='角色'),
|
||||
),
|
||||
]
|
||||
91
backend/migrations/0004_role_daily_club_id.py
Normal file
91
backend/migrations/0004_role_daily_club_id.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from django.db import migrations, models
|
||||
from django.db.models import Max
|
||||
|
||||
|
||||
def _dedupe_role_daily(apps, schema_editor, model_name, user_field):
|
||||
Model = apps.get_model('backend', model_name)
|
||||
for row in Model.objects.values('club_id', user_field, 'Date').annotate(max_id=Max('id')):
|
||||
Model.objects.filter(
|
||||
club_id=row['club_id'],
|
||||
**{user_field: row[user_field], 'Date': row['Date']},
|
||||
).exclude(id=row['max_id']).delete()
|
||||
|
||||
|
||||
def _dedupe_merchant(apps, schema_editor):
|
||||
_dedupe_role_daily(apps, schema_editor, 'MerchantDailyStats', 'MerchantID')
|
||||
|
||||
|
||||
def _dedupe_player(apps, schema_editor):
|
||||
_dedupe_role_daily(apps, schema_editor, 'PlayerDailyStats', 'PlayerID')
|
||||
|
||||
|
||||
def _dedupe_leader(apps, schema_editor):
|
||||
_dedupe_role_daily(apps, schema_editor, 'LeaderDailyStats', 'LeaderID')
|
||||
|
||||
|
||||
def _dedupe_manager(apps, schema_editor):
|
||||
_dedupe_role_daily(apps, schema_editor, 'ManagerDailyStats', 'ManagerID')
|
||||
|
||||
|
||||
def _dedupe_manager_renewal(apps, schema_editor):
|
||||
_dedupe_role_daily(apps, schema_editor, 'ManagerRenewalDailyStats', 'ManagerID')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('backend', '0003_withdrawaldailystats_club_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='merchantdailystats',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='playerdailystats',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='leaderdailystats',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='managerdailystats',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='managerrenewaldailystats',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.RunPython(_dedupe_merchant, migrations.RunPython.noop),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='merchantdailystats',
|
||||
unique_together={('club_id', 'MerchantID', 'Date')},
|
||||
),
|
||||
migrations.RunPython(_dedupe_player, migrations.RunPython.noop),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='playerdailystats',
|
||||
unique_together={('club_id', 'PlayerID', 'Date')},
|
||||
),
|
||||
migrations.RunPython(_dedupe_leader, migrations.RunPython.noop),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='leaderdailystats',
|
||||
unique_together={('club_id', 'LeaderID', 'Date')},
|
||||
),
|
||||
migrations.RunPython(_dedupe_manager, migrations.RunPython.noop),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='managerdailystats',
|
||||
unique_together={('club_id', 'ManagerID', 'Date')},
|
||||
),
|
||||
migrations.RunPython(_dedupe_manager_renewal, migrations.RunPython.noop),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='managerrenewaldailystats',
|
||||
unique_together={('club_id', 'ManagerID', 'Date')},
|
||||
),
|
||||
]
|
||||
@@ -1,271 +1,333 @@
|
||||
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 = '角色'
|
||||
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 = '权限'
|
||||
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 = '角色权限关联'
|
||||
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') # 对应 UserMain 的 phone 字段
|
||||
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 = '用户角色关联'
|
||||
unique_together = [['account_id', 'role']] # 同一用户同一角色只能有一条
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.account_id} -> {self.role.role_name}"
|
||||
|
||||
|
||||
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')
|
||||
|
||||
# 地理位置信息(需借助第三方API,这里只存储请求时的IP,后续可离线解析)
|
||||
# 但我们可以存储更多请求细节
|
||||
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}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ShangjiaRiTongji(QModel):
|
||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='商家用户ID')
|
||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
||||
|
||||
# 派发统计
|
||||
paifa_dingdan_shu = models.IntegerField(default=0, verbose_name='派发订单总数')
|
||||
paifa_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='派发订单总金额')
|
||||
|
||||
# 结算统计(成交)
|
||||
jiesuan_dingdan_shu = models.IntegerField(default=0, verbose_name='结算订单总数')
|
||||
jiesuan_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='结算总金额')
|
||||
|
||||
# 退款统计(可选)
|
||||
tuikuan_dingdan_shu = models.IntegerField(default=0, verbose_name='退款订单总数')
|
||||
tuikuan_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='退款总金额')
|
||||
|
||||
class Meta:
|
||||
db_table = 'shangjia_ri_tongji'
|
||||
verbose_name = '商家每日统计'
|
||||
unique_together = [['yonghuid', 'riqi']] # 每人每天一条记录
|
||||
indexes = [
|
||||
models.Index(fields=['riqi', '-paifa_jine']),
|
||||
models.Index(fields=['riqi', '-jiesuan_jine']),
|
||||
models.Index(fields=['riqi', '-paifa_dingdan_shu']),
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class DashouRiTongji(QModel):
|
||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='打手用户ID')
|
||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
||||
|
||||
# 接单统计
|
||||
jiedan_zongliang = models.IntegerField(default=0, verbose_name='接单总量')
|
||||
chengjiao_zongliang = models.IntegerField(default=0, verbose_name='成交单总量')
|
||||
|
||||
# 金额统计
|
||||
jiedan_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='接单总金额')
|
||||
chengjiao_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='成交总金额')
|
||||
|
||||
# 退款统计
|
||||
tuikuan_liang = models.IntegerField(default=0, verbose_name='退款订单量')
|
||||
tuikuan_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='退款总金额')
|
||||
|
||||
class Meta:
|
||||
db_table = 'dashou_ri_tongji'
|
||||
verbose_name = '打手每日统计'
|
||||
unique_together = [['yonghuid', 'riqi']]
|
||||
indexes = [
|
||||
models.Index(fields=['riqi', '-jiedan_zongliang']),
|
||||
models.Index(fields=['riqi', '-chengjiao_zongliang']),
|
||||
models.Index(fields=['riqi', '-jiedan_zonge']),
|
||||
models.Index(fields=['riqi', '-chengjiao_zonge']),
|
||||
]
|
||||
|
||||
|
||||
|
||||
class ZuzhangRiTongji(QModel):
|
||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='组长用户ID')
|
||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
||||
|
||||
yaoqing_guanshi_shu = models.IntegerField(default=0, verbose_name='邀请管事总数')
|
||||
shouru_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='收入总额(分佣)')
|
||||
|
||||
# 可选明细
|
||||
fenyong_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='分佣金额')
|
||||
|
||||
class Meta:
|
||||
db_table = 'zuzhang_ri_tongji'
|
||||
verbose_name = '组长每日统计'
|
||||
unique_together = [['yonghuid', 'riqi']]
|
||||
indexes = [
|
||||
models.Index(fields=['riqi', '-yaoqing_guanshi_shu']),
|
||||
models.Index(fields=['riqi', '-shouru_zonge']),
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
class GuanshiRiTongji(QModel):
|
||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='管事用户ID')
|
||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
||||
|
||||
yaoqing_dashou_shu = models.IntegerField(default=0, verbose_name='邀请打手总数')
|
||||
chongzhi_dashou_shu = models.IntegerField(default=0, verbose_name='充值打手数量')
|
||||
shouru_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='收入总额')
|
||||
|
||||
class Meta:
|
||||
db_table = 'guanshi_ri_tongji'
|
||||
verbose_name = '管事每日统计'
|
||||
unique_together = [['yonghuid', 'riqi']]
|
||||
indexes = [
|
||||
models.Index(fields=['riqi', '-yaoqing_dashou_shu']),
|
||||
models.Index(fields=['riqi', '-chongzhi_dashou_shu']),
|
||||
models.Index(fields=['riqi', '-shouru_zonge']),
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
class GuanshiXufeiRiTongji(QModel):
|
||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='管事用户ID')
|
||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
||||
|
||||
xufei_zongshu = models.IntegerField(default=0, verbose_name='今日续费总数') # 续费订单数(或续费打手数)
|
||||
xufei_shouyi = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='续费收益金额')
|
||||
|
||||
class Meta:
|
||||
db_table = 'guanshi_xufei_ri_tongji'
|
||||
verbose_name = '管事续费每日统计'
|
||||
unique_together = [['yonghuid', 'riqi']]
|
||||
indexes = [
|
||||
models.Index(fields=['riqi', '-xufei_zongshu']),
|
||||
models.Index(fields=['riqi', '-xufei_shouyi']),
|
||||
]
|
||||
|
||||
|
||||
|
||||
# models.py 中添加
|
||||
|
||||
class TixianRiTongji(QModel):
|
||||
"""
|
||||
每日提现统计表(按身份统计当天提现总额)
|
||||
"""
|
||||
riqi = models.DateField(db_index=True, verbose_name='日期')
|
||||
leixing = models.IntegerField(
|
||||
choices=[(1, '打手'), (2, '管事'), (3, '组长'),
|
||||
(4, '考核官'), (5, '打手押金'), (6, '商家余额')],
|
||||
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 = [['riqi', 'leixing']] # 每天每个身份一条记录
|
||||
indexes = [models.Index(fields=['riqi', 'leixing'])]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.riqi} - {self.get_leixing_display()} 提现总额: {self.total_amount}"
|
||||
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)"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
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 = [['club_id', '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)"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
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 = [['club_id', '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)"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
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 = [['club_id', '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)"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
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 = [['club_id', '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)"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
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 = [['club_id', '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),按俱乐部+身份统计当天提现到账总额"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
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 = [['club_id', 'Date', 'WithdrawalType']]
|
||||
indexes = [
|
||||
models.Index(fields=['club_id', 'Date', 'WithdrawalType']),
|
||||
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
|
||||
|
||||
@@ -32,7 +32,7 @@ class PopupPageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = PopupPage
|
||||
fields = [
|
||||
'id', 'page_key', 'name', 'description',
|
||||
'id', 'club_id', 'page_key', 'name', 'description',
|
||||
'is_active', 'ignore_user_mute',
|
||||
'created_at', 'updated_at', 'popups'
|
||||
]
|
||||
@@ -6,15 +6,15 @@ from .view import (GetRolePermissionView,
|
||||
ModifyAdminUserView, KefuGetDashouListView, KefuGetDashouDetailView, KefuUpdateDashouView, KefuGetShangjiaListView,
|
||||
KefuGetShangjiaDetailView, KefuUpdateShangjiaView, GetProductBaseDataView, GetProductListView, SaveProductOrderView,
|
||||
AddProductView, DeleteProductView, GetProductDetailView, UpdateProductView, UpdateMemberView, GetMemberListView,
|
||||
AddMemberView, GetGuanliListView, GetGuanliDetailView, UpdateGuanliView, GetWithdrawSettingsView,
|
||||
UpdateWithdrawSettingsView, GetZuzhangListView, GetZuzhangDetailView, UpdateZuzhangView, GetProductTypeZoneView,
|
||||
AddMemberView, GetGuanliListView, GetGuanliDetailView, PromoteGuanshiToZuzhangView, ChangeInviterView, UpdateGuanliView, GetWithdrawSettingsView,
|
||||
UpdateWithdrawSettingsView, GetZuzhangListView, GetZuzhangDetailView, UpdateZuzhangView, GetOperationLogListView, GetProductTypeZoneView,
|
||||
ModifyProductTypeZoneView, GetRateView, ModifyRateView, PopupNoticeListAPIView, PopupNoticeModifyAPIView,ShopListView,
|
||||
ShopModifyView, ShopPublicTypeAndAuditView, ShopListForProductView, ShopProductModifyView,
|
||||
ShopProductTypeMappingView, ProductListView, ProductModifyView, KefuChatPermissionsView,
|
||||
FineApplyView, PunishDashouView,
|
||||
FaKuanTongJiView, FaKuanLieBiaoView, FaKuanChuLiView, FaKuanChuangJianView,
|
||||
HqbkxxView,BkxgView,CaiwuView,CwhybkhqView, HybkjtsjView, SzxxView, CwddhqlxView, CwhqjtddsjView,
|
||||
CwqtczhqView, KhpzhqView, ChzsgcView,KhgglView, ShgxgsjView, ZxkfghdsView)
|
||||
CwqtczhqView, KhpzhqView, ChzsgcView,KhgglView, ShgxgsjView, KhjlglView, KhjlczView, ZxkfghdsView)
|
||||
|
||||
urlpatterns = [
|
||||
path('gljshq', GetRolePermissionView.as_view(), name='获取就角色信息'),
|
||||
@@ -46,7 +46,10 @@ urlpatterns = [
|
||||
path('hthqgslb', GetGuanliListView.as_view(), name='后台获取管事列表'),
|
||||
|
||||
path('hthqgsxq', GetGuanliDetailView.as_view(), name='后台获取管事详情信息'),
|
||||
path('htgssjzz', PromoteGuanshiToZuzhangView.as_view(), name='后台管事升级组长'),
|
||||
path('htghyqr', ChangeInviterView.as_view(), name='后台更换邀请人'),
|
||||
path('htxggsxx', UpdateGuanliView.as_view(), name='后台更新管事数据'),
|
||||
path('hqczrz', GetOperationLogListView.as_view(), name='后台操作日志列表'),
|
||||
path('hthqtxpz', GetWithdrawSettingsView.as_view(), name='后台获取提现配置'),
|
||||
path('htxgtxsz', UpdateWithdrawSettingsView.as_view(), name='后台修改提现配置'),
|
||||
path('hthqzzlb', GetZuzhangListView.as_view(), name='后台获取组长列表'),
|
||||
@@ -100,6 +103,8 @@ urlpatterns = [
|
||||
|
||||
path('khggl', KhgglView.as_view(), name='获取考核管信息'),
|
||||
path('shgxgsj', ShgxgsjView.as_view(), name='修改考核管数据'),
|
||||
path('khjlgl', KhjlglView.as_view(), name='考核记录列表与统计'),
|
||||
path('khjlcz', KhjlczView.as_view(), name='考核记录操作'),
|
||||
|
||||
path('zxkfghds', ZxkfghdsView.as_view(), name='链接换打手或重新下单'),
|
||||
|
||||
|
||||
1103
backend/utils.py
1103
backend/utils.py
File diff suppressed because it is too large
Load Diff
17407
backend/view.py
17407
backend/view.py
File diff suppressed because it is too large
Load Diff
27
check_prod_uid.py
Normal file
27
check_prod_uid.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""检查生产库中 UserUID 为空的用户"""
|
||||
import pymysql
|
||||
|
||||
conn = pymysql.connect(
|
||||
host='gvsds.com', port=50030,
|
||||
user='root', password='sajksh.sdfGH3YUge.wjkd+',
|
||||
database='xaio_cheng_xu', charset='utf8mb4'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
# UserUID 为 NULL 的用户数
|
||||
cur.execute("SELECT COUNT(*) FROM user WHERE UserUID IS NULL OR UserUID = ''")
|
||||
print(f'UserUID为空的用户数: {cur.fetchone()[0]}')
|
||||
|
||||
# 总用户数
|
||||
cur.execute("SELECT COUNT(*) FROM user")
|
||||
print(f'总用户数: {cur.fetchone()[0]}')
|
||||
|
||||
# 抽样 UserUID 为 NULL 的用户
|
||||
cur.execute("SELECT UserUUID, UserName, OpenID, Phone FROM user WHERE UserUID IS NULL OR UserUID = '' LIMIT 5")
|
||||
rows = cur.fetchall()
|
||||
if rows:
|
||||
print('\nUserUID为空的用户示例:')
|
||||
for r in rows:
|
||||
print(f' UserName={r[1]}, OpenID={r[2]}, Phone={r[3]}')
|
||||
|
||||
conn.close()
|
||||
114
config/management/commands/fix_income_month.py
Normal file
114
config/management/commands/fix_income_month.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
修复 repair_wechat_income 搞乱的收入(200 显示成 600+)。
|
||||
|
||||
两种模式(先预览,再加 --yes):
|
||||
|
||||
A. 默认:重扫本月真实微信支付,删掉脏数据后重建
|
||||
python manage.py fix_income_month
|
||||
python manage.py fix_income_month --yes
|
||||
|
||||
B. 若日志金额已对、只是日统计表脏了:
|
||||
python manage.py fix_income_month --logs-only --yes
|
||||
"""
|
||||
from calendar import monthrange
|
||||
from datetime import date
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Sum
|
||||
|
||||
from config.models import DailyIncomeStat, PlatformIncomeLog
|
||||
from jituan.services.szjilu_accounting import (
|
||||
align_daily_income_from_logs_only,
|
||||
dedupe_platform_income_logs,
|
||||
fix_income_period,
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '修复本月收入统计错乱(repair 重复记账)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--year', type=int, default=0, help='年,默认今年')
|
||||
parser.add_argument('--month', type=int, default=0, help='月,默认本月')
|
||||
parser.add_argument('--today-only', action='store_true', help='只修今天')
|
||||
parser.add_argument('--logs-only', action='store_true', help='只按现有日志重写日统计,不重扫订单')
|
||||
parser.add_argument('--yes', action='store_true', help='确认执行')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
today = date.today()
|
||||
year = options['year'] or today.year
|
||||
month = options['month'] or today.month
|
||||
dry_run = not options['yes']
|
||||
|
||||
if options['today_only']:
|
||||
start = end = today
|
||||
label = f'今日 {today}'
|
||||
else:
|
||||
_, last = monthrange(year, month)
|
||||
start = date(year, month, 1)
|
||||
end = date(year, month, last)
|
||||
label = f'{year}-{month:02d} 全月'
|
||||
|
||||
if options['logs_only']:
|
||||
self._run_logs_only(dry_run, label)
|
||||
return
|
||||
|
||||
self.stdout.write(self.style.WARNING(f'【{label}】收入修复\n'))
|
||||
if dry_run:
|
||||
self.stdout.write('预览模式(未改库)\n')
|
||||
|
||||
r = fix_income_period(start, end, dry_run=dry_run)
|
||||
self.stdout.write(f' 错乱日统计合计: {r["before_daily_amount"]:.2f} 元 / {r["before_daily_count"]} 笔')
|
||||
self.stdout.write(f' 错乱日志合计: {r["before_log_amount"]:.2f} 元 / {r["before_log_count"]} 笔')
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f' 重建后(严格): {r["after_amount"]:.2f} 元 / {r["after_count"]} 笔'
|
||||
))
|
||||
|
||||
if r['items']:
|
||||
self.stdout.write(' 明细:')
|
||||
for row in r['items']:
|
||||
if len(row) == 5:
|
||||
d, ref, cid, amt, kind = row
|
||||
self.stdout.write(f' {d} {ref} {amt}元 club={cid} ({kind})')
|
||||
else:
|
||||
ref, cid, amt, kind = row
|
||||
self.stdout.write(f' {ref} {amt}元 club={cid} ({kind})')
|
||||
|
||||
if not dry_run:
|
||||
self._print_today_total(today)
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'\n重建后金额对 → python manage.py fix_income_month --yes\n'
|
||||
'只修今天 → python manage.py fix_income_month --today-only --yes\n'
|
||||
'日志已对、日统计脏 → python manage.py fix_income_month --logs-only --yes'
|
||||
))
|
||||
|
||||
def _run_logs_only(self, dry_run, label):
|
||||
try:
|
||||
log_sum = PlatformIncomeLog.objects.aggregate(t=Sum('amount'))['t'] or 0
|
||||
log_cnt = PlatformIncomeLog.objects.count()
|
||||
daily_sum = DailyIncomeStat.objects.aggregate(t=Sum('total_amount'))['t'] or 0
|
||||
except Exception as exc:
|
||||
self.stderr.write(f'读取失败: {exc}')
|
||||
return
|
||||
|
||||
self.stdout.write(f'【{label}】仅按日志对齐日统计\n')
|
||||
self.stdout.write(f' platform_income_log 合计: {float(log_sum):.2f} 元 / {log_cnt} 笔')
|
||||
self.stdout.write(f' daily_income_stat 现合计: {float(daily_sum):.2f} 元')
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('\n预览。确认后: python manage.py fix_income_month --logs-only --yes'))
|
||||
return
|
||||
|
||||
n = align_daily_income_from_logs_only()
|
||||
self.stdout.write(self.style.SUCCESS(f'已重写 daily_income_stat {n} 行,并重算 szjilu'))
|
||||
self._print_today_total(date.today())
|
||||
|
||||
def _print_today_total(self, today):
|
||||
agg = DailyIncomeStat.objects.filter(date=today).aggregate(
|
||||
amt=Sum('total_amount'), cnt=Sum('total_count'),
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'\n数据库今日日统计: {float(agg["amt"] or 0):.2f} 元 / {int(agg["cnt"] or 0)} 笔'
|
||||
))
|
||||
self.stdout.write('请刷新后台财务页核对。')
|
||||
29
config/management/commands/fix_today_income_count.py
Normal file
29
config/management/commands/fix_today_income_count.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""只修正今日 daily_income_stat.total_count(不动金额、不动其它天)。"""
|
||||
from datetime import date
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from jituan.services.szjilu_accounting import fix_income_stat_count_for_date
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '修正今日收入笔数(repair 会把 total_count 虚增,本命令只改笔数列)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--date', type=str, default='', help='YYYY-MM-DD,默认今天')
|
||||
parser.add_argument('--yes', action='store_true')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
stat_date = date.fromisoformat(options['date']) if options['date'] else date.today()
|
||||
dry_run = not options['yes']
|
||||
r = fix_income_stat_count_for_date(stat_date, dry_run=dry_run)
|
||||
self.stdout.write(f'日期: {r["date"]}')
|
||||
self.stdout.write(f' 表内笔数合计(错): {r["before_count"]}')
|
||||
self.stdout.write(self.style.SUCCESS(f' 修正后笔数: {r["after_count"]}'))
|
||||
if r['clubs']:
|
||||
for cid, cnt in r['clubs'].items():
|
||||
self.stdout.write(f' club={cid}: {cnt} 笔')
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('\n确认: python manage.py fix_today_income_count --yes'))
|
||||
else:
|
||||
self.stdout.write(self.style.SUCCESS('\n已写入 daily_income_stat.total_count,请刷新财务页。'))
|
||||
70
config/management/commands/rebuild_income_stats.py
Normal file
70
config/management/commands/rebuild_income_stats.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
【终极恢复】清空全部收支脏数据,按真实微信支付逐笔重建。
|
||||
|
||||
只执行这一条,不要再用 repair / undo / fix:
|
||||
|
||||
预览:python manage.py rebuild_income_stats
|
||||
执行:python manage.py rebuild_income_stats --yes
|
||||
"""
|
||||
from datetime import date
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Sum
|
||||
|
||||
from config.models import DailyIncomeStat
|
||||
from jituan.services.szjilu_accounting import rebuild_income_stats_from_scratch
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '清空收支脏数据,按真实微信支付逐笔重建(终极恢复)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--since-year', type=int, default=0, help='从哪一年扫,默认去年')
|
||||
parser.add_argument('--until-year', type=int, default=0, help='到哪一年,默认今年')
|
||||
parser.add_argument('--yes', action='store_true', help='确认执行')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
until_year = options['until_year'] or date.today().year
|
||||
since_year = options['since_year'] or (until_year - 1)
|
||||
dry_run = not options['yes']
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('【预览】未改数据库\n'))
|
||||
else:
|
||||
self.stdout.write(self.style.ERROR(
|
||||
'【执行】删除全部收入日志和日统计,按真实支付重建...\n'
|
||||
))
|
||||
|
||||
r = rebuild_income_stats_from_scratch(
|
||||
since_year=since_year, until_year=until_year, dry_run=dry_run,
|
||||
)
|
||||
|
||||
self.stdout.write(f'=== {since_year} ~ {until_year} ===')
|
||||
self.stdout.write(f' 错乱前 daily 合计: {r["before_daily_total"]:.2f} 元')
|
||||
self.stdout.write(f' 错乱前 log: {r["before_log_total"]:.2f} 元 / {r["before_log_count"]} 条')
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f' 重建后合计: {r["after_total"]:.2f} 元 / {r["after_count"]} 笔'
|
||||
))
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f' 重建后本月: {r["month_amount"]:.2f} 元'
|
||||
))
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f' 重建后今日: {r["today_amount"]:.2f} 元 / {r["today_count"]} 笔 ← 看这条'
|
||||
))
|
||||
|
||||
if not dry_run:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'\ndaily 表合计: {r.get("after_daily_total", 0):.2f} 元'
|
||||
))
|
||||
agg = DailyIncomeStat.objects.filter(date=date.today()).aggregate(
|
||||
amt=Sum('total_amount'), cnt=Sum('total_count'),
|
||||
)
|
||||
self.stdout.write(
|
||||
f'今日 daily 行合计: {float(agg["amt"] or 0):.2f} 元 / {int(agg["cnt"] or 0)} 笔'
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS('\n完成。刷新后台财务页。'))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
f'\n今日 {r["today_amount"]:.2f} 元对的话:\n'
|
||||
f' python manage.py rebuild_income_stats --yes'
|
||||
))
|
||||
104
config/management/commands/recover_config_0006.py
Normal file
104
config/management/commands/recover_config_0006.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""修复 config.0006 半路失败:去重 + 补唯一约束/索引,然后 fake 0006。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection
|
||||
from django.db.models import Max
|
||||
|
||||
from config.models import PopupPage, Tupianpeizhi
|
||||
|
||||
|
||||
def _table_indexes(table):
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f'SHOW INDEX FROM `{table}`')
|
||||
rows = cursor.fetchall()
|
||||
# Key_name, Column_name, Non_unique
|
||||
by_key = {}
|
||||
for row in rows:
|
||||
key_name = row[2]
|
||||
col = row[4]
|
||||
non_unique = row[1]
|
||||
by_key.setdefault(key_name, {'cols': [], 'unique': non_unique == 0})
|
||||
by_key[key_name]['cols'].append(col)
|
||||
return by_key
|
||||
|
||||
|
||||
def _has_unique_on(table, columns):
|
||||
cols = list(columns)
|
||||
for info in _table_indexes(table).values():
|
||||
if info['unique'] and info['cols'] == cols:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _drop_index(table, key_name):
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f'ALTER TABLE `{table}` DROP INDEX `{key_name}`')
|
||||
|
||||
|
||||
def _add_unique(table, name, columns):
|
||||
cols = ', '.join(f'`{c}`' for c in columns)
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f'ALTER TABLE `{table}` ADD UNIQUE `{name}` ({cols})')
|
||||
|
||||
|
||||
def _add_index(table, name, columns):
|
||||
cols = ', '.join(f'`{c}`' for c in columns)
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f'CREATE INDEX `{name}` ON `{table}` ({cols})')
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'config.0006 半路失败时:清重复数据、补约束,再执行 migrate config 0006 --fake'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write('1) 清理重复数据…')
|
||||
|
||||
for row in Tupianpeizhi.objects.values('club_id', 'ImageType').annotate(max_id=Max('id')):
|
||||
n = Tupianpeizhi.objects.filter(
|
||||
club_id=row['club_id'],
|
||||
ImageType=row['ImageType'],
|
||||
).exclude(id=row['max_id']).delete()[0]
|
||||
if n:
|
||||
self.stdout.write(f' tupianpeizhi 删除 {n} 条重复')
|
||||
|
||||
for row in PopupPage.objects.values('club_id', 'page_key').annotate(max_id=Max('id')):
|
||||
n = PopupPage.objects.filter(
|
||||
club_id=row['club_id'],
|
||||
page_key=row['page_key'],
|
||||
).exclude(id=row['max_id']).delete()[0]
|
||||
if n:
|
||||
self.stdout.write(f' popup_page 删除 {n} 条重复')
|
||||
|
||||
self.stdout.write('2) 检查并补约束/索引…')
|
||||
|
||||
# popup_page:去掉 page_key 单独唯一,改为 (club_id, page_key)
|
||||
popup_indexes = _table_indexes('popup_page')
|
||||
for key_name, info in popup_indexes.items():
|
||||
if info['unique'] and info['cols'] == ['page_key']:
|
||||
self.stdout.write(f' 删除 popup_page 旧唯一索引 {key_name}')
|
||||
_drop_index('popup_page', key_name)
|
||||
|
||||
if not _has_unique_on('popup_page', ['club_id', 'page_key']):
|
||||
self.stdout.write(' 添加 popup_page UNIQUE (club_id, page_key)')
|
||||
_add_unique('popup_page', 'popup_page_club_page_key_uniq', ['club_id', 'page_key'])
|
||||
else:
|
||||
self.stdout.write(' popup_page UNIQUE (club_id, page_key) 已存在')
|
||||
|
||||
if not _has_unique_on('tupianpeizhi', ['club_id', 'ImageType']):
|
||||
self.stdout.write(' 添加 tupianpeizhi UNIQUE (club_id, ImageType)')
|
||||
_add_unique('tupianpeizhi', 'tupianpeizhi_club_id_ImageType_uniq', ['club_id', 'ImageType'])
|
||||
else:
|
||||
self.stdout.write(' tupianpeizhi UNIQUE (club_id, ImageType) 已存在')
|
||||
|
||||
lunbo_indexes = _table_indexes('lunbo')
|
||||
if 'lunbo_club_page_type_idx' not in lunbo_indexes:
|
||||
self.stdout.write(' 添加 lunbo 索引 lunbo_club_page_type_idx')
|
||||
_add_index('lunbo', 'lunbo_club_page_type_idx', ['club_id', 'page_key', 'ImageType'])
|
||||
else:
|
||||
self.stdout.write(' lunbo 索引 lunbo_club_page_type_idx 已存在')
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
'\n完成。请执行:\n'
|
||||
' python manage.py migrate config 0006 --fake\n'
|
||||
' python manage.py migrate config\n'
|
||||
' python manage.py migrate\n'
|
||||
))
|
||||
92
config/management/commands/recover_wechat_income_stats.py
Normal file
92
config/management/commands/recover_wechat_income_stats.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
恢复被 repair_wechat_income 搞乱的收入统计。
|
||||
|
||||
repair 会对已入账订单重复加钱(200 变 900)。
|
||||
本命令:删当日错误日志 → 按真实微信支付重建 → 扣回 szjilu 多记部分。
|
||||
|
||||
用法:
|
||||
1. 先预览:python manage.py recover_wechat_income_stats
|
||||
2. 看「重建后」金额是否对(应接近真实收入,如 200 左右)
|
||||
3. 确认后:python manage.py recover_wechat_income_stats --yes
|
||||
4. 若多日错乱:python manage.py recover_wechat_income_stats --days 7 --yes
|
||||
5. 最后全量对齐日统计表:python manage.py recover_wechat_income_stats --yes --rebuild-all-daily
|
||||
"""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Sum
|
||||
|
||||
from config.models import DailyIncomeStat
|
||||
from jituan.services.szjilu_accounting import (
|
||||
rebuild_all_daily_income_from_logs,
|
||||
recover_income_stats_for_date,
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '恢复 repair_wechat_income 重复记账(先预览,--yes 执行)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--date', type=str, default='', help='日期 YYYY-MM-DD,默认今天')
|
||||
parser.add_argument('--days', type=int, default=1, help='向前恢复几天')
|
||||
parser.add_argument('--yes', action='store_true', help='确认执行')
|
||||
parser.add_argument(
|
||||
'--rebuild-all-daily',
|
||||
action='store_true',
|
||||
help='恢复后按 platform_income_log 全量重写 daily_income_stat(纠正累计总收入)',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if options['date']:
|
||||
end_day = date.fromisoformat(options['date'])
|
||||
else:
|
||||
end_day = date.today()
|
||||
days = max(1, int(options['days'] or 1))
|
||||
start_day = end_day - timedelta(days=days - 1)
|
||||
dry_run = not options['yes']
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'【预览】未改数据库。下面「重建后」应是正确金额。\n'
|
||||
))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING('【执行】正在恢复...\n'))
|
||||
|
||||
for i in range(days):
|
||||
d = start_day + timedelta(days=i)
|
||||
r = recover_income_stats_for_date(d, dry_run=dry_run)
|
||||
self.stdout.write(f'=== {r["date"]} ===')
|
||||
self.stdout.write(
|
||||
f' 错乱前日统计: {r["before_daily_amount"]:.2f} 元 / {r["before_daily_count"]} 笔'
|
||||
)
|
||||
self.stdout.write(
|
||||
f' 当日幂等日志: {r["before_log_amount"]:.2f} 元 / {r["before_log_count"]} 笔'
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f' 重建后(正确值): {r["after_amount"]:.2f} 元 / {r["after_count"]} 笔'
|
||||
))
|
||||
if r['items']:
|
||||
self.stdout.write(' 明细:')
|
||||
for ref, cid, amt, kind in r['items']:
|
||||
self.stdout.write(f' {ref} {amt}元 club={cid} ({kind})')
|
||||
|
||||
if not dry_run and options['rebuild_all_daily']:
|
||||
n = rebuild_all_daily_income_from_logs()
|
||||
self.stdout.write(self.style.SUCCESS(f'已按日志全量重写 daily_income_stat: {n} 行'))
|
||||
|
||||
if not dry_run:
|
||||
agg = DailyIncomeStat.objects.filter(date=end_day).aggregate(
|
||||
amt=Sum('total_amount'),
|
||||
cnt=Sum('total_count'),
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'\n数据库今日日统计现为: {float(agg["amt"] or 0):.2f} 元 / {int(agg["cnt"] or 0)} 笔'
|
||||
))
|
||||
self.stdout.write(self.style.SUCCESS('恢复完成,请刷新后台财务页。'))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'\n预览完成。重建后金额对的话执行:\n'
|
||||
' python manage.py recover_wechat_income_stats --yes\n'
|
||||
'累计总收入仍不对再加:\n'
|
||||
' python manage.py recover_wechat_income_stats --yes --rebuild-all-daily'
|
||||
))
|
||||
15
config/management/commands/repair_wechat_income.py
Normal file
15
config/management/commands/repair_wechat_income.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""【已废弃】请用 sync_daily_income_from_log,本命令曾导致重复记账。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '已废弃:勿使用。请改用 python manage.py sync_daily_income_from_log --reset-day'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stderr.write(self.style.ERROR(
|
||||
'repair_wechat_income 已禁用(会对已入账订单重复加钱)。\n'
|
||||
'纠正今日虚高请执行:\n'
|
||||
' python manage.py sync_daily_income_from_log --reset-day\n'
|
||||
'可选同时重算累计:\n'
|
||||
' python manage.py sync_daily_income_from_log --reset-day --sync-szjilu'
|
||||
))
|
||||
48
config/management/commands/repopulate_today_income.py
Normal file
48
config/management/commands/repopulate_today_income.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""只恢复今日收入(修复后台四个板子显示 0)。"""
|
||||
from datetime import date
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Sum
|
||||
|
||||
from config.models import DailyIncomeStat
|
||||
from jituan.services.szjilu_accounting import repopulate_today_income_stats
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '重建今日 daily_income_stat(修复财务页今日收入为 0)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--date', type=str, default='', help='日期 YYYY-MM-DD,默认今天')
|
||||
parser.add_argument('--yes', action='store_true', help='确认执行')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
stat_date = date.fromisoformat(options['date']) if options['date'] else date.today()
|
||||
dry_run = not options['yes']
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('【预览】\n'))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING('【执行】重建今日收入...\n'))
|
||||
|
||||
r = repopulate_today_income_stats(stat_date, dry_run=dry_run)
|
||||
self.stdout.write(f'日期: {r["date"]}')
|
||||
self.stdout.write(f' 当前: {r["before_amount"]:.2f} 元 / {r["before_count"]} 笔')
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f' 重建后: {r["after_amount"]:.2f} 元 / {r["after_count"]} 笔'
|
||||
))
|
||||
if r['items']:
|
||||
for ref, cid, amt, kind in r['items']:
|
||||
self.stdout.write(f' {ref} {amt}元 club={cid} ({kind})')
|
||||
|
||||
if not dry_run:
|
||||
agg = DailyIncomeStat.objects.filter(date=stat_date).aggregate(
|
||||
amt=Sum('total_amount'), cnt=Sum('total_count'),
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'\n完成。今日日统计: {float(agg["amt"] or 0):.2f} 元 / {int(agg["cnt"] or 0)} 笔'
|
||||
))
|
||||
self.stdout.write('请刷新后台财务页。')
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'\n确认后: python manage.py repopulate_today_income --yes'
|
||||
))
|
||||
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
从订单/充值表恢复 daily_income_stat(不删订单,只重算统计表)。
|
||||
repair/rebuild 搞乱后用这个,不要用 repair_wechat_income。
|
||||
|
||||
预览:python manage.py restore_daily_income_from_orders
|
||||
执行:python manage.py restore_daily_income_from_orders --yes
|
||||
"""
|
||||
from datetime import date
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from jituan.services.szjilu_accounting import restore_daily_income_from_orders
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '从订单/充值表恢复日收入统计(订单不动)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--since-year', type=int, default=0)
|
||||
parser.add_argument('--until-year', type=int, default=0)
|
||||
parser.add_argument('--yes', action='store_true')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
until_year = options['until_year'] or date.today().year
|
||||
since_year = options['since_year'] or (until_year - 1)
|
||||
dry_run = not options['yes']
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('【预览】订单数据不会动\n'))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING('【执行】按订单/充值重算日统计...\n'))
|
||||
|
||||
r = restore_daily_income_from_orders(since_year, until_year, dry_run=dry_run)
|
||||
self.stdout.write(f'范围: {r["since_year"]} ~ {r["until_year"]}')
|
||||
self.stdout.write(f' 支付笔数(订单表): {r["payment_rows"]}')
|
||||
self.stdout.write(f' 日统计行数: {r["daily_rows"]}')
|
||||
self.stdout.write(f' 当前 daily 合计: {r["before_daily_total"]:.2f} 元')
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f' 恢复后 daily 合计: {r["after_daily_total"]:.2f} 元'
|
||||
))
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'\n确认: python manage.py restore_daily_income_from_orders --yes'
|
||||
))
|
||||
else:
|
||||
self.stdout.write(self.style.SUCCESS('\n完成。推 kefu 后刷新财务页。'))
|
||||
68
config/management/commands/restore_finance_stats.py
Normal file
68
config/management/commands/restore_finance_stats.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
恢复 daily_income_stat(每日收入统计表)全表数据。
|
||||
|
||||
repair_wechat_income 对该表的破坏:
|
||||
- 把扫描到的每笔订单/充值重复写入 daily_income_stat
|
||||
- 金额记到「执行命令那天」,不是真实支付日
|
||||
- 含退款单(Status=5)也被当成收入加进去
|
||||
|
||||
本命令按 platform_income_log + 无日志的已支付单,重建整张 daily_income_stat。
|
||||
不碰 daily_payout_stat,不碰 szjilu,不删订单。
|
||||
|
||||
预览:python manage.py restore_finance_stats
|
||||
执行:python manage.py restore_finance_stats --yes
|
||||
"""
|
||||
from datetime import date
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Sum
|
||||
|
||||
from config.models import DailyIncomeStat, DailyPayoutStat
|
||||
from jituan.services.szjilu_accounting import rebuild_daily_income_stat_table
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '恢复 daily_income_stat 全表(财务四个板子读这张表)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--yes', action='store_true', help='确认写入数据库')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry_run = not options['yes']
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('【预览】未改数据库\n'))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING('【执行】重建 daily_income_stat...\n'))
|
||||
|
||||
r = rebuild_daily_income_stat_table(dry_run=dry_run)
|
||||
|
||||
self.stdout.write(f' 支付笔数(去重): {r["payment_rows"]}')
|
||||
self.stdout.write(f' 其中日志: {r["from_log"]}')
|
||||
self.stdout.write(f' 补漏订单: {r["gap_orders"]}')
|
||||
self.stdout.write(f' 补漏充值: {r["gap_cz"]}')
|
||||
self.stdout.write(f' 日统计行数: {r["daily_rows"]}')
|
||||
self.stdout.write(f' 恢复前 daily 合计: {r["before_daily_total"]:.2f} 元')
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f' 恢复后 daily 合计: {r["after_daily_total"]:.2f} 元'
|
||||
))
|
||||
self.stdout.write(f' 其中今日收入: {r["today_amount"]:.2f} 元 / {r["today_count"]} 笔')
|
||||
|
||||
payout_today = DailyPayoutStat.objects.filter(date=date.today()).aggregate(
|
||||
t=Sum('total_amount'),
|
||||
)['t'] or 0
|
||||
self.stdout.write(
|
||||
f' (未动)今日支出表: {float(payout_today):.2f} 元'
|
||||
)
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'\n预览数字合理后执行:\n'
|
||||
' python manage.py restore_finance_stats --yes\n'
|
||||
'然后 push 代码并刷新财务页。'
|
||||
))
|
||||
else:
|
||||
check = DailyIncomeStat.objects.aggregate(t=Sum('total_amount'))['t'] or 0
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'\n完成。数据库 daily_income_stat 合计: {float(check):.2f} 元'
|
||||
))
|
||||
68
config/management/commands/sync_daily_income_from_log.py
Normal file
68
config/management/commands/sync_daily_income_from_log.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
用 platform_income_log 重写 daily_income_stat,纠正重复记账后的虚高数字。
|
||||
权威数据源是 platform_income_log(每笔支付一条,biz_ref 唯一),不是扫订单重算。
|
||||
"""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from jituan.services.szjilu_accounting import (
|
||||
sync_daily_income_stat_for_date,
|
||||
sync_szjilu_income_from_logs,
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = (
|
||||
'按 platform_income_log 同步 daily_income_stat(纠正今日收入虚高/笔数不准)。'
|
||||
'不要用 repair_wechat_income,那个会重复加钱。'
|
||||
)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--date',
|
||||
type=str,
|
||||
default='',
|
||||
help='同步日期 YYYY-MM-DD,默认今天',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--days',
|
||||
type=int,
|
||||
default=1,
|
||||
help='从指定日期起向前同步几天,默认 1',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--reset-day',
|
||||
action='store_true',
|
||||
help='同步前先删除该日全部日统计行,再按日志重建(纠正重复记账必选)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sync-szjilu',
|
||||
action='store_true',
|
||||
help='同时按日志全量重算 szjilu 累计收入/今日流水',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if options['date']:
|
||||
end_day = date.fromisoformat(options['date'])
|
||||
else:
|
||||
end_day = date.today()
|
||||
days = max(1, int(options['days'] or 1))
|
||||
start_day = end_day - timedelta(days=days - 1)
|
||||
reset_day = bool(options['reset_day'])
|
||||
|
||||
total = 0
|
||||
for i in range(days):
|
||||
d = start_day + timedelta(days=i)
|
||||
n = sync_daily_income_stat_for_date(d, reset_day=reset_day)
|
||||
total += n
|
||||
self.stdout.write(f'{d}: 已同步 {n} 个俱乐部日统计行')
|
||||
|
||||
if options['sync_szjilu']:
|
||||
sz = sync_szjilu_income_from_logs()
|
||||
self.stdout.write(f'szjilu: 已重算 {sz} 个俱乐部累计收入')
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'完成。今日收入/笔数现与 platform_income_log 一致。'
|
||||
f'若仍有漏记支付,请确认 migrate config 后等新支付自动入账。'
|
||||
))
|
||||
133
config/management/commands/undo_repair_wechat_income.py
Normal file
133
config/management/commands/undo_repair_wechat_income.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
【反向撤销】repair_wechat_income 造成的重复记账。
|
||||
|
||||
你当时执行的指令是:
|
||||
python manage.py repair_wechat_income
|
||||
或:
|
||||
python manage.py repair_wechat_income --date 2026-06-24 --days 7
|
||||
|
||||
repair 逻辑:按「当日创建的已支付订单/充值单」逐条 +金额、+笔数、写 platform_income_log、加 szjilu。
|
||||
本命令用完全相同扫描列表,逐条反向 -金额、-笔数、删日志、减 szjilu。
|
||||
|
||||
用法:
|
||||
1. 预览(不改库):
|
||||
python manage.py undo_repair_wechat_income
|
||||
|
||||
2. 确认后撤销(加多少减多少):
|
||||
python manage.py undo_repair_wechat_income --yes
|
||||
|
||||
3. 若当时带了参数,必须一致:
|
||||
python manage.py undo_repair_wechat_income --date 2026-06-01 --days 30 --run-date 2026-06-24 --yes
|
||||
(--run-date = 你执行 repair 那天,默认今天)
|
||||
|
||||
4. 全部清零(恢复到 0):
|
||||
python manage.py undo_repair_wechat_income --zero-all --yes
|
||||
"""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Sum
|
||||
|
||||
from config.models import DailyIncomeStat
|
||||
from jituan.services.szjilu_accounting import (
|
||||
undo_repair_wechat_income_period,
|
||||
zero_all_income_statistics,
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '反向撤销 repair_wechat_income 造成的收支重复记账'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--date', type=str, default='',
|
||||
help='与 repair 相同的 --date(扫描区间结束日),默认今天',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--days', type=int, default=1,
|
||||
help='与 repair 相同的 --days,默认 1',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--run-date', type=str, default='',
|
||||
help='你执行 repair 的日期(金额被记到哪天的 daily),默认今天',
|
||||
)
|
||||
parser.add_argument('--yes', action='store_true', help='确认执行')
|
||||
parser.add_argument(
|
||||
'--zero-all', action='store_true',
|
||||
help='收支统计全部清零(日收入、日志、szjilu 收入流水归零)',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if options['zero_all']:
|
||||
self._zero_all(not options['yes'])
|
||||
return
|
||||
|
||||
if options['date']:
|
||||
end_day = date.fromisoformat(options['date'])
|
||||
else:
|
||||
end_day = date.today()
|
||||
days = max(1, int(options['days'] or 1))
|
||||
start_day = end_day - timedelta(days=days - 1)
|
||||
if options['run_date']:
|
||||
inflation_date = date.fromisoformat(options['run_date'])
|
||||
else:
|
||||
inflation_date = date.today()
|
||||
|
||||
dry_run = not options['yes']
|
||||
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'========== 反向撤销 repair_wechat_income ==========\n'
|
||||
f'原 repair 扫描区间: {start_day} ~ {end_day} (--days {days})\n'
|
||||
f'金额被记到日统计日期: {inflation_date} (--run-date)\n'
|
||||
))
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write('【预览】未改数据库\n')
|
||||
else:
|
||||
self.stdout.write('【执行】正在反向扣减...\n')
|
||||
|
||||
r = undo_repair_wechat_income_period(
|
||||
start_day, end_day, inflation_date, dry_run=dry_run,
|
||||
)
|
||||
|
||||
self.stdout.write(f' 将撤销条数: {r["undo_count"]} 条')
|
||||
self.stdout.write(f' 将扣减金额: {r["undo_amount"]:.2f} 元')
|
||||
self.stdout.write(
|
||||
f' 执行前 {inflation_date} 日统计: {r["before_daily_on_inflation_date"]:.2f} 元'
|
||||
)
|
||||
|
||||
if r['items']:
|
||||
self.stdout.write(' 明细:')
|
||||
for ref, cid, amt, kind in r['items']:
|
||||
self.stdout.write(f' - {ref} {amt}元 club={cid} ({kind})')
|
||||
|
||||
if not dry_run:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'\n执行后 {inflation_date} 日统计: {r.get("after_daily_on_inflation_date", 0):.2f} 元'
|
||||
))
|
||||
self.stdout.write(self.style.SUCCESS('反向撤销完成,请刷新后台财务页。'))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'\n预览完成。确认后执行(参数与 repair 保持一致):\n'
|
||||
f' python manage.py undo_repair_wechat_income --date {end_day} --days {days} '
|
||||
f'--run-date {inflation_date} --yes\n'
|
||||
'全部清零:\n'
|
||||
' python manage.py undo_repair_wechat_income --zero-all --yes'
|
||||
))
|
||||
|
||||
def _zero_all(self, dry_run):
|
||||
agg = DailyIncomeStat.objects.aggregate(amt=Sum('total_amount'), cnt=Sum('total_count'))
|
||||
self.stdout.write(self.style.ERROR(
|
||||
'========== 收支统计全部清零 ==========\n'
|
||||
f'当前 daily_income_stat 合计: {float(agg["amt"] or 0):.2f} 元 / {int(agg["cnt"] or 0)} 笔\n'
|
||||
'将删除: platform_income_log 全部、daily_income_stat 全部\n'
|
||||
'将归零: szjilu.TotalIncome / TotalFlow / DailyFlow\n'
|
||||
))
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'预览。确认清零:\n'
|
||||
' python manage.py undo_repair_wechat_income --zero-all --yes'
|
||||
))
|
||||
return
|
||||
zero_all_income_statistics()
|
||||
self.stdout.write(self.style.SUCCESS('已全部清零。请刷新后台。'))
|
||||
@@ -1,92 +1,296 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-14 19:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Gonggao',
|
||||
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='更改时间')),
|
||||
('jieshao', models.TextField(blank=True, null=True, verbose_name='公告介绍')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '公告',
|
||||
'verbose_name_plural': '公告',
|
||||
'db_table': 'gonggao',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Lunbo',
|
||||
fields=[
|
||||
('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='更改时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '轮播',
|
||||
'verbose_name_plural': '轮播',
|
||||
'db_table': 'lunbo',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Qunpeizhi',
|
||||
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='更改时间')),
|
||||
('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='介绍')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'qunpeizhi',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Szjilu',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='记录ID')),
|
||||
('zongsy', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='总收益')),
|
||||
('zongls', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='总流水')),
|
||||
('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='更改时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '收支记录',
|
||||
'verbose_name_plural': '收支记录',
|
||||
'db_table': 'szjilu',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tupianpeizhi',
|
||||
fields=[
|
||||
('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='更改时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '配置图片',
|
||||
'verbose_name_plural': '配置图片',
|
||||
'db_table': 'tupianpeizhi',
|
||||
},
|
||||
),
|
||||
]
|
||||
# Generated by Django 4.2.27 on 2026-06-17 21:18
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Gonggao',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('leixing', models.IntegerField(blank=True, null=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={
|
||||
'verbose_name': '公告',
|
||||
'verbose_name_plural': '公告',
|
||||
'db_table': 'gonggao',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Lunbo',
|
||||
fields=[
|
||||
('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:个人中心背景')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更改时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '轮播',
|
||||
'verbose_name_plural': '轮播',
|
||||
'db_table': 'lunbo',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PopupConfig',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('popup_id', models.CharField(max_length=50, verbose_name='弹窗唯一ID')),
|
||||
('title', models.CharField(blank=True, max_length=200, verbose_name='标题(后台用)')),
|
||||
('description', models.TextField(blank=True, verbose_name='描述(后台用)')),
|
||||
('strategy_type', models.CharField(choices=[('once', '仅一次'), ('daily', '每日'), ('weekly', '每周'), ('monthly', '每月'), ('yearly', '每年'), ('always', '始终'), ('count', '周期内次数限制')], default='once', max_length=20)),
|
||||
('max_count', models.PositiveIntegerField(default=1, verbose_name='周期内最大展示次数')),
|
||||
('reset_interval', models.CharField(blank=True, choices=[('day', '天'), ('week', '周'), ('month', '月'), ('year', '年')], max_length=20, verbose_name='重置周期(count类型时使用)')),
|
||||
('duration', models.PositiveIntegerField(default=0, verbose_name='强制阅读秒数(0=无强制)')),
|
||||
('force_even_muted', models.BooleanField(default=False, verbose_name='强制弹出(忽略用户今日屏蔽)')),
|
||||
('sort_order', models.PositiveIntegerField(default=0, verbose_name='展示顺序')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('start_time', models.DateTimeField(blank=True, null=True, verbose_name='生效开始时间')),
|
||||
('end_time', models.DateTimeField(blank=True, null=True, verbose_name='生效结束时间')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '弹窗配置',
|
||||
'verbose_name_plural': '弹窗配置',
|
||||
'db_table': 'popup_config',
|
||||
'ordering': ['sort_order', 'id'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PopupPage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('page_key', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='页面标识')),
|
||||
('name', models.CharField(max_length=100, verbose_name='页面名称')),
|
||||
('description', models.TextField(blank=True, verbose_name='描述')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('ignore_user_mute', models.BooleanField(default=False, verbose_name='忽略用户今日屏蔽')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '弹窗页面',
|
||||
'verbose_name_plural': '弹窗页面',
|
||||
'db_table': 'popup_page',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Qunpeizhi',
|
||||
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.管事')),
|
||||
('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='介绍')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'qunpeizhi',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Szjilu',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='记录ID')),
|
||||
('zongsy', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='总收益')),
|
||||
('zongls', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='总流水')),
|
||||
('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='今日流水')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更改时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '收支记录',
|
||||
'verbose_name_plural': '收支记录',
|
||||
'db_table': 'szjilu',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TixianQuotaDefault',
|
||||
fields=[
|
||||
('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='默认每日提现限额(元)')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '默认提现限额',
|
||||
'verbose_name_plural': '默认提现限额',
|
||||
'db_table': 'tixian_quota_default',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tupianpeizhi',
|
||||
fields=[
|
||||
('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:默认头像图片')),
|
||||
('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={
|
||||
'verbose_name': '配置图片',
|
||||
'verbose_name_plural': '配置图片',
|
||||
'db_table': 'tupianpeizhi',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WithdrawConfig',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('mode', models.IntegerField(choices=[(1, '自动提现'), (2, '手动提现')], default=2, verbose_name='提现模式')),
|
||||
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '提现模式配置',
|
||||
'verbose_name_plural': '提现模式配置',
|
||||
'db_table': 'withdraw_config',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ShangjiaMoban',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='模板ID')),
|
||||
('yonghu_id', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
|
||||
('leixing_id', models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')),
|
||||
('moban_jieshao', models.CharField(max_length=500, verbose_name='模板介绍')),
|
||||
('jiage', models.DecimalField(decimal_places=2, help_text='单位:元', max_digits=10, verbose_name='模板价格')),
|
||||
('fabu_shuliang', models.PositiveIntegerField(default=0, verbose_name='发布数量')),
|
||||
('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')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商家订单模板',
|
||||
'verbose_name_plural': '商家订单模板',
|
||||
'db_table': 'shangjia_moban',
|
||||
'indexes': [models.Index(fields=['yonghu_id', 'leixing_id'], name='shangjia_mo_yonghu__bb287d_idx'), models.Index(fields=['yonghu_id', 'leixing_id', 'id'], name='shangjia_mo_yonghu__4240d0_idx')],
|
||||
'unique_together': {('yonghu_id', 'leixing_id', 'moban_jieshao')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ShangjiaLianjie',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='链接ID')),
|
||||
('yonghu_id', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
|
||||
('leixing_id', models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')),
|
||||
('moban_id', models.PositiveIntegerField(db_index=True, verbose_name='模板ID')),
|
||||
('lianjie', models.CharField(max_length=500, unique=True, verbose_name='链接地址')),
|
||||
('dingdan_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='订单ID')),
|
||||
('is_used', models.BooleanField(db_index=True, default=False, verbose_name='是否已使用')),
|
||||
('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')),
|
||||
('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')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商家链接',
|
||||
'verbose_name_plural': '商家链接',
|
||||
'db_table': 'shangjia_lianjie',
|
||||
'indexes': [models.Index(fields=['yonghu_id', 'moban_id', 'leixing_id'], name='shangjia_li_yonghu__9f2b25_idx'), models.Index(fields=['lianjie_token'], name='shangjia_li_lianjie_99014c_idx'), models.Index(fields=['dingdan_id'], name='shangjia_li_dingdan_95b682_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PopupImage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('image_url', models.CharField(max_length=500, verbose_name='图片相对路径')),
|
||||
('text', models.TextField(blank=True, verbose_name='图片上方文字')),
|
||||
('sort_order', models.PositiveIntegerField(default=0, verbose_name='展示顺序')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('popup_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='config.popupconfig')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '弹窗图片',
|
||||
'verbose_name_plural': '弹窗图片',
|
||||
'db_table': 'popup_image',
|
||||
'ordering': ['sort_order', 'id'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='popupconfig',
|
||||
name='page',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='popups', to='config.popuppage'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DailyPayoutStat',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(db_index=True, unique=True, verbose_name='统计日期')),
|
||||
('year', models.PositiveSmallIntegerField(verbose_name='年份')),
|
||||
('month', models.PositiveSmallIntegerField(verbose_name='月份')),
|
||||
('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='出款总笔数')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '每日出款统计',
|
||||
'verbose_name_plural': '每日出款统计',
|
||||
'db_table': 'daily_payout_stat',
|
||||
'indexes': [models.Index(fields=['date'], name='daily_payou_date_b534ec_idx'), models.Index(fields=['year', 'month', 'day'], name='daily_payou_year_6376ca_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DailyIncomeStat',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(db_index=True, unique=True, verbose_name='统计日期')),
|
||||
('year', models.PositiveSmallIntegerField(verbose_name='年份')),
|
||||
('month', models.PositiveSmallIntegerField(verbose_name='月份')),
|
||||
('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='总收入笔数')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '每日收入统计',
|
||||
'verbose_name_plural': '每日收入统计',
|
||||
'db_table': 'daily_income_stat',
|
||||
'indexes': [models.Index(fields=['date'], name='daily_incom_date_75e6df_idx'), models.Index(fields=['year', 'month', 'day'], name='daily_incom_year_d85d10_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AccountPermission',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('account_id', models.CharField(db_index=True, max_length=11, verbose_name='账号ID')),
|
||||
('permission_code', models.IntegerField(verbose_name='权限代码')),
|
||||
('is_enabled', models.BooleanField(default=False, verbose_name='是否开启')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '账号权限',
|
||||
'verbose_name_plural': '账号权限',
|
||||
'db_table': 'account_permission',
|
||||
'unique_together': {('account_id', 'permission_code')},
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='popupconfig',
|
||||
unique_together={('page', 'popup_id')},
|
||||
),
|
||||
]
|
||||
|
||||
64
config/migrations/0002_rename_pinyin_to_pascal.py
Normal file
64
config/migrations/0002_rename_pinyin_to_pascal.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# Generated manually to rename pinyin fields to PascalCase
|
||||
# db_column is preserved, so these are no-ops at the database level
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Lunbo
|
||||
migrations.RenameField(model_name='lunbo', old_name='tupian_url', new_name='ImageURL'),
|
||||
migrations.RenameField(model_name='lunbo', old_name='leixing', new_name='ImageType'),
|
||||
|
||||
# Gonggao
|
||||
migrations.RenameField(model_name='gonggao', old_name='leixing', new_name='NoticeType'),
|
||||
migrations.RenameField(model_name='gonggao', old_name='jieshao', new_name='Content'),
|
||||
|
||||
# Qunpeizhi
|
||||
migrations.RenameField(model_name='qunpeizhi', old_name='peizhiid', new_name='GroupType'),
|
||||
migrations.RenameField(model_name='qunpeizhi', old_name='neirong', new_name='GroupContent'),
|
||||
migrations.RenameField(model_name='qunpeizhi', old_name='qunid', new_name='GroupID'),
|
||||
migrations.RenameField(model_name='qunpeizhi', old_name='jieshao', new_name='Description'),
|
||||
|
||||
# Tupianpeizhi
|
||||
migrations.RenameField(model_name='tupianpeizhi', old_name='tupian', new_name='ImageURL'),
|
||||
migrations.RenameField(model_name='tupianpeizhi', old_name='leixing', new_name='ImageType'),
|
||||
migrations.RenameField(model_name='tupianpeizhi', old_name='fangwen_cishu', new_name='AccessCount'),
|
||||
|
||||
# Szjilu
|
||||
migrations.RenameField(model_name='szjilu', old_name='zongsy', new_name='TotalIncome'),
|
||||
migrations.RenameField(model_name='szjilu', old_name='zongls', new_name='TotalFlow'),
|
||||
migrations.RenameField(model_name='szjilu', old_name='zongzc', new_name='TotalExpense'),
|
||||
migrations.RenameField(model_name='szjilu', old_name='jrzc', new_name='DailyExpense'),
|
||||
migrations.RenameField(model_name='szjilu', old_name='jrls', new_name='DailyFlow'),
|
||||
|
||||
# ShangjiaMoban
|
||||
migrations.RenameField(model_name='shangjiamoban', old_name='yonghu_id', new_name='UserID'),
|
||||
migrations.RenameField(model_name='shangjiamoban', old_name='leixing_id', new_name='ProductTypeID'),
|
||||
migrations.RenameField(model_name='shangjiamoban', old_name='moban_jieshao', new_name='TemplateDesc'),
|
||||
migrations.RenameField(model_name='shangjiamoban', old_name='jiage', new_name='Price'),
|
||||
migrations.RenameField(model_name='shangjiamoban', old_name='fabu_shuliang', new_name='PublishedCount'),
|
||||
migrations.RenameField(model_name='shangjiamoban', old_name='yongjin_qiangdan', new_name='CommissionGrabEnabled'),
|
||||
migrations.RenameField(model_name='shangjiamoban', old_name='yongjin_jiage', new_name='CommissionPrice'),
|
||||
migrations.RenameField(model_name='shangjiamoban', old_name='chenghao_id', new_name='TitleID'),
|
||||
|
||||
# ShangjiaLianjie
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='yonghu_id', new_name='UserID'),
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='leixing_id', new_name='ProductTypeID'),
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='moban_id', new_name='TemplateID'),
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='lianjie', new_name='LinkURL'),
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='dingdan_id', new_name='OrderID'),
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='shiyongzhe_ip', new_name='UserIP'),
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='youxiao_shijian', new_name='ExpireTime'),
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='lianjie_token', new_name='LinkToken'),
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='previous_lianjie_id', new_name='PreviousLinkID'),
|
||||
migrations.RenameField(model_name='shangjialianjie', old_name='previous_dingdan_id', new_name='PreviousOrderID'),
|
||||
|
||||
# TixianQuotaDefault
|
||||
migrations.RenameField(model_name='tixianquotadefault', old_name='leixing', new_name='UserType'),
|
||||
]
|
||||
@@ -1,56 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-01-27 12:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ShangjiaMoban',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='模板ID')),
|
||||
('yonghu_id', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
|
||||
('leixing_id', models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')),
|
||||
('moban_jieshao', models.CharField(max_length=500, verbose_name='模板介绍')),
|
||||
('jiage', models.DecimalField(decimal_places=2, help_text='单位:元', max_digits=10, verbose_name='模板价格')),
|
||||
('fabu_shuliang', 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='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商家订单模板',
|
||||
'verbose_name_plural': '商家订单模板',
|
||||
'db_table': 'shangjia_moban',
|
||||
'indexes': [models.Index(fields=['yonghu_id', 'leixing_id'], name='shangjia_mo_yonghu__bb287d_idx'), models.Index(fields=['yonghu_id', 'leixing_id', 'id'], name='shangjia_mo_yonghu__4240d0_idx')],
|
||||
'unique_together': {('yonghu_id', 'leixing_id', 'moban_jieshao')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ShangjiaLianjie',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='链接ID')),
|
||||
('yonghu_id', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
|
||||
('leixing_id', models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')),
|
||||
('moban_id', models.PositiveIntegerField(db_index=True, verbose_name='模板ID')),
|
||||
('lianjie', models.CharField(max_length=500, unique=True, verbose_name='链接地址')),
|
||||
('dingdan_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='订单ID')),
|
||||
('is_used', models.BooleanField(db_index=True, default=False, verbose_name='是否已使用')),
|
||||
('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='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商家链接',
|
||||
'verbose_name_plural': '商家链接',
|
||||
'db_table': 'shangjia_lianjie',
|
||||
'indexes': [models.Index(fields=['yonghu_id', 'moban_id', 'leixing_id'], name='shangjia_li_yonghu__9f2b25_idx'), models.Index(fields=['lianjie_token'], name='shangjia_li_lianjie_99014c_idx'), models.Index(fields=['dingdan_id'], name='shangjia_li_dingdan_95b682_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,76 @@
|
||||
# Generated by Django 4.2.27 on 2026-06-19 11:36
|
||||
# Modified to use SeparateDatabaseAndState for RemoveIndex to bypass
|
||||
# MySQL backend's _create_missing_fk_index check that fails after RenameField
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0002_rename_pinyin_to_pascal'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# RemoveIndex operations wrapped in SeparateDatabaseAndState
|
||||
# to bypass MySQL backend's FK index check that references old field names
|
||||
migrations.SeparateDatabaseAndState(
|
||||
state_operations=[
|
||||
migrations.RemoveIndex(
|
||||
model_name='shangjialianjie',
|
||||
name='shangjia_li_yonghu__9f2b25_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='shangjialianjie',
|
||||
name='shangjia_li_lianjie_99014c_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='shangjialianjie',
|
||||
name='shangjia_li_dingdan_95b682_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='shangjiamoban',
|
||||
name='shangjia_mo_yonghu__bb287d_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='shangjiamoban',
|
||||
name='shangjia_mo_yonghu__4240d0_idx',
|
||||
),
|
||||
],
|
||||
database_operations=[
|
||||
migrations.RunSQL(
|
||||
sql=[
|
||||
'DROP INDEX `shangjia_li_yonghu__9f2b25_idx` ON `shangjia_lianjie`',
|
||||
'DROP INDEX `shangjia_li_lianjie_99014c_idx` ON `shangjia_lianjie`',
|
||||
'DROP INDEX `shangjia_li_dingdan_95b682_idx` ON `shangjia_lianjie`',
|
||||
'DROP INDEX `shangjia_mo_yonghu__bb287d_idx` ON `shangjia_moban`',
|
||||
'DROP INDEX `shangjia_mo_yonghu__4240d0_idx` ON `shangjia_moban`',
|
||||
],
|
||||
reverse_sql=[
|
||||
# No reverse needed - indexes will be recreated by AddIndex
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
# AddIndex operations - these should work fine since fields exist with new names
|
||||
migrations.AddIndex(
|
||||
model_name='shangjialianjie',
|
||||
index=models.Index(fields=['UserID', 'TemplateID', 'ProductTypeID'], name='shangjia_li_UserID_e59d94_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='shangjialianjie',
|
||||
index=models.Index(fields=['LinkToken'], name='shangjia_li_LinkTok_6b32bb_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='shangjialianjie',
|
||||
index=models.Index(fields=['OrderID'], name='shangjia_li_OrderID_e7ddf9_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='shangjiamoban',
|
||||
index=models.Index(fields=['UserID', 'ProductTypeID'], name='shangjia_mo_UserID_abd699_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='shangjiamoban',
|
||||
index=models.Index(fields=['UserID', 'ProductTypeID', 'id'], name='shangjia_mo_UserID_2dcb54_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-03-16 17:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0002_shangjiamoban_shangjialianjie'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tupianpeizhi',
|
||||
name='fangwen_cishu',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='访问次数'),
|
||||
),
|
||||
]
|
||||
47
config/migrations/0004_daily_stat_club_id.py
Normal file
47
config/migrations/0004_daily_stat_club_id.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0003_remove_shangjialianjie_shangjia_li_yonghu__9f2b25_idx_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dailyincomestat',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='所属俱乐部'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dailypayoutstat',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='所属俱乐部'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dailyincomestat',
|
||||
name='date',
|
||||
field=models.DateField(db_index=True, verbose_name='统计日期'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dailypayoutstat',
|
||||
name='date',
|
||||
field=models.DateField(db_index=True, verbose_name='统计日期'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='dailyincomestat',
|
||||
index=models.Index(fields=['club_id', 'date'], name='idx_income_club_date'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='dailypayoutstat',
|
||||
index=models.Index(fields=['club_id', 'date'], name='idx_payout_club_date'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='dailyincomestat',
|
||||
constraint=models.UniqueConstraint(fields=('date', 'club_id'), name='uniq_daily_income_club_date'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='dailypayoutstat',
|
||||
constraint=models.UniqueConstraint(fields=('date', 'club_id'), name='uniq_daily_payout_club_date'),
|
||||
),
|
||||
]
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-03-20 15:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0003_tupianpeizhi_fangwen_cishu'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TixianQuotaDefault',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('leixing', models.IntegerField(choices=[(1, '打手'), (2, '管事')], 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='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '默认提现限额',
|
||||
'verbose_name_plural': '默认提现限额',
|
||||
'db_table': 'tixian_quota_default',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,76 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-03-24 17:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0004_tixianquotadefault'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrderTypeMapping',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('partner_club_id', models.CharField(db_index=True, max_length=6, verbose_name='对方俱乐部ID')),
|
||||
('partner_type_id', models.PositiveIntegerField(verbose_name='对方订单类型ID')),
|
||||
('partner_type_name', models.CharField(max_length=100, verbose_name='对方订单类型名称')),
|
||||
('our_type_id', models.PositiveIntegerField(verbose_name='我方订单类型ID')),
|
||||
('our_type_name', models.CharField(max_length=100, verbose_name='我方订单类型名称')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '订单类型映射',
|
||||
'verbose_name_plural': '订单类型映射',
|
||||
'db_table': 'order_type_mapping',
|
||||
'unique_together': {('partner_club_id', 'partner_type_id')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DailyDispatchStat',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(db_index=True, verbose_name='统计日期')),
|
||||
('year', models.PositiveSmallIntegerField(verbose_name='年份')),
|
||||
('month', models.PositiveSmallIntegerField(verbose_name='月份')),
|
||||
('day', models.PositiveSmallIntegerField(verbose_name='日期')),
|
||||
('direction', models.PositiveSmallIntegerField(choices=[(1, '我方派单'), (2, '对方派单')], verbose_name='派单方向')),
|
||||
('partner_club_id', models.CharField(db_index=True, max_length=6, verbose_name='对方俱乐部ID')),
|
||||
('dispatch_count', models.PositiveIntegerField(default=0, verbose_name='派单数')),
|
||||
('dispatch_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='派单总额')),
|
||||
('success_count', models.PositiveIntegerField(default=0, verbose_name='成交订单数')),
|
||||
('success_amount', 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='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '每日派单统计',
|
||||
'verbose_name_plural': '每日派单统计',
|
||||
'db_table': 'daily_dispatch_stat',
|
||||
'indexes': [models.Index(fields=['year', 'month', 'day', 'direction', 'partner_club_id'], name='daily_dispa_year_f98f91_idx'), models.Index(fields=['date'], name='daily_dispa_date_9ac7fe_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Club',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=6, verbose_name='本俱乐部ID')),
|
||||
('partner_club_id', models.CharField(max_length=6, unique=True, verbose_name='对方俱乐部ID')),
|
||||
('is_interop_enabled', models.BooleanField(default=False, verbose_name='是否开启平台互通')),
|
||||
('is_receive_enabled', models.BooleanField(default=False, verbose_name='是否同意接收对方订单')),
|
||||
('dashou_rate', models.DecimalField(decimal_places=4, default=1.0, max_digits=5, verbose_name='打手分红费率')),
|
||||
('partner_domain', models.URLField(blank=True, verbose_name='对方服务器域名')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '俱乐部',
|
||||
'verbose_name_plural': '俱乐部',
|
||||
'db_table': 'club',
|
||||
'indexes': [models.Index(fields=['club_id'], name='club_club_id_e75636_idx'), models.Index(fields=['partner_club_id'], name='club_partner_b76039_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
37
config/migrations/0005_szjilu_club_id.py
Normal file
37
config/migrations/0005_szjilu_club_id.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def backfill_szjilu_club_id(apps, schema_editor):
|
||||
Szjilu = apps.get_model('config', 'Szjilu')
|
||||
for row in Szjilu.objects.all():
|
||||
if not getattr(row, 'club_id', None):
|
||||
row.club_id = 'xq'
|
||||
row.save(update_fields=['club_id'])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0004_daily_stat_club_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='szjilu',
|
||||
name='club_id',
|
||||
field=models.CharField(
|
||||
db_index=True,
|
||||
default='xq',
|
||||
max_length=16,
|
||||
verbose_name='俱乐部ID',
|
||||
),
|
||||
),
|
||||
migrations.RunPython(backfill_szjilu_club_id, migrations.RunPython.noop),
|
||||
migrations.AddConstraint(
|
||||
model_name='szjilu',
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=('club_id',),
|
||||
name='uniq_szjilu_club_id',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,52 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-01 09:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0005_ordertypemapping_dailydispatchstat_club'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ClubConfig',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=6, verbose_name='俱乐部ID')),
|
||||
('club_nickname', models.CharField(blank=True, default='', max_length=50, verbose_name='俱乐部昵称')),
|
||||
('club_avatar', models.CharField(blank=True, default='', max_length=500, verbose_name='俱乐部头像地址')),
|
||||
('backend_domain', models.CharField(blank=True, default='', max_length=200, verbose_name='后台域名')),
|
||||
('chat_api_key', models.CharField(blank=True, default='', max_length=200, verbose_name='聊天API密钥')),
|
||||
('chat_api_id', models.CharField(blank=True, default='', max_length=200, verbose_name='聊天API ID')),
|
||||
('customer_service_api_url', models.CharField(blank=True, default='', max_length=200, verbose_name='客服API URL')),
|
||||
('customer_service_corp_id', models.CharField(blank=True, default='', max_length=100, verbose_name='客服企业ID')),
|
||||
('storage_bucket_domain', models.CharField(blank=True, default='', max_length=500, verbose_name='存储桶域名')),
|
||||
('is_self', models.SmallIntegerField(choices=[(0, '对方'), (1, '我方')], db_index=True, default=0, verbose_name='是否我方俱乐部')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '俱乐部配置',
|
||||
'verbose_name_plural': '俱乐部配置',
|
||||
'db_table': 'club_config',
|
||||
'unique_together': {('club_id',)},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AccountPermission',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('account_id', models.CharField(db_index=True, max_length=11, verbose_name='账号ID')),
|
||||
('permission_code', models.IntegerField(verbose_name='权限代码')),
|
||||
('is_enabled', models.BooleanField(default=False, verbose_name='是否开启')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '账号权限',
|
||||
'verbose_name_plural': '账号权限',
|
||||
'db_table': 'account_permission',
|
||||
'unique_together': {('account_id', 'permission_code')},
|
||||
},
|
||||
),
|
||||
]
|
||||
81
config/migrations/0006_display_club_id.py
Normal file
81
config/migrations/0006_display_club_id.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from django.db import migrations, models
|
||||
from django.db.models import Max
|
||||
|
||||
|
||||
def _dedupe_popuppage(apps, schema_editor):
|
||||
PopupPage = apps.get_model('config', 'PopupPage')
|
||||
for row in PopupPage.objects.values('club_id', 'page_key').annotate(max_id=Max('id')):
|
||||
PopupPage.objects.filter(
|
||||
club_id=row['club_id'],
|
||||
page_key=row['page_key'],
|
||||
).exclude(id=row['max_id']).delete()
|
||||
|
||||
|
||||
def _dedupe_tupianpeizhi(apps, schema_editor):
|
||||
Tupianpeizhi = apps.get_model('config', 'Tupianpeizhi')
|
||||
for row in Tupianpeizhi.objects.values('club_id', 'ImageType').annotate(max_id=Max('id')):
|
||||
Tupianpeizhi.objects.filter(
|
||||
club_id=row['club_id'],
|
||||
ImageType=row['ImageType'],
|
||||
).exclude(id=row['max_id']).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0005_szjilu_club_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='gonggao',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lunbo',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lunbo',
|
||||
name='page_key',
|
||||
field=models.CharField(
|
||||
db_index=True, default='order_pool', max_length=32,
|
||||
verbose_name='页面标识(order_pool/accept_order/merchant_home/dashou_center)',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='popuppage',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tupianpeizhi',
|
||||
name='club_id',
|
||||
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='popuppage',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='popuppage',
|
||||
name='page_key',
|
||||
field=models.CharField(db_index=True, max_length=50, verbose_name='页面标识'),
|
||||
),
|
||||
migrations.RunPython(_dedupe_popuppage, migrations.RunPython.noop),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='popuppage',
|
||||
unique_together={('club_id', 'page_key')},
|
||||
),
|
||||
migrations.RunPython(_dedupe_tupianpeizhi, migrations.RunPython.noop),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='tupianpeizhi',
|
||||
unique_together={('club_id', 'ImageType')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='lunbo',
|
||||
index=models.Index(fields=['club_id', 'page_key', 'ImageType'], name='lunbo_club_page_type_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-02 13:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0006_clubconfig_accountpermission'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='club',
|
||||
name='receive_rate',
|
||||
field=models.DecimalField(decimal_places=4, default=1.0, max_digits=5, verbose_name='接收打手分红费率'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dailydispatchstat',
|
||||
name='claimed_amount',
|
||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='抢单总额'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dailydispatchstat',
|
||||
name='claimed_count',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='抢单数'),
|
||||
),
|
||||
]
|
||||
26
config/migrations/0007_platform_income_log.py
Normal file
26
config/migrations/0007_platform_income_log.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0006_display_club_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PlatformIncomeLog',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('biz_ref', models.CharField(db_index=True, max_length=64, unique=True, verbose_name='业务单号')),
|
||||
('club_id', models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID')),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='入账金额')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '平台入账幂等日志',
|
||||
'verbose_name_plural': '平台入账幂等日志',
|
||||
'db_table': 'platform_income_log',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-04 18:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0007_club_receive_rate_dailydispatchstat_claimed_amount_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='clubconfig',
|
||||
name='storage_bucket_name',
|
||||
field=models.CharField(blank=True, default='', max_length=100, verbose_name='存储桶名称'),
|
||||
),
|
||||
]
|
||||
34
config/migrations/0008_gonggao_page_key.py
Normal file
34
config/migrations/0008_gonggao_page_key.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def backfill_gonggao_page_key(apps, schema_editor):
|
||||
Gonggao = apps.get_model('config', 'Gonggao')
|
||||
for row in Gonggao.objects.filter(page_key='').only('id'):
|
||||
row.page_key = 'order_pool'
|
||||
row.save(update_fields=['page_key'])
|
||||
for row in Gonggao.objects.filter(page_key__isnull=True).only('id'):
|
||||
row.page_key = 'order_pool'
|
||||
row.save(update_fields=['page_key'])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0007_platform_income_log'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='gonggao',
|
||||
name='page_key',
|
||||
field=models.CharField(
|
||||
db_index=True, default='order_pool', max_length=32,
|
||||
verbose_name='页面标识(order_pool/accept_order/merchant_home)',
|
||||
),
|
||||
),
|
||||
migrations.RunPython(backfill_gonggao_page_key, migrations.RunPython.noop),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='gonggao',
|
||||
unique_together={('club_id', 'page_key')},
|
||||
),
|
||||
]
|
||||
@@ -1,53 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-05 13:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0008_clubconfig_storage_bucket_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DailyPayoutStat',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(db_index=True, unique=True, verbose_name='统计日期')),
|
||||
('year', models.PositiveSmallIntegerField(verbose_name='年份')),
|
||||
('month', models.PositiveSmallIntegerField(verbose_name='月份')),
|
||||
('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='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '每日出款统计',
|
||||
'verbose_name_plural': '每日出款统计',
|
||||
'db_table': 'daily_payout_stat',
|
||||
'indexes': [models.Index(fields=['date'], name='daily_payou_date_b534ec_idx'), models.Index(fields=['year', 'month', 'day'], name='daily_payou_year_6376ca_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DailyIncomeStat',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(db_index=True, unique=True, verbose_name='统计日期')),
|
||||
('year', models.PositiveSmallIntegerField(verbose_name='年份')),
|
||||
('month', models.PositiveSmallIntegerField(verbose_name='月份')),
|
||||
('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='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '每日收入统计',
|
||||
'verbose_name_plural': '每日收入统计',
|
||||
'db_table': 'daily_income_stat',
|
||||
'indexes': [models.Index(fields=['date'], name='daily_incom_date_75e6df_idx'), models.Index(fields=['year', 'month', 'day'], name='daily_incom_year_d85d10_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
27
config/migrations/0009_gonggao_copy_pages.py
Normal file
27
config/migrations/0009_gonggao_copy_pages.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def copy_gonggao_to_other_pages(apps, schema_editor):
|
||||
"""将 order_pool 公告复制到 accept_order / merchant_home(仅当目标页尚无记录)。"""
|
||||
Gonggao = apps.get_model('config', 'Gonggao')
|
||||
for pk in ('accept_order', 'merchant_home'):
|
||||
for row in Gonggao.objects.filter(page_key='order_pool'):
|
||||
if Gonggao.objects.filter(club_id=row.club_id, page_key=pk).exists():
|
||||
continue
|
||||
Gonggao.objects.create(
|
||||
club_id=row.club_id,
|
||||
page_key=pk,
|
||||
NoticeType=row.NoticeType,
|
||||
Content=row.Content or '',
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0008_gonggao_page_key'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(copy_gonggao_to_other_pages, migrations.RunPython.noop),
|
||||
]
|
||||
67
config/migrations/0010_club_miniapp_icon_pindao.py
Normal file
67
config/migrations/0010_club_miniapp_icon_pindao.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0009_gonggao_copy_pages'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ClubMiniappIcon',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID')),
|
||||
('icon_key', models.CharField(db_index=True, max_length=64, verbose_name='图标键')),
|
||||
('label', models.CharField(blank=True, default='', max_length=100, verbose_name='展示名称')),
|
||||
('image_path', models.CharField(blank=True, default='', max_length=500, verbose_name='图片相对路径')),
|
||||
('description', models.CharField(blank=True, default='', max_length=300, verbose_name='说明')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '小程序图标',
|
||||
'verbose_name_plural': '小程序图标',
|
||||
'db_table': 'club_miniapp_icon',
|
||||
'ordering': ['icon_key'],
|
||||
'unique_together': {('club_id', 'icon_key')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClubPindao',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(default='xq', max_length=16, unique=True, verbose_name='俱乐部ID')),
|
||||
('channel_no', models.CharField(blank=True, default='', max_length=100, verbose_name='频道号')),
|
||||
('title', models.CharField(blank=True, default='频道', max_length=100, verbose_name='标题')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '俱乐部频道',
|
||||
'verbose_name_plural': '俱乐部频道',
|
||||
'db_table': 'club_pindao',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClubPindaoImage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID')),
|
||||
('image_path', models.CharField(max_length=500, verbose_name='图片相对路径')),
|
||||
('sort_order', models.PositiveIntegerField(default=0, verbose_name='排序')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('pindao', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='config.clubpindao')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '频道图片',
|
||||
'verbose_name_plural': '频道图片',
|
||||
'db_table': 'club_pindao_image',
|
||||
'ordering': ['sort_order', 'id'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,84 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-22 11:24
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0009_dailypayoutstat_dailyincomestat'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PopupConfig',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('popup_id', models.CharField(max_length=50, verbose_name='弹窗唯一ID')),
|
||||
('title', models.CharField(blank=True, max_length=200, verbose_name='标题(后台用)')),
|
||||
('description', models.TextField(blank=True, verbose_name='描述(后台用)')),
|
||||
('strategy_type', models.CharField(choices=[('once', '仅一次'), ('daily', '每日'), ('weekly', '每周'), ('monthly', '每月'), ('yearly', '每年'), ('always', '始终'), ('count', '周期内次数限制')], default='once', max_length=20)),
|
||||
('max_count', models.PositiveIntegerField(default=1, verbose_name='周期内最大展示次数')),
|
||||
('reset_interval', models.CharField(blank=True, choices=[('day', '天'), ('week', '周'), ('month', '月'), ('year', '年')], max_length=20, verbose_name='重置周期(count类型时使用)')),
|
||||
('duration', models.PositiveIntegerField(default=0, verbose_name='强制阅读秒数(0=无强制)')),
|
||||
('force_even_muted', models.BooleanField(default=False, verbose_name='强制弹出(忽略用户今日屏蔽)')),
|
||||
('sort_order', models.PositiveIntegerField(default=0, verbose_name='展示顺序')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('start_time', models.DateTimeField(blank=True, null=True, verbose_name='生效开始时间')),
|
||||
('end_time', models.DateTimeField(blank=True, null=True, verbose_name='生效结束时间')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '弹窗配置',
|
||||
'verbose_name_plural': '弹窗配置',
|
||||
'db_table': 'popup_config',
|
||||
'ordering': ['sort_order', 'id'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PopupPage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('page_key', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='页面标识')),
|
||||
('name', models.CharField(max_length=100, verbose_name='页面名称')),
|
||||
('description', models.TextField(blank=True, verbose_name='描述')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('ignore_user_mute', models.BooleanField(default=False, verbose_name='忽略用户今日屏蔽')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '弹窗页面',
|
||||
'verbose_name_plural': '弹窗页面',
|
||||
'db_table': 'popup_page',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PopupImage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('image_url', models.CharField(max_length=500, verbose_name='图片相对路径')),
|
||||
('text', models.TextField(blank=True, verbose_name='图片上方文字')),
|
||||
('sort_order', models.PositiveIntegerField(default=0, verbose_name='展示顺序')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('popup_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='config.popupconfig')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '弹窗图片',
|
||||
'verbose_name_plural': '弹窗图片',
|
||||
'db_table': 'popup_image',
|
||||
'ordering': ['sort_order', 'id'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='popupconfig',
|
||||
name='page',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='popups', to='config.popuppage'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='popupconfig',
|
||||
unique_together={('page', 'popup_id')},
|
||||
),
|
||||
]
|
||||
@@ -1,18 +1,16 @@
|
||||
# Generated by Django 4.2.27 on 2026-05-18 02:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0012_usershenheguan'),
|
||||
('config', '0010_club_miniapp_icon_pindao'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='usershenheguan',
|
||||
name='is_renzheng',
|
||||
field=models.BooleanField(default=False, verbose_name='是否认证'),
|
||||
model_name='clubminiappicon',
|
||||
name='is_uploaded',
|
||||
field=models.BooleanField(default=False, verbose_name='是否已在后台上传'),
|
||||
),
|
||||
]
|
||||
@@ -1,27 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-25 12:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0010_popupconfig_popuppage_popupimage_popupconfig_page_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='WithdrawConfig',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('mode', models.IntegerField(choices=[(1, '自动提现'), (2, '手动提现')], default=2, verbose_name='提现模式')),
|
||||
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '提现模式配置',
|
||||
'verbose_name_plural': '提现模式配置',
|
||||
'db_table': 'withdraw_config',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-05-17 18:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0011_withdrawconfig'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='shangjiamoban',
|
||||
name='chenghao_id',
|
||||
field=models.IntegerField(blank=True, null=True, verbose_name='关联称号ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shangjiamoban',
|
||||
name='yongjin_jiage',
|
||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='佣金价格'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shangjiamoban',
|
||||
name='yongjin_qiangdan',
|
||||
field=models.BooleanField(default=False, verbose_name='是否开启佣金抢单'),
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-05-26 03:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0012_shangjiamoban_chenghao_id_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='shangjialianjie',
|
||||
name='previous_dingdan_id',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='上一个订单ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shangjialianjie',
|
||||
name='previous_lianjie_id',
|
||||
field=models.PositiveIntegerField(blank=True, db_index=True, null=True, verbose_name='上一个链接ID'),
|
||||
),
|
||||
]
|
||||
@@ -1,30 +0,0 @@
|
||||
# Generated by Django 4.2.27 on 2026-06-15 01:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0013_shangjialianjie_previous_dingdan_id_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Club',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='ClubConfig',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DailyDispatchStat',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='OrderTypeMapping',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tixianquotadefault',
|
||||
name='leixing',
|
||||
field=models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '组长'), (4, '考核官'), (5, '打手押金'), (6, '商家余额')], unique=True, verbose_name='用户类型'),
|
||||
),
|
||||
]
|
||||
956
config/models.py
956
config/models.py
@@ -1,434 +1,522 @@
|
||||
from django.db import models
|
||||
from gvsdsdk.model_base import QModel
|
||||
|
||||
|
||||
class Lunbo(QModel):
|
||||
tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='轮播图片URL')
|
||||
leixing = 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='更改时间')
|
||||
class Meta:
|
||||
db_table = 'lunbo'
|
||||
verbose_name = '轮播'
|
||||
verbose_name_plural = '轮播'
|
||||
# 不要索引!数据量太小
|
||||
|
||||
|
||||
class Gonggao(QModel):
|
||||
leixing = 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='更改时间')
|
||||
jieshao = models.TextField(null=True, blank=True, verbose_name='公告介绍')
|
||||
class Meta:
|
||||
db_table = 'gonggao'
|
||||
verbose_name = '公告'
|
||||
verbose_name_plural = '公告'
|
||||
# 不要索引!数据量太小
|
||||
|
||||
class Qunpeizhi(QModel):
|
||||
peizhiid = 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='更改时间')
|
||||
neirong = models.CharField(max_length=200, null=True, blank=True, verbose_name='群介绍')
|
||||
qunid = models.CharField(max_length=100, null=True, blank=True, verbose_name='群id')
|
||||
jieshao = models.CharField(max_length=200, null=True, blank=True, verbose_name='介绍')
|
||||
class Meta:
|
||||
db_table = 'qunpeizhi'
|
||||
|
||||
class Tupianpeizhi(QModel):
|
||||
tupian = models.CharField(max_length=500, null=True, blank=True, verbose_name='配置图片URL')
|
||||
leixing = 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='更改时间')
|
||||
fangwen_cishu = models.PositiveIntegerField(default=0, verbose_name='访问次数') # 新增字段
|
||||
class Meta:
|
||||
db_table = 'tupianpeizhi'
|
||||
verbose_name = '配置图片'
|
||||
verbose_name_plural = '配置图片'
|
||||
|
||||
|
||||
class Szjilu(QModel):
|
||||
id = models.AutoField(primary_key=True, verbose_name='记录ID')
|
||||
zongsy = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='总收益')
|
||||
zongls = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='总流水')
|
||||
zongzc = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='总支出')
|
||||
jrzc = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日支出')
|
||||
jrls = 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 = 'szjilu'
|
||||
verbose_name = '收支记录'
|
||||
verbose_name_plural = '收支记录'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ShangjiaMoban(QModel):
|
||||
"""商家订单模板表 - 存储商家创建的订单模板"""
|
||||
|
||||
# 主键ID(自动递增)
|
||||
id = models.AutoField(primary_key=True, verbose_name='模板ID')
|
||||
|
||||
# 关联字段
|
||||
yonghu_id = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
|
||||
leixing_id = models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')
|
||||
|
||||
# 模板内容
|
||||
|
||||
moban_jieshao = models.CharField(max_length=500, verbose_name='模板介绍')
|
||||
jiage = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
verbose_name='模板价格',
|
||||
help_text='单位:元'
|
||||
)
|
||||
|
||||
# 统计字段
|
||||
fabu_shuliang = models.PositiveIntegerField(default=0, verbose_name='发布数量')
|
||||
|
||||
yongjin_qiangdan = models.BooleanField(default=False, verbose_name='是否开启佣金抢单')
|
||||
yongjin_jiage = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='佣金价格')
|
||||
chenghao_id = models.IntegerField(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 = 'shangjia_moban'
|
||||
verbose_name = '商家订单模板'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
# 高频查询组合索引
|
||||
models.Index(fields=['yonghu_id', 'leixing_id']),
|
||||
models.Index(fields=['yonghu_id', 'leixing_id','id']),
|
||||
|
||||
]
|
||||
# 同一个商家同一类型下模板介绍不能重复(允许修改)
|
||||
unique_together = ['yonghu_id', 'leixing_id', 'moban_jieshao']
|
||||
|
||||
def __str__(self):
|
||||
return f"模板{self.id}-{self.yonghu_id}"
|
||||
|
||||
|
||||
|
||||
|
||||
class ShangjiaLianjie(QModel):
|
||||
"""商家链接存储表 - 存储生成的派单链接"""
|
||||
|
||||
# 主键ID(自动递增)
|
||||
id = models.AutoField(primary_key=True, verbose_name='链接ID')
|
||||
|
||||
# 关联字段
|
||||
yonghu_id = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
|
||||
leixing_id = models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')
|
||||
moban_id = models.PositiveIntegerField(db_index=True, verbose_name='模板ID')
|
||||
|
||||
# 链接核心数据
|
||||
lianjie = models.CharField(max_length=500, unique=True, verbose_name='链接地址')
|
||||
#lianjie_token = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='链接Token')
|
||||
|
||||
# 订单关联(生成订单后更新)
|
||||
dingdan_id = models.CharField(max_length=32, null=True, blank=True, db_index=True, verbose_name='订单ID')
|
||||
|
||||
# 使用状态
|
||||
is_used = models.BooleanField(default=False, verbose_name='是否已使用', db_index=True)
|
||||
#shiyong_time = models.DateTimeField(null=True, blank=True, verbose_name='使用时间')
|
||||
|
||||
# 使用者信息
|
||||
shiyongzhe_ip = models.CharField(max_length=50, null=True, blank=True, verbose_name='使用者IP')
|
||||
#shiyongzhe_info = models.TextField(null=True, blank=True, verbose_name='使用者信息')
|
||||
|
||||
# 链接控制
|
||||
#shiyong_cishu = models.PositiveIntegerField(default=1, verbose_name='可使用次数')
|
||||
#yixiang_cishu = models.PositiveIntegerField(default=0, verbose_name='已使用次数')
|
||||
youxiao_shijian = models.DateTimeField(null=True, blank=True, verbose_name='有效截止时间')
|
||||
|
||||
# 安全字段
|
||||
#anquan_ma = models.CharField(max_length=100, null=True, blank=True, verbose_name='安全码')
|
||||
# 新增 Token 字段
|
||||
lianjie_token = models.CharField(
|
||||
max_length=32,
|
||||
unique=True,
|
||||
db_index=True,
|
||||
verbose_name='链接Token',
|
||||
null=True,
|
||||
blank=True # 先设置为可选,后面再设为必填
|
||||
)
|
||||
|
||||
# 时间字段
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
# 在 ShangjiaLianjie 类中添加
|
||||
|
||||
# 上一个链接的ID(可选,记录订单被更换前的链接)
|
||||
previous_lianjie_id = models.PositiveIntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
verbose_name='上一个链接ID'
|
||||
)
|
||||
|
||||
# 上一个订单的ID(可选,记录被更换的旧订单ID)
|
||||
previous_dingdan_id = models.CharField(
|
||||
max_length=32,
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
verbose_name='上一个订单ID'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = 'shangjia_lianjie'
|
||||
verbose_name = '商家链接'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
# 高频查询组合索引
|
||||
models.Index(fields=['yonghu_id', 'moban_id','leixing_id']),
|
||||
models.Index(fields=['lianjie_token']),
|
||||
#models.Index(fields=['lianjie_token']),
|
||||
models.Index(fields=['dingdan_id']),
|
||||
#models.Index(fields=['moban_id']),
|
||||
#models.Index(fields=['create_time']),
|
||||
#models.Index(fields=['youxiao_shijian', 'is_used']),
|
||||
]
|
||||
#ordering = ['-create_time']
|
||||
|
||||
def __str__(self):
|
||||
return f"链接{self.id}-{self.yonghu_id}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TixianQuotaDefault(QModel):
|
||||
"""
|
||||
默认每日提现限额表
|
||||
"""
|
||||
id = models.AutoField(primary_key=True)
|
||||
leixing = models.IntegerField(
|
||||
choices=[(1, '打手'), (2, '管事'), (3, '组长'),
|
||||
(4, '考核官'), (5, '打手押金'), (6, '商家余额')],
|
||||
unique=True,
|
||||
verbose_name='用户类型'
|
||||
)
|
||||
default_quota = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
default=20.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 = 'tixian_quota_default'
|
||||
verbose_name = '默认提现限额'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.get_leixing_display()} 默认限额: {self.default_quota}"
|
||||
|
||||
|
||||
|
||||
|
||||
class WithdrawConfig(QModel):
|
||||
"""
|
||||
提现模式配置表
|
||||
- mode = 1: 自动提现(前端展示收款码模式)
|
||||
- mode = 2: 手动提现(前端展示微信零钱模式)
|
||||
默认值为 2(手动打款),没有记录时前端也按默认 2 处理
|
||||
"""
|
||||
MODE_CHOICES = (
|
||||
(1, '自动提现'), # 对应前端模式一
|
||||
(2, '手动提现'), # 对应前端模式二
|
||||
)
|
||||
|
||||
mode = models.IntegerField(
|
||||
choices=MODE_CHOICES,
|
||||
default=2,
|
||||
verbose_name='提现模式'
|
||||
)
|
||||
created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'withdraw_config'
|
||||
verbose_name = '提现模式配置'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return f'提现模式: {"自动" if self.mode == 1 else "手动"}'
|
||||
|
||||
|
||||
|
||||
|
||||
class AccountPermission(QModel):
|
||||
"""账号权限表:记录每个账号拥有的权限及开关状态"""
|
||||
account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID')
|
||||
permission_code = models.IntegerField(verbose_name='权限代码')
|
||||
is_enabled = models.BooleanField(default=False, verbose_name='是否开启')
|
||||
|
||||
class Meta:
|
||||
db_table = 'account_permission'
|
||||
verbose_name = '账号权限'
|
||||
verbose_name_plural = '账号权限'
|
||||
unique_together = [['account_id', 'permission_code']] # 一个账号的同一权限只能有一条记录
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.account_id} 权限{self.permission_code}: {'开启' if self.is_enabled else '关闭'}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# apps/dingdan/models.py (新增模型)
|
||||
|
||||
class DailyIncomeStat(QModel):
|
||||
"""
|
||||
每日收入统计表
|
||||
记录平台每天通过微信支付获得的总收入和总笔数
|
||||
"""
|
||||
# 日期相关字段(便于快速筛选和分组)
|
||||
date = models.DateField(verbose_name='统计日期', db_index=True, unique=True)
|
||||
year = models.PositiveSmallIntegerField(verbose_name='年份')
|
||||
month = models.PositiveSmallIntegerField(verbose_name='月份')
|
||||
day = models.PositiveSmallIntegerField(verbose_name='日期')
|
||||
|
||||
# 收入统计
|
||||
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='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'daily_income_stat'
|
||||
verbose_name = '每日收入统计'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
models.Index(fields=['date']),
|
||||
models.Index(fields=['year', 'month', 'day']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.date} - 收入{self.total_amount}元 - {self.total_count}笔"
|
||||
|
||||
|
||||
# apps/dingdan/models.py
|
||||
|
||||
class DailyPayoutStat(QModel):
|
||||
"""
|
||||
每日出款统计表
|
||||
记录平台每天通过提现/结算等方式的出款总额和总笔数
|
||||
"""
|
||||
date = models.DateField(verbose_name='统计日期', db_index=True, unique=True)
|
||||
year = models.PositiveSmallIntegerField(verbose_name='年份')
|
||||
month = models.PositiveSmallIntegerField(verbose_name='月份')
|
||||
day = models.PositiveSmallIntegerField(verbose_name='日期')
|
||||
|
||||
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='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'daily_payout_stat'
|
||||
verbose_name = '每日出款统计'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
models.Index(fields=['date']),
|
||||
models.Index(fields=['year', 'month', 'day']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.date} - 出款{self.total_amount}元 - {self.total_count}笔"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class PopupPage(QModel):
|
||||
page_key = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='页面标识')
|
||||
name = models.CharField(max_length=100, verbose_name='页面名称')
|
||||
description = models.TextField(blank=True, verbose_name='描述')
|
||||
is_active = models.BooleanField(default=True, verbose_name='是否启用')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
ignore_user_mute = models.BooleanField(default=False, verbose_name='忽略用户今日屏蔽')
|
||||
|
||||
class Meta:
|
||||
db_table = 'popup_page'
|
||||
verbose_name = '弹窗页面'
|
||||
verbose_name_plural = '弹窗页面'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name} ({self.page_key})'
|
||||
|
||||
|
||||
class PopupConfig(QModel):
|
||||
STRATEGY_CHOICES = (
|
||||
('once', '仅一次'),
|
||||
('daily', '每日'),
|
||||
('weekly', '每周'),
|
||||
('monthly', '每月'),
|
||||
('yearly', '每年'),
|
||||
('always', '始终'),
|
||||
('count', '周期内次数限制'),
|
||||
)
|
||||
page = models.ForeignKey(PopupPage, on_delete=models.CASCADE, related_name='popups')
|
||||
popup_id = models.CharField(max_length=50, verbose_name='弹窗唯一ID')
|
||||
title = models.CharField(max_length=200, blank=True, verbose_name='标题(后台用)')
|
||||
description = models.TextField(blank=True, verbose_name='描述(后台用)')
|
||||
strategy_type = models.CharField(max_length=20, choices=STRATEGY_CHOICES, default='once')
|
||||
max_count = models.PositiveIntegerField(default=1, verbose_name='周期内最大展示次数')
|
||||
reset_interval = models.CharField(max_length=20, blank=True,
|
||||
choices=(('day','天'),('week','周'),('month','月'),('year','年')),
|
||||
verbose_name='重置周期(count类型时使用)')
|
||||
duration = models.PositiveIntegerField(default=0, verbose_name='强制阅读秒数(0=无强制)')
|
||||
force_even_muted = models.BooleanField(default=False, verbose_name='强制弹出(忽略用户今日屏蔽)')
|
||||
sort_order = models.PositiveIntegerField(default=0, verbose_name='展示顺序')
|
||||
is_active = models.BooleanField(default=True, verbose_name='是否启用')
|
||||
start_time = models.DateTimeField(null=True, blank=True, verbose_name='生效开始时间')
|
||||
end_time = models.DateTimeField(null=True, blank=True, verbose_name='生效结束时间')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'popup_config'
|
||||
verbose_name = '弹窗配置'
|
||||
verbose_name_plural = '弹窗配置'
|
||||
unique_together = ('page', 'popup_id')
|
||||
ordering = ['sort_order', 'id']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.page.name} - {self.popup_id}'
|
||||
|
||||
|
||||
class PopupImage(QModel):
|
||||
popup_config = models.ForeignKey(PopupConfig, on_delete=models.CASCADE, related_name='images')
|
||||
image_url = models.CharField(max_length=500, verbose_name='图片相对路径')
|
||||
text = models.TextField(blank=True, verbose_name='图片上方文字')
|
||||
sort_order = models.PositiveIntegerField(default=0, verbose_name='展示顺序')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'popup_image'
|
||||
verbose_name = '弹窗图片'
|
||||
verbose_name_plural = '弹窗图片'
|
||||
ordering = ['sort_order', 'id']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.popup_config.popup_id} - 图片{self.sort_order}'
|
||||
from django.db import models
|
||||
from gvsdsdk.model_base import QModel
|
||||
|
||||
|
||||
class Lunbo(QModel):
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
page_key = models.CharField(
|
||||
max_length=32, default='order_pool', db_index=True,
|
||||
verbose_name='页面标识(order_pool/accept_order/merchant_home/dashou_center)',
|
||||
)
|
||||
ImageURL = models.CharField(max_length=500, null=True, blank=True, verbose_name='轮播图片URL')
|
||||
ImageType = models.IntegerField(null=True, blank=True, verbose_name='图片类型:1:轮播2:个人中心背景')
|
||||
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更改时间')
|
||||
class Meta:
|
||||
db_table = 'lunbo'
|
||||
verbose_name = '轮播'
|
||||
verbose_name_plural = '轮播'
|
||||
indexes = [
|
||||
models.Index(fields=['club_id', 'page_key', 'ImageType']),
|
||||
]
|
||||
|
||||
|
||||
class Gonggao(QModel):
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
page_key = models.CharField(
|
||||
max_length=32, default='order_pool', db_index=True,
|
||||
verbose_name='页面标识(order_pool/accept_order/merchant_home)',
|
||||
)
|
||||
NoticeType = models.IntegerField(null=True, blank=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'
|
||||
verbose_name = '公告'
|
||||
verbose_name_plural = '公告'
|
||||
unique_together = [['club_id', 'page_key']]
|
||||
|
||||
class Qunpeizhi(QModel):
|
||||
GroupType = models.IntegerField(null=True, blank=True, verbose_name='群类型:1.打手 2.管事')
|
||||
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='介绍')
|
||||
class Meta:
|
||||
db_table = 'qunpeizhi'
|
||||
|
||||
class Tupianpeizhi(QModel):
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
ImageURL = models.CharField(max_length=500, null=True, blank=True, verbose_name='配置图片URL')
|
||||
ImageType = models.PositiveIntegerField(null=True, blank=True, verbose_name='提现类型1:打手规则图片2:默认头像图片')
|
||||
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'
|
||||
verbose_name = '配置图片'
|
||||
verbose_name_plural = '配置图片'
|
||||
# ImageType 1/2 每俱乐部唯一;3(关注快手) 允许多张
|
||||
|
||||
|
||||
class Szjilu(QModel):
|
||||
"""
|
||||
【已废弃 · 禁止使用】
|
||||
|
||||
本表与财务看板「每日收入 / 每日支出」无关。
|
||||
财务四个板子只读 daily_income_stat、daily_payout_stat,不得读写本表。
|
||||
repair_wechat_income 曾误写本表导致数据膨胀,后续入账也不再更新本表。
|
||||
"""
|
||||
id = models.AutoField(primary_key=True, verbose_name='记录ID')
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
TotalIncome = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='总收益')
|
||||
TotalFlow = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='总流水')
|
||||
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='今日流水')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更改时间')
|
||||
class Meta:
|
||||
db_table = 'szjilu'
|
||||
verbose_name = '收支记录'
|
||||
verbose_name_plural = '收支记录'
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['club_id'], name='uniq_szjilu_club_id'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
class ShangjiaMoban(QModel):
|
||||
"""商家订单模板表 - 存储商家创建的订单模板"""
|
||||
|
||||
# 主键ID(自动递增)
|
||||
id = models.AutoField(primary_key=True, verbose_name='模板ID')
|
||||
|
||||
# 关联字段
|
||||
UserID = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
|
||||
ProductTypeID = models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')
|
||||
|
||||
# 模板内容
|
||||
TemplateDesc = models.CharField(max_length=500, verbose_name='模板介绍')
|
||||
Price = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
verbose_name='模板价格',
|
||||
help_text='单位:元'
|
||||
)
|
||||
|
||||
# 统计字段
|
||||
PublishedCount = models.PositiveIntegerField(default=0, verbose_name='发布数量')
|
||||
|
||||
CommissionGrabEnabled = models.BooleanField(default=False, verbose_name='是否开启佣金抢单')
|
||||
CommissionPrice = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='佣金价格')
|
||||
TitleID = models.IntegerField(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 = 'shangjia_moban'
|
||||
verbose_name = '商家订单模板'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
# 高频查询组合索引
|
||||
models.Index(fields=['UserID', 'ProductTypeID']),
|
||||
models.Index(fields=['UserID', 'ProductTypeID','id']),
|
||||
|
||||
]
|
||||
# 同一个商家同一类型下模板介绍不能重复(允许修改)
|
||||
unique_together = ['UserID', 'ProductTypeID', 'TemplateDesc']
|
||||
|
||||
def __str__(self):
|
||||
return f"模板{self.id}-{self.UserID}"
|
||||
|
||||
|
||||
|
||||
|
||||
class ShangjiaLianjie(QModel):
|
||||
"""商家链接存储表 - 存储生成的派单链接"""
|
||||
|
||||
# 主键ID(自动递增)
|
||||
id = models.AutoField(primary_key=True, verbose_name='链接ID')
|
||||
|
||||
# 关联字段
|
||||
UserID = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
|
||||
ProductTypeID = models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')
|
||||
TemplateID = models.PositiveIntegerField(db_index=True, verbose_name='模板ID')
|
||||
|
||||
# 链接核心数据
|
||||
LinkURL = models.CharField(max_length=500, unique=True, verbose_name='链接地址')
|
||||
|
||||
# 订单关联(生成订单后更新)
|
||||
OrderID = models.CharField(max_length=32, null=True, blank=True, db_index=True, verbose_name='订单ID')
|
||||
|
||||
# 使用状态
|
||||
is_used = models.BooleanField(default=False, verbose_name='是否已使用', db_index=True)
|
||||
|
||||
# 使用者信息
|
||||
UserIP = models.CharField(max_length=50, null=True, blank=True, verbose_name='使用者IP')
|
||||
|
||||
# 链接控制
|
||||
ExpireTime = models.DateTimeField(null=True, blank=True, verbose_name='有效截止时间')
|
||||
|
||||
# 安全字段
|
||||
LinkToken = models.CharField(
|
||||
max_length=32,
|
||||
unique=True,
|
||||
db_index=True,
|
||||
verbose_name='链接Token',
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
# 时间字段
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
# 上一个链接的ID(可选,记录订单被更换前的链接)
|
||||
PreviousLinkID = models.PositiveIntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
verbose_name='上一个链接ID'
|
||||
)
|
||||
|
||||
# 上一个订单的ID(可选,记录被更换的旧订单ID)
|
||||
PreviousOrderID = models.CharField(
|
||||
max_length=32,
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
verbose_name='上一个订单ID'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = 'shangjia_lianjie'
|
||||
verbose_name = '商家链接'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
# 高频查询组合索引
|
||||
models.Index(fields=['UserID', 'TemplateID','ProductTypeID']),
|
||||
models.Index(fields=['LinkToken']),
|
||||
models.Index(fields=['OrderID']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"链接{self.id}-{self.UserID}"
|
||||
|
||||
|
||||
|
||||
|
||||
class TixianQuotaDefault(QModel):
|
||||
"""
|
||||
默认每日提现限额表
|
||||
"""
|
||||
id = models.AutoField(primary_key=True)
|
||||
UserType = models.IntegerField(
|
||||
choices=[(1, '打手'), (2, '管事'), (3, '组长'),
|
||||
(4, '考核官'), (5, '打手押金'), (6, '商家余额')],
|
||||
unique=True,
|
||||
verbose_name='用户类型'
|
||||
)
|
||||
default_quota = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
default=20.00,
|
||||
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'
|
||||
verbose_name = '默认提现限额'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.get_UserType_display()} 默认限额: {self.default_quota}"
|
||||
|
||||
|
||||
|
||||
|
||||
class WithdrawConfig(QModel):
|
||||
"""
|
||||
提现模式配置表
|
||||
- mode = 1: 自动提现(前端展示收款码模式)
|
||||
- mode = 2: 手动提现(前端展示微信零钱模式)
|
||||
默认值为 2(手动打款),没有记录时前端也按默认 2 处理
|
||||
"""
|
||||
MODE_CHOICES = (
|
||||
(1, '自动提现'), # 对应前端模式一
|
||||
(2, '手动提现'), # 对应前端模式二
|
||||
)
|
||||
|
||||
mode = models.IntegerField(
|
||||
choices=MODE_CHOICES,
|
||||
default=2,
|
||||
verbose_name='提现模式'
|
||||
)
|
||||
created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'withdraw_config'
|
||||
verbose_name = '提现模式配置'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return f'提现模式: {"自动" if self.mode == 1 else "手动"}'
|
||||
|
||||
|
||||
|
||||
|
||||
class AccountPermission(QModel):
|
||||
"""账号权限表:记录每个账号拥有的权限及开关状态"""
|
||||
account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID')
|
||||
permission_code = models.IntegerField(verbose_name='权限代码')
|
||||
is_enabled = models.BooleanField(default=False, verbose_name='是否开启')
|
||||
|
||||
class Meta:
|
||||
db_table = 'account_permission'
|
||||
verbose_name = '账号权限'
|
||||
verbose_name_plural = '账号权限'
|
||||
unique_together = [['account_id', 'permission_code']] # 一个账号的同一权限只能有一条记录
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.account_id} 权限{self.permission_code}: {'开启' if self.is_enabled else '关闭'}"
|
||||
|
||||
|
||||
|
||||
|
||||
# apps/dingdan/models.py (新增模型)
|
||||
|
||||
class DailyIncomeStat(QModel):
|
||||
"""
|
||||
每日收入统计表 —— 财务看板「今日收入 / 累计收入 / 每日明细」唯一数据源。
|
||||
微信支付成功时由 update_daily_income 写入;勿读 szjilu 表。
|
||||
"""
|
||||
# 日期相关字段(便于快速筛选和分组)
|
||||
date = models.DateField(verbose_name='统计日期', db_index=True)
|
||||
club_id = models.CharField(
|
||||
max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部',
|
||||
)
|
||||
year = models.PositiveSmallIntegerField(verbose_name='年份')
|
||||
month = models.PositiveSmallIntegerField(verbose_name='月份')
|
||||
day = models.PositiveSmallIntegerField(verbose_name='日期')
|
||||
|
||||
# 收入统计
|
||||
total_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='总收入金额')
|
||||
total_count = models.PositiveIntegerField(default=0, 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'
|
||||
verbose_name = '每日收入统计'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
models.Index(fields=['date']),
|
||||
models.Index(fields=['year', 'month', 'day']),
|
||||
models.Index(fields=['club_id', 'date']),
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['date', 'club_id'], name='uniq_daily_income_club_date'),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.date} - 收入{self.total_amount}元 - {self.total_count}笔"
|
||||
|
||||
|
||||
# apps/dingdan/models.py
|
||||
|
||||
class DailyPayoutStat(QModel):
|
||||
"""
|
||||
每日出款统计表 —— 财务看板「今日支出 / 累计支出」唯一数据源。
|
||||
"""
|
||||
date = models.DateField(verbose_name='统计日期', db_index=True)
|
||||
club_id = models.CharField(
|
||||
max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部',
|
||||
)
|
||||
year = models.PositiveSmallIntegerField(verbose_name='年份')
|
||||
month = models.PositiveSmallIntegerField(verbose_name='月份')
|
||||
day = models.PositiveSmallIntegerField(verbose_name='日期')
|
||||
|
||||
total_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='出款总额')
|
||||
total_count = models.PositiveIntegerField(default=0, 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'
|
||||
verbose_name = '每日出款统计'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
models.Index(fields=['date']),
|
||||
models.Index(fields=['year', 'month', 'day']),
|
||||
models.Index(fields=['club_id', 'date']),
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['date', 'club_id'], name='uniq_daily_payout_club_date'),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.date} - 出款{self.total_amount}元 - {self.total_count}笔"
|
||||
|
||||
|
||||
class PlatformIncomeLog(QModel):
|
||||
"""微信入账幂等日志:同一 biz_ref 只记一次 daily_income_stat。"""
|
||||
biz_ref = models.CharField(max_length=64, unique=True, db_index=True, verbose_name='业务单号')
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
amount = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='入账金额')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'platform_income_log'
|
||||
verbose_name = '平台入账幂等日志'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
|
||||
class PopupPage(QModel):
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
page_key = models.CharField(max_length=50, db_index=True, verbose_name='页面标识')
|
||||
name = models.CharField(max_length=100, verbose_name='页面名称')
|
||||
description = models.TextField(blank=True, verbose_name='描述')
|
||||
is_active = models.BooleanField(default=True, verbose_name='是否启用')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
ignore_user_mute = models.BooleanField(default=False, verbose_name='忽略用户今日屏蔽')
|
||||
|
||||
class Meta:
|
||||
db_table = 'popup_page'
|
||||
verbose_name = '弹窗页面'
|
||||
verbose_name_plural = '弹窗页面'
|
||||
unique_together = [['club_id', 'page_key']]
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name} ({self.page_key})'
|
||||
|
||||
class PopupConfig(QModel):
|
||||
STRATEGY_CHOICES = (
|
||||
('once', '仅一次'),
|
||||
('daily', '每日'),
|
||||
('weekly', '每周'),
|
||||
('monthly', '每月'),
|
||||
('yearly', '每年'),
|
||||
('always', '始终'),
|
||||
('count', '周期内次数限制'),
|
||||
)
|
||||
page = models.ForeignKey(PopupPage, on_delete=models.CASCADE, related_name='popups')
|
||||
popup_id = models.CharField(max_length=50, verbose_name='弹窗唯一ID')
|
||||
title = models.CharField(max_length=200, blank=True, verbose_name='标题(后台用)')
|
||||
description = models.TextField(blank=True, verbose_name='描述(后台用)')
|
||||
strategy_type = models.CharField(max_length=20, choices=STRATEGY_CHOICES, default='once')
|
||||
max_count = models.PositiveIntegerField(default=1, verbose_name='周期内最大展示次数')
|
||||
reset_interval = models.CharField(max_length=20, blank=True,
|
||||
choices=(('day','天'),('week','周'),('month','月'),('year','年')),
|
||||
verbose_name='重置周期(count类型时使用)')
|
||||
duration = models.PositiveIntegerField(default=0, verbose_name='强制阅读秒数(0=无强制)')
|
||||
force_even_muted = models.BooleanField(default=False, verbose_name='强制弹出(忽略用户今日屏蔽)')
|
||||
sort_order = models.PositiveIntegerField(default=0, verbose_name='展示顺序')
|
||||
is_active = models.BooleanField(default=True, verbose_name='是否启用')
|
||||
start_time = models.DateTimeField(null=True, blank=True, verbose_name='生效开始时间')
|
||||
end_time = models.DateTimeField(null=True, blank=True, verbose_name='生效结束时间')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'popup_config'
|
||||
verbose_name = '弹窗配置'
|
||||
verbose_name_plural = '弹窗配置'
|
||||
unique_together = ('page', 'popup_id')
|
||||
ordering = ['sort_order', 'id']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.page.name} - {self.popup_id}'
|
||||
|
||||
|
||||
class PopupImage(QModel):
|
||||
popup_config = models.ForeignKey(PopupConfig, on_delete=models.CASCADE, related_name='images')
|
||||
image_url = models.CharField(max_length=500, verbose_name='图片相对路径')
|
||||
text = models.TextField(blank=True, verbose_name='图片上方文字')
|
||||
sort_order = models.PositiveIntegerField(default=0, verbose_name='展示顺序')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'popup_image'
|
||||
verbose_name = '弹窗图片'
|
||||
verbose_name_plural = '弹窗图片'
|
||||
ordering = ['sort_order', 'id']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.popup_config.popup_id} - 图片{self.sort_order}'
|
||||
|
||||
|
||||
class ClubMiniappIcon(QModel):
|
||||
"""小程序功能图标(按俱乐部,固定 icon_key + 存储桶相对路径)。"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
icon_key = models.CharField(max_length=64, db_index=True, verbose_name='图标键')
|
||||
label = models.CharField(max_length=100, blank=True, default='', verbose_name='展示名称')
|
||||
image_path = models.CharField(max_length=500, blank=True, default='', verbose_name='图片相对路径')
|
||||
description = models.CharField(max_length=300, blank=True, default='', verbose_name='说明')
|
||||
is_active = models.BooleanField(default=True, verbose_name='是否启用')
|
||||
is_uploaded = models.BooleanField(default=False, verbose_name='是否已在后台上传')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'club_miniapp_icon'
|
||||
verbose_name = '小程序图标'
|
||||
verbose_name_plural = verbose_name
|
||||
unique_together = [['club_id', 'icon_key']]
|
||||
ordering = ['icon_key']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.club_id}:{self.icon_key}'
|
||||
|
||||
|
||||
class ClubPindao(QModel):
|
||||
"""俱乐部频道配置(每俱乐部一条)。"""
|
||||
club_id = models.CharField(max_length=16, default='xq', unique=True, verbose_name='俱乐部ID')
|
||||
channel_no = models.CharField(max_length=100, blank=True, default='', verbose_name='频道号')
|
||||
title = models.CharField(max_length=100, blank=True, default='频道', verbose_name='标题')
|
||||
is_active = models.BooleanField(default=True, verbose_name='是否启用')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'club_pindao'
|
||||
verbose_name = '俱乐部频道'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.club_id} 频道'
|
||||
|
||||
|
||||
class ClubPindaoImage(QModel):
|
||||
"""频道展示图片(多张)。"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
pindao = models.ForeignKey(
|
||||
ClubPindao, on_delete=models.CASCADE, related_name='images', null=True, blank=True,
|
||||
)
|
||||
image_path = models.CharField(max_length=500, verbose_name='图片相对路径')
|
||||
sort_order = models.PositiveIntegerField(default=0, verbose_name='排序')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'club_pindao_image'
|
||||
verbose_name = '频道图片'
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ['sort_order', 'id']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.club_id} 频道图{self.sort_order}'
|
||||
|
||||
126
config/tasks.py
126
config/tasks.py
@@ -1,85 +1,43 @@
|
||||
"""
|
||||
阿龙电竞 - 收支记录定时任务
|
||||
处理收支记录表的每日清零
|
||||
固定使用ID=1的记录
|
||||
"""
|
||||
from celery import shared_task
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
import logging
|
||||
|
||||
# 导入模型
|
||||
from config.models import Szjilu
|
||||
from utils.celery_utils import log_task_execution, rollback_on_failure
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@shared_task
|
||||
@rollback_on_failure("收支记录每日清零")
|
||||
def daily_sz_reset_task():
|
||||
"""
|
||||
收支记录每日清零 - 每天凌晨0点5分执行
|
||||
清零收支记录表的今日相关字段,固定使用ID=1的记录
|
||||
"""
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# 获取今天的日期
|
||||
today = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
logger.info(f"开始执行收支记录清零任务,日期: {today}")
|
||||
|
||||
# 查询ID=1的收支记录,如果不存在则创建
|
||||
try:
|
||||
szjilu = Szjilu.objects.get(id=1)
|
||||
logger.info(f"找到ID=1的收支记录")
|
||||
except Szjilu.DoesNotExist:
|
||||
# 创建ID=1的初始记录
|
||||
szjilu = Szjilu.objects.create(
|
||||
id=1,
|
||||
zongsy=Decimal('0.00'),
|
||||
zongls=Decimal('0.00'),
|
||||
zongzc=Decimal('0.00'),
|
||||
jrzc=Decimal('0.00'),
|
||||
jrls=Decimal('0.00')
|
||||
)
|
||||
logger.info(f"创建ID=1的收支记录")
|
||||
|
||||
# 记录清零前的值(用于日志)
|
||||
old_jrzc = szjilu.jrzc
|
||||
old_jrls = szjilu.jrls
|
||||
|
||||
# 清零今日字段
|
||||
szjilu.jrzc = Decimal('0.00')
|
||||
szjilu.jrls = Decimal('0.00')
|
||||
szjilu.save(update_fields=['jrzc', 'jrls', 'update_time'])
|
||||
|
||||
logger.info(f"收支记录清零完成,今日支出原值: {old_jrzc},今日流水原值: {old_jrls}")
|
||||
|
||||
result_msg = f"收支记录清零完成 - 日期: {today}, 记录ID: {szjilu.id}"
|
||||
|
||||
log_task_execution("收支记录每日清零", True, result_msg)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"date": today,
|
||||
"record_id": szjilu.id,
|
||||
"jrzc_cleared": True,
|
||||
"jrls_cleared": True,
|
||||
"old_jrzc": str(old_jrzc),
|
||||
"old_jrls": str(old_jrls),
|
||||
"message": result_msg
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"收支记录每日清零失败: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
log_task_execution("收支记录每日清零", False, error_msg)
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"date": datetime.now().strftime('%Y-%m-%d'),
|
||||
"message": error_msg
|
||||
"""
|
||||
收支记录定时任务:每日清零各俱乐部今日流水/今日支出。
|
||||
"""
|
||||
from celery import shared_task
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from jituan.services.szjilu_accounting import reset_all_daily_szjilu
|
||||
from utils.celery_utils import log_task_execution, rollback_on_failure
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@shared_task
|
||||
@rollback_on_failure("收支记录每日清零")
|
||||
def daily_sz_reset_task():
|
||||
"""收支记录每日清零 - 每天凌晨0点5分执行。"""
|
||||
try:
|
||||
today = datetime.now().strftime('%Y-%m-%d')
|
||||
logger.info(f"开始执行收支记录清零任务,日期: {today}")
|
||||
|
||||
updated = reset_all_daily_szjilu()
|
||||
result_msg = f"收支记录清零完成 - 日期: {today}, 俱乐部记录数: {updated}"
|
||||
log_task_execution("收支记录每日清零", True, result_msg)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"date": today,
|
||||
"clubs_updated": updated,
|
||||
"message": result_msg,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"收支记录每日清零失败: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
log_task_execution("收支记录每日清零", False, error_msg)
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"date": datetime.now().strftime('%Y-%m-%d'),
|
||||
"message": error_msg,
|
||||
}
|
||||
@@ -3,8 +3,8 @@ from django.urls import path
|
||||
from .views import ShangpinGonggaoView, AdminUpdateConfigView, AdminUploadImageView, AdminConfigQueryView, \
|
||||
DashouPaihangView,GetAdminZhanghaoView, GetXiugaiJiluView,ShangjiaTianjiaMobanView, ShangjiaMobanListView, \
|
||||
ShangjiaGengxinMobanView, ShangjiaShanchuMobanView, ShangjiaGenerateLinkView,KehuGetDingdanLianjieView, \
|
||||
KehuTianxieDingdanView,GuanZhuAListView, ZuzhangHaibaoView,GuanshiQRCodeView,\
|
||||
GetDynamicConfigView, PopupConfigView, GetWithdrawModeView,CheckPhoneAuthView,\
|
||||
KehuTianxieDingdanView, GuanZhuAListView, ZuzhangHaibaoView,GuanshiQRCodeView,\
|
||||
GetDynamicConfigView, HaibaoPeizhiView, PopupConfigView, GetWithdrawModeView,CheckPhoneAuthView,\
|
||||
ShangjiaLianjieListView
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ urlpatterns = [
|
||||
path('guanshiewm', GuanshiQRCodeView.as_view(), name='管事生成二维码'),
|
||||
path('zuzhanghb', ZuzhangHaibaoView.as_view(), name='组长二维码'),
|
||||
path('qdpzhq', GetDynamicConfigView.as_view(), name='小程序获取配置'),
|
||||
path('haibaopz', HaibaoPeizhiView.as_view(), name='推广海报配置'),
|
||||
|
||||
path('tanchuang', PopupConfigView.as_view(), name='微信小程序获取弹窗数据'),
|
||||
path('hqtxzsym', GetWithdrawModeView.as_view(), name='小程序获取提现方式'),
|
||||
|
||||
987
config/views.py
987
config/views.py
File diff suppressed because it is too large
Load Diff
82
create_rbac_tables.sql
Normal file
82
create_rbac_tables.sql
Normal file
@@ -0,0 +1,82 @@
|
||||
-- ============================================================
|
||||
-- RBAC 表创建脚本
|
||||
-- 仅创建生产数据库中缺失的表,已存在的表会被跳过
|
||||
-- 执行方式: mysql -u root -p xaio_cheng_xu < create_rbac_tables.sql
|
||||
-- 注意: 索引和外键单独处理,避免重复创建报错
|
||||
-- ============================================================
|
||||
|
||||
-- ============================================================
|
||||
-- Part 1: backend RBAC 表(小写表名,verify_kefu_permission 使用)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `role` (
|
||||
`role_id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
`role_code` varchar(50) NOT NULL UNIQUE,
|
||||
`role_name` varchar(50) NOT NULL,
|
||||
`description` varchar(200) NOT NULL,
|
||||
`create_time` datetime(6) NOT NULL,
|
||||
`update_time` datetime(6) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `permission` (
|
||||
`perm_id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
`perm_code` varchar(100) NOT NULL UNIQUE,
|
||||
`perm_name` varchar(100) NOT NULL,
|
||||
`description` varchar(200) NOT NULL,
|
||||
`create_time` datetime(6) NOT NULL,
|
||||
`update_time` datetime(6) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `user_role` (
|
||||
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
`account_id` varchar(11) NOT NULL,
|
||||
`create_time` datetime(6) NOT NULL,
|
||||
`update_time` datetime(6) NOT NULL,
|
||||
`role_id` integer NOT NULL,
|
||||
UNIQUE KEY `user_role_account_id_role_id_uniq` (`account_id`, `role_id`)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `role_permission` (
|
||||
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
`create_time` datetime(6) NOT NULL,
|
||||
`permission_id` integer NOT NULL,
|
||||
`role_id` integer NOT NULL,
|
||||
UNIQUE KEY `role_permission_role_id_permission_id_uniq` (`role_id`, `permission_id`)
|
||||
);
|
||||
|
||||
-- backend 表索引(忽略已存在的错误)
|
||||
CREATE INDEX `user_role_account_id_idx` ON `user_role` (`account_id`);
|
||||
|
||||
-- backend 外键约束
|
||||
ALTER TABLE `user_role` ADD CONSTRAINT `user_role_role_fk` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`);
|
||||
ALTER TABLE `role_permission` ADD CONSTRAINT `role_permission_permission_fk` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`perm_id`);
|
||||
ALTER TABLE `role_permission` ADD CONSTRAINT `role_permission_role_fk` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`);
|
||||
|
||||
-- ============================================================
|
||||
-- Part 2: gvsdsdk RBAC 表(大写表名,UUID 主键)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `AccountActivation` (`AccountActivationCode` BINARY(6) NOT NULL PRIMARY KEY, `UserUUID` BINARY(16) NOT NULL, `CreateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `EphemeralToken` (`ELTUUID` BINARY(16) NOT NULL PRIMARY KEY, `ELTUserUUID` BINARY(16) NOT NULL, `ELTCreateTime` datetime(6) NOT NULL, `ELTEndTime` datetime(6) NOT NULL, `ELTType` smallint NOT NULL, `ELTAllowIP` json NOT NULL, `ELTStatus` smallint NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `Password` (`UserUUID` BINARY(16) NOT NULL PRIMARY KEY, `Password` varchar(1024) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `Permission` (`PermUUID` BINARY(16) NOT NULL PRIMARY KEY, `TenantUUID` BINARY(16) NOT NULL, `PermName` varchar(128) NOT NULL, `PermCode` varchar(128) NOT NULL, `PermCategory` varchar(64) NOT NULL, `PermStatus` smallint NOT NULL, `PermDesc` varchar(512) NOT NULL, `CreateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `Position` (`PositionUUID` BINARY(16) NOT NULL PRIMARY KEY, `TenantUUID` BINARY(16) NOT NULL, `PositionName` varchar(128) NOT NULL, `PositionCode` varchar(64) NOT NULL, `PositionLevel` smallint NOT NULL, `PositionStatus` smallint NOT NULL, `PositionDesc` varchar(512) NOT NULL, `CreateTime` datetime(6) NOT NULL, `UpdateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `PositionTemplate` (`PositionTemplateUUID` BINARY(16) NOT NULL PRIMARY KEY, `TenantUUID` BINARY(16) NOT NULL, `TemplateName` varchar(128) NOT NULL, `TemplateCode` varchar(64) NOT NULL, `TemplateStatus` smallint NOT NULL, `TemplateDesc` varchar(512) NOT NULL, `CreateTime` datetime(6) NOT NULL, `UpdateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `PositionTemplatePermission` (`PTPermUUID` BINARY(16) NOT NULL PRIMARY KEY, `PositionTemplateUUID` BINARY(16) NOT NULL, `PermUUID` BINARY(16) NOT NULL, `CreateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `Role` (`RoleUUID` BINARY(16) NOT NULL PRIMARY KEY, `TenantUUID` BINARY(16) NOT NULL, `RoleName` varchar(128) NOT NULL, `RoleType` smallint NOT NULL, `RoleStatus` smallint NOT NULL, `AssignScope` smallint NOT NULL, `AssignScopeUUID` BINARY(16) NULL, `RoleDesc` varchar(512) NOT NULL, `CreateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `RolePermission` (`RolePermUUID` BINARY(16) NOT NULL PRIMARY KEY, `RoleUUID` BINARY(16) NOT NULL, `PermUUID` BINARY(16) NOT NULL, `CreateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `RoleShop` (`RoleShopUUID` BINARY(16) NOT NULL PRIMARY KEY, `RoleUUID` BINARY(16) NOT NULL, `ShopUUID` BINARY(16) NOT NULL, `IsMainShop` smallint NOT NULL, `CreateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `Shop` (`ShopUUID` BINARY(16) NOT NULL PRIMARY KEY, `CompanyUUID` BINARY(16) NOT NULL, `ShopName` varchar(128) NOT NULL, `ShopCode` varchar(64) NOT NULL, `ShopType` smallint NOT NULL, `ShopStatus` smallint NOT NULL, `ShopAddress` varchar(512) NOT NULL, `ShopContact` varchar(128) NOT NULL, `ShopPhone` varchar(32) NOT NULL, `ShopDesc` varchar(512) NOT NULL, `CreateTime` datetime(6) NOT NULL, `UpdateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `User2FA` (`User2FAUUID` BINARY(16) NOT NULL PRIMARY KEY, `UserUUID` BINARY(16) NOT NULL, `SecretKey` varchar(64) NOT NULL, `IsEnabled` smallint NOT NULL, `Verified` smallint NOT NULL, `LastUsedAt` datetime(6) NULL, `CreateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `UserABAC` (`UserABACUUID` BINARY(16) NOT NULL PRIMARY KEY, `UserUUID` BINARY(16) NOT NULL, `HierarchicalPermissionIdentifiers` varchar(256) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `UserAvatar` (`UserUUID` BINARY(16) NOT NULL PRIMARY KEY, `UserAvatar` longblob NOT NULL, `AvatarUploadTime` datetime(6) NOT NULL, `AvatarFileType` varchar(16) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `UserDept` (`UserDeptUUID` BINARY(16) NOT NULL PRIMARY KEY, `UserUUID` BINARY(16) NOT NULL, `DeptUUID` BINARY(16) NOT NULL, `PositionUUID` BINARY(16) NULL, `IsMainDept` smallint NOT NULL, `CreateTime` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `UserLoginLog` (`LogUUID` BINARY(16) NOT NULL PRIMARY KEY, `UserUUID` BINARY(16) NOT NULL, `LoginIP` varchar(39) NOT NULL, `LoginPlatform` varchar(32) NOT NULL, `LoginStatus` smallint NOT NULL, `LoginTime` datetime(6) NOT NULL, `UserAgent` varchar(512) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `UserPending2FA` (`PendingUUID` BINARY(16) NOT NULL PRIMARY KEY, `UserUUID` BINARY(16) NOT NULL UNIQUE, `SecretKey` varchar(64) NOT NULL, `ExpireAt` datetime(6) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS `UserRole` (`UserRoleUUID` BINARY(16) NOT NULL PRIMARY KEY, `UserUUID` BINARY(16) NOT NULL, `RoleUUID` BINARY(16) NOT NULL, `CompanyUUID` BINARY(16) NOT NULL, `CreateTime` datetime(6) NOT NULL);
|
||||
|
||||
-- gvsdsdk 索引
|
||||
CREATE INDEX `EphemELTUserUUID_idx` ON `EphemeralToken` (`ELTUserUUID`);
|
||||
CREATE INDEX `EphemELTStatus_idx` ON `EphemeralToken` (`ELTStatus`);
|
||||
CREATE INDEX `User2FA_UserUUID_idx` ON `User2FA` (`UserUUID`);
|
||||
CREATE INDEX `UserLoginLog_UserUUID_idx` ON `UserLoginLog` (`UserUUID`);
|
||||
1
data/ai_chat/context.json
Normal file
1
data/ai_chat/context.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
73
deploy/pre_migrate_dedupe.sql
Normal file
73
deploy/pre_migrate_dedupe.sql
Normal file
@@ -0,0 +1,73 @@
|
||||
-- 部署 jituan 前若 migrate 报 Duplicate entry 唯一约束冲突时使用。
|
||||
-- 不要用 mysql 命令行(1Panel MySQL 不在本机 socket),请用:
|
||||
-- python manage.py dbshell < deploy/pre_migrate_dedupe.sql
|
||||
-- 或半路失败的 config.0006:
|
||||
-- python manage.py recover_config_0006
|
||||
-- python manage.py migrate config 0006 --fake
|
||||
-- python manage.py migrate
|
||||
|
||||
-- config.0006 tupianpeizhi
|
||||
DELETE FROM tupianpeizhi
|
||||
WHERE id NOT IN (
|
||||
SELECT keep_id FROM (
|
||||
SELECT MAX(id) AS keep_id FROM tupianpeizhi GROUP BY club_id, ImageType
|
||||
) AS t
|
||||
);
|
||||
|
||||
-- config.0006 popup_page
|
||||
DELETE FROM popup_page
|
||||
WHERE id NOT IN (
|
||||
SELECT keep_id FROM (
|
||||
SELECT MAX(id) AS keep_id FROM popup_page GROUP BY club_id, page_key
|
||||
) AS t
|
||||
);
|
||||
|
||||
-- products.0005 duoci_fenhong
|
||||
DELETE FROM duoci_fenhong
|
||||
WHERE id NOT IN (
|
||||
SELECT keep_id FROM (
|
||||
SELECT MAX(id) AS keep_id FROM duoci_fenhong GROUP BY club_id, huiyuan, yonghuid, cishu
|
||||
) AS t
|
||||
);
|
||||
|
||||
-- backend.0003 提现日统计
|
||||
DELETE FROM tixian_ri_tongji
|
||||
WHERE id NOT IN (
|
||||
SELECT keep_id FROM (
|
||||
SELECT MAX(id) AS keep_id FROM tixian_ri_tongji GROUP BY club_id, Date, WithdrawalType
|
||||
) AS t
|
||||
);
|
||||
|
||||
-- backend.0004 角色日统计
|
||||
DELETE FROM shangjia_ri_tongji
|
||||
WHERE id NOT IN (
|
||||
SELECT keep_id FROM (
|
||||
SELECT MAX(id) AS keep_id FROM shangjia_ri_tongji GROUP BY club_id, MerchantID, Date
|
||||
) AS t
|
||||
);
|
||||
DELETE FROM dashou_ri_tongji
|
||||
WHERE id NOT IN (
|
||||
SELECT keep_id FROM (
|
||||
SELECT MAX(id) AS keep_id FROM dashou_ri_tongji GROUP BY club_id, PlayerID, Date
|
||||
) AS t
|
||||
);
|
||||
DELETE FROM zuzhang_ri_tongji
|
||||
WHERE id NOT IN (
|
||||
SELECT keep_id FROM (
|
||||
SELECT MAX(id) AS keep_id FROM zuzhang_ri_tongji GROUP BY club_id, LeaderID, Date
|
||||
) AS t
|
||||
);
|
||||
DELETE FROM guanshi_ri_tongji
|
||||
WHERE id NOT IN (
|
||||
SELECT keep_id FROM (
|
||||
SELECT MAX(id) AS keep_id FROM guanshi_ri_tongji GROUP BY club_id, ManagerID, Date
|
||||
) AS t
|
||||
);
|
||||
DELETE FROM guanshi_xufei_ri_tongji
|
||||
WHERE id NOT IN (
|
||||
SELECT keep_id FROM (
|
||||
SELECT MAX(id) AS keep_id FROM guanshi_xufei_ri_tongji GROUP BY club_id, ManagerID, Date
|
||||
) AS t
|
||||
);
|
||||
|
||||
-- szjilu 应只有 1 行;多行时勿自动删,需人工核对 TotalIncome 后处理
|
||||
163
find.py
163
find.py
@@ -1,163 +0,0 @@
|
||||
"""
|
||||
REPL 模式文件内容搜索工具
|
||||
支持正则或纯文本搜索,默认搜索当前目录
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# 硬编码排除目录
|
||||
EXCLUDE_DIRS = {
|
||||
'.git', '.vs', 'node_modules', '__pycache__', '.idea',
|
||||
'miniprogram_npm', '_backup_pre_redesign',
|
||||
}
|
||||
|
||||
# 排除的文件扩展名
|
||||
EXCLUDE_EXTS = {
|
||||
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.bmp',
|
||||
'.mp3', '.mp4', '.wav', '.avi',
|
||||
'.zip', '.rar', '.7z', '.tar', '.gz',
|
||||
'.exe', '.dll', '.so', '.dylib',
|
||||
'.pyc', '.pyo', '.class', '.o', '.obj',
|
||||
'.db', '.sqlite', '.vsidx',
|
||||
}
|
||||
|
||||
# 默认搜索根目录
|
||||
DEFAULT_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def should_skip(dirpath):
|
||||
"""判断目录是否应跳过"""
|
||||
parts = dirpath.replace('\\', '/').split('/')
|
||||
return any(p in EXCLUDE_DIRS for p in parts)
|
||||
|
||||
|
||||
def search(pattern, root=DEFAULT_ROOT, use_regex=True, max_results=50):
|
||||
"""搜索文件内容"""
|
||||
try:
|
||||
if use_regex:
|
||||
compiled = re.compile(pattern, re.IGNORECASE)
|
||||
except re.error as e:
|
||||
print(f' 正则语法错误: {e}')
|
||||
return
|
||||
|
||||
count = 0
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
# 就地修改 dirnames 以跳过排除目录
|
||||
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
|
||||
|
||||
for fname in filenames:
|
||||
ext = os.path.splitext(fname)[1].lower()
|
||||
if ext in EXCLUDE_EXTS:
|
||||
continue
|
||||
|
||||
fpath = os.path.join(dirpath, fname)
|
||||
relpath = os.path.relpath(fpath, root)
|
||||
try:
|
||||
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
for lineno, line in enumerate(f, 1):
|
||||
line_stripped = line.rstrip('\n\r')
|
||||
if use_regex:
|
||||
match = compiled.search(line_stripped)
|
||||
if match:
|
||||
highlight = line_stripped[:match.start()] + \
|
||||
f'>>> {match.group()} <<<' + \
|
||||
line_stripped[match.end():]
|
||||
print(f' {relpath}:{lineno} {highlight}')
|
||||
count += 1
|
||||
else:
|
||||
if pattern.lower() in line_stripped.lower():
|
||||
print(f' {relpath}:{lineno} {line_stripped}')
|
||||
count += 1
|
||||
|
||||
if count >= max_results:
|
||||
print(f'\n ... 已达上限 {max_results} 条,停止')
|
||||
return
|
||||
except (PermissionError, OSError):
|
||||
continue
|
||||
|
||||
if count == 0:
|
||||
print(' 未找到匹配内容')
|
||||
else:
|
||||
print(f'\n 共 {count} 条匹配')
|
||||
|
||||
|
||||
def main():
|
||||
print('=== 文件内容搜索工具 ===')
|
||||
print(f'搜索根目录: {DEFAULT_ROOT}')
|
||||
print(f'排除目录: {EXCLUDE_DIRS}')
|
||||
print()
|
||||
print('命令:')
|
||||
print(' /regex <pattern> 正则搜索')
|
||||
print(' /text <text> 纯文本搜索')
|
||||
print(' /max <n> 设置结果上限(默认50)')
|
||||
print(' /dir <path> 切换搜索目录')
|
||||
print(' /help 显示帮助')
|
||||
print(' /quit 退出')
|
||||
print()
|
||||
print('直接输入内容默认使用正则搜索')
|
||||
print()
|
||||
|
||||
root = DEFAULT_ROOT
|
||||
use_regex = True
|
||||
max_results = 50
|
||||
|
||||
while True:
|
||||
try:
|
||||
raw = input('find> ').strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print('\n再见')
|
||||
break
|
||||
|
||||
if not raw:
|
||||
continue
|
||||
|
||||
if raw == '/quit':
|
||||
print('再见')
|
||||
break
|
||||
|
||||
if raw == '/help':
|
||||
print(' /regex <pattern> 正则搜索')
|
||||
print(' /text <text> 纯文本搜索')
|
||||
print(' /max <n> 设置结果上限')
|
||||
print(' /dir <path> 切换搜索目录')
|
||||
print(' /help 显示帮助')
|
||||
print(' /quit 退出')
|
||||
print(' 直接输入内容默认使用正则搜索')
|
||||
continue
|
||||
|
||||
if raw.startswith('/regex '):
|
||||
pattern = raw[7:].strip()
|
||||
if pattern:
|
||||
search(pattern, root, use_regex=True, max_results=max_results)
|
||||
continue
|
||||
|
||||
if raw.startswith('/text '):
|
||||
pattern = raw[6:].strip()
|
||||
if pattern:
|
||||
search(pattern, root, use_regex=False, max_results=max_results)
|
||||
continue
|
||||
|
||||
if raw.startswith('/max '):
|
||||
try:
|
||||
max_results = int(raw[5:].strip())
|
||||
print(f' 结果上限设为 {max_results}')
|
||||
except ValueError:
|
||||
print(' 请输入数字')
|
||||
continue
|
||||
|
||||
if raw.startswith('/dir '):
|
||||
new_dir = raw[5:].strip()
|
||||
if os.path.isdir(new_dir):
|
||||
root = os.path.abspath(new_dir)
|
||||
print(f' 搜索目录切换为: {root}')
|
||||
else:
|
||||
print(f' 目录不存在: {new_dir}')
|
||||
continue
|
||||
|
||||
# 默认正则搜索
|
||||
search(raw, root, use_regex=use_regex, max_results=max_results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,35 +0,0 @@
|
||||
"""
|
||||
修复 django_migrations 表中的旧 app 名称引用。
|
||||
在服务器上执行: python manage.py shell < fix_migration_history.py
|
||||
或: python manage.py shell
|
||||
然后粘贴此脚本内容
|
||||
"""
|
||||
from django.db import connection
|
||||
|
||||
APP_NAME_MAPPINGS = {
|
||||
'houtai': 'backend',
|
||||
'yonghu': 'users',
|
||||
'dingdan': 'orders',
|
||||
'peizhi': 'config',
|
||||
'shangpin': 'products',
|
||||
'shangdian': 'shop',
|
||||
'dengji': 'rank',
|
||||
}
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
for old_name, new_name in APP_NAME_MAPPINGS.items():
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM django_migrations WHERE app = %s",
|
||||
[old_name]
|
||||
)
|
||||
count = cursor.fetchone()[0]
|
||||
if count > 0:
|
||||
cursor.execute(
|
||||
"UPDATE django_migrations SET app = %s WHERE app = %s",
|
||||
[new_name, old_name]
|
||||
)
|
||||
print(f"已更新 {count} 条记录: {old_name} -> {new_name}")
|
||||
else:
|
||||
print(f"无需更新: {old_name} (无记录)")
|
||||
|
||||
print("\n修复完成!请运行 python manage.py migrate")
|
||||
@@ -13,7 +13,8 @@ class ELTAuthentication:
|
||||
except (ValueError, AttributeError):
|
||||
return None
|
||||
|
||||
from gvsdsdk.models import User, EphemeralToken
|
||||
from gvsdsdk.models import EphemeralToken
|
||||
from gvsds.apps.core.models import User
|
||||
import datetime
|
||||
|
||||
try:
|
||||
|
||||
54
gvsdsdk/auth/jwt_auth.py
Normal file
54
gvsdsdk/auth/jwt_auth.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""自定义 JWT 认证 — 向后兼容旧 token 的 user_id claim"""
|
||||
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from rest_framework_simplejwt.exceptions import InvalidToken, AuthenticationFailed
|
||||
from rest_framework_simplejwt.settings import api_settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class CompatibleJWTAuthentication(JWTAuthentication):
|
||||
"""
|
||||
兼容旧 token 的 JWT 认证类。
|
||||
|
||||
旧 token(USER_ID_CLAIM='user_id' 时生成)payload 中使用 'user_id' claim,
|
||||
值为 user.id(UUID 字符串)。
|
||||
新 token(USER_ID_CLAIM='UserUID' 时生成)payload 中使用 'UserUID' claim,
|
||||
值为 user.UserUID(7位业务ID)。
|
||||
|
||||
此类先尝试新 claim,找不到时回退到旧 claim,并用对应字段查库。
|
||||
"""
|
||||
|
||||
def get_user(self, validated_token):
|
||||
# 1. 先尝试当前配置的 USER_ID_CLAIM
|
||||
user_id = validated_token.get(api_settings.USER_ID_CLAIM)
|
||||
lookup_field = api_settings.USER_ID_FIELD
|
||||
|
||||
if user_id is None:
|
||||
# 2. 回退:尝试旧 token 中的 'user_id' claim
|
||||
user_id = validated_token.get('user_id')
|
||||
if user_id is None:
|
||||
raise InvalidToken(
|
||||
{'detail': _('令牌未包含用户标识符'), 'code': 'token_not_valid'}
|
||||
)
|
||||
# 旧 token 的 user_id 来自 user.id(即 UserUUID),
|
||||
# 需要用 UserUUID 字段查库
|
||||
lookup_field = 'UserUUID'
|
||||
|
||||
try:
|
||||
user = self.user_model.objects.get(**{lookup_field: user_id})
|
||||
except self.user_model.DoesNotExist:
|
||||
raise AuthenticationFailed(_('用户不存在'), code='user_not_found')
|
||||
|
||||
if not user.is_active:
|
||||
raise AuthenticationFailed(_('用户已被禁用'), code='user_inactive')
|
||||
|
||||
if api_settings.CHECK_REVOKE_TOKEN:
|
||||
from rest_framework_simplejwt.utils import get_md5_hash_password
|
||||
if validated_token.get(
|
||||
api_settings.REVOKE_TOKEN_CLAIM
|
||||
) != get_md5_hash_password(user.password):
|
||||
raise AuthenticationFailed(
|
||||
_('用户密码已更改'), code='password_changed'
|
||||
)
|
||||
|
||||
return user
|
||||
@@ -87,32 +87,39 @@ class RoleManager:
|
||||
@staticmethod
|
||||
def GetUsersForRole(RoleUUID):
|
||||
"""获取拥有指定角色的所有用户"""
|
||||
from gvsdsdk.models import User
|
||||
from gvsds.apps.core.models import User
|
||||
user_uuids = UserRole.objects.filter(RoleUUID=RoleUUID).values_list('UserUUID', flat=True)
|
||||
return User.objects.filter(UserUUID__in=user_uuids)
|
||||
|
||||
@staticmethod
|
||||
def AssignPermission(RoleUUID, PermUUID):
|
||||
"""为角色分配权限"""
|
||||
def AssignPermission(RoleUUID, PermUUID, CompanyUUID):
|
||||
"""为角色分配权限(指定公司)"""
|
||||
rp = RolePermission(
|
||||
RolePermUUID=uuid.uuid4().bytes,
|
||||
RoleUUID=RoleUUID,
|
||||
PermUUID=PermUUID,
|
||||
CompanyUUID=CompanyUUID,
|
||||
CreateTime=datetime.datetime.utcnow(),
|
||||
)
|
||||
rp.save()
|
||||
return rp
|
||||
|
||||
@staticmethod
|
||||
def RemovePermission(RoleUUID, PermUUID):
|
||||
def RemovePermission(RoleUUID, PermUUID, CompanyUUID=None):
|
||||
"""移除角色的指定权限"""
|
||||
deleted, _ = RolePermission.objects.filter(RoleUUID=RoleUUID, PermUUID=PermUUID).delete()
|
||||
qs = RolePermission.objects.filter(RoleUUID=RoleUUID, PermUUID=PermUUID)
|
||||
if CompanyUUID is not None:
|
||||
qs = qs.filter(CompanyUUID=CompanyUUID)
|
||||
deleted, _ = qs.delete()
|
||||
return deleted > 0
|
||||
|
||||
@staticmethod
|
||||
def GetPermissionsForRole(RoleUUID):
|
||||
"""获取角色的所有权限"""
|
||||
perm_uuids = RolePermission.objects.filter(RoleUUID=RoleUUID).values_list('PermUUID', flat=True)
|
||||
def GetPermissionsForRole(RoleUUID, CompanyUUID=None):
|
||||
"""获取角色的所有权限(可按公司过滤)"""
|
||||
qs = RolePermission.objects.filter(RoleUUID=RoleUUID)
|
||||
if CompanyUUID is not None:
|
||||
qs = qs.filter(CompanyUUID=CompanyUUID)
|
||||
perm_uuids = qs.values_list('PermUUID', flat=True)
|
||||
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from gvsdsdk.models import User, UserRole, Role, RolePermission, Permission, UserABAC
|
||||
from gvsds.apps.core.models import User
|
||||
from gvsdsdk.models import UserRole, Role, RolePermission, Permission, UserABAC
|
||||
from rest_framework.permissions import BasePermission
|
||||
|
||||
|
||||
@@ -48,7 +49,7 @@ def PermissionVerifier(permission, attributes, user=None, request=None):
|
||||
"""权限验证器——混合 ABAC + RBAC 检查
|
||||
|
||||
RBAC 仅检查当前角色(CurrentRoleUUID Cookie 指定的角色)的权限,
|
||||
通过 RolePermission → Permission 链路查询。
|
||||
通过 RolePermission → Permission 链路查询,按当前公司过滤。
|
||||
超级管理员/员工直接放行。
|
||||
"""
|
||||
if not permission:
|
||||
@@ -63,6 +64,7 @@ def PermissionVerifier(permission, attributes, user=None, request=None):
|
||||
return True
|
||||
|
||||
current_role = getattr(request, 'current_role', None) if request else None
|
||||
current_company = getattr(request, 'current_company_uuid', None) if request else None
|
||||
|
||||
for attr in attributes:
|
||||
# ABAC 检查
|
||||
@@ -73,13 +75,16 @@ def PermissionVerifier(permission, attributes, user=None, request=None):
|
||||
if abac_match:
|
||||
continue
|
||||
|
||||
# RBAC 检查:仅检查当前角色
|
||||
# RBAC 检查:仅检查当前角色,按公司过滤权限
|
||||
if current_role and isinstance(current_role, Role):
|
||||
if current_role.RoleStatus != 1:
|
||||
return False
|
||||
perm_uuids = RolePermission.objects.filter(
|
||||
rp_qs = RolePermission.objects.filter(
|
||||
RoleUUID=current_role.RoleUUID
|
||||
).values_list('PermUUID', flat=True)
|
||||
)
|
||||
if current_company:
|
||||
rp_qs = rp_qs.filter(CompanyUUID=current_company)
|
||||
perm_uuids = rp_qs.values_list('PermUUID', flat=True)
|
||||
rbac_result = Permission.objects.filter(
|
||||
PermUUID__in=perm_uuids,
|
||||
PermCode=attr,
|
||||
@@ -94,9 +99,12 @@ def PermissionVerifier(permission, attributes, user=None, request=None):
|
||||
RoleUUID__in=user_role_uuids,
|
||||
RoleStatus=1,
|
||||
).values_list('RoleUUID', flat=True)
|
||||
perm_uuids = RolePermission.objects.filter(
|
||||
rp_qs = RolePermission.objects.filter(
|
||||
RoleUUID__in=active_role_uuids
|
||||
).values_list('PermUUID', flat=True)
|
||||
)
|
||||
if current_company:
|
||||
rp_qs = rp_qs.filter(CompanyUUID=current_company)
|
||||
perm_uuids = rp_qs.values_list('PermUUID', flat=True)
|
||||
rbac_result = Permission.objects.filter(
|
||||
PermUUID__in=perm_uuids,
|
||||
PermCode=attr,
|
||||
|
||||
@@ -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'])
|
||||
|
||||
1662
gvsdsdk/fluent.py
1662
gvsdsdk/fluent.py
File diff suppressed because it is too large
Load Diff
30
gvsdsdk/management/commands/hash_passwords.py
Normal file
30
gvsdsdk/management/commands/hash_passwords.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""将 User 表中明文密码迁移为 bcrypt 哈希"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '将 User 表中所有明文密码迁移为 bcrypt 哈希'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# 筛选非空且非 bcrypt 哈希的密码
|
||||
users = User.objects.exclude(UserPassword='')
|
||||
updated = 0
|
||||
skipped = 0
|
||||
|
||||
for user in users.iterator():
|
||||
stored = user.UserPassword or ''
|
||||
# 已是 bcrypt 哈希则跳过
|
||||
if stored.startswith(('$2b$', '$2a$')) and len(stored) == 60:
|
||||
skipped += 1
|
||||
continue
|
||||
# 明文密码 → bcrypt 哈希
|
||||
user.SetPassword(stored)
|
||||
user.save(update_fields=['UserPassword'])
|
||||
updated += 1
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'完成:{updated} 条明文密码已转为 bcrypt,{skipped} 条已是哈希跳过'
|
||||
))
|
||||
@@ -1,8 +1,8 @@
|
||||
# Generated by Django 6.0.5 on 2026-06-13 11:43
|
||||
# Generated by Django 4.2.27 on 2026-06-19 18:42
|
||||
|
||||
import datetime
|
||||
import gvsdsdk.models
|
||||
from django.db import migrations, models
|
||||
import gvsdsdk.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -16,9 +16,9 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='AccountActivation',
|
||||
fields=[
|
||||
('AccountActivationCode', gvsdsdk.models.FixedBinaryField(binary_length=6, db_column='AccountActivationCode', max_length=6, primary_key=True, serialize=False)),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID')),
|
||||
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
|
||||
('AccountActivationCode', gvsdsdk.models.FixedBinaryField(binary_length=6, db_column='AccountActivationCode', max_length=6, primary_key=True, serialize=False, verbose_name='激活码')),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', verbose_name='用户UUID')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '账户激活',
|
||||
@@ -30,13 +30,13 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='EphemeralToken',
|
||||
fields=[
|
||||
('ELTUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ELTUUID', primary_key=True, serialize=False)),
|
||||
('ELTUserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ELTUserUUID', db_index=True)),
|
||||
('ELTCreateTime', models.DateTimeField(db_column='ELTCreateTime', default=datetime.datetime.utcnow)),
|
||||
('ELTEndTime', models.DateTimeField(db_column='ELTEndTime', default=gvsdsdk.models._elt_end_time_default)),
|
||||
('ELTType', models.SmallIntegerField(db_column='ELTType', default=0)),
|
||||
('ELTAllowIP', models.JSONField(db_column='ELTAllowIP', default=list)),
|
||||
('ELTStatus', models.SmallIntegerField(db_column='ELTStatus', db_index=True, default=1)),
|
||||
('ELTUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ELTUUID', primary_key=True, serialize=False, verbose_name='令牌UUID')),
|
||||
('ELTUserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ELTUserUUID', db_index=True, verbose_name='用户UUID')),
|
||||
('ELTCreateTime', models.DateTimeField(db_column='ELTCreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
('ELTEndTime', models.DateTimeField(db_column='ELTEndTime', default=gvsdsdk.models._EltEndTimeDefault, verbose_name='过期时间')),
|
||||
('ELTType', models.SmallIntegerField(db_column='ELTType', default=0, verbose_name='令牌类型')),
|
||||
('ELTAllowIP', models.JSONField(db_column='ELTAllowIP', default=list, verbose_name='允许IP')),
|
||||
('ELTStatus', models.SmallIntegerField(db_column='ELTStatus', db_index=True, default=1, verbose_name='状态')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '临时令牌',
|
||||
@@ -48,8 +48,8 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='Password',
|
||||
fields=[
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False)),
|
||||
('Password', models.CharField(db_column='Password', max_length=1024)),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False, verbose_name='用户UUID')),
|
||||
('Password', models.CharField(db_column='Password', max_length=1024, verbose_name='密码哈希')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '密码',
|
||||
@@ -61,12 +61,14 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='Permission',
|
||||
fields=[
|
||||
('PermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PermUUID', primary_key=True, serialize=False)),
|
||||
('PermName', models.CharField(db_column='PermName', max_length=128)),
|
||||
('PermCode', models.CharField(db_column='PermCode', max_length=128)),
|
||||
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')),
|
||||
('PermStatus', models.SmallIntegerField(db_column='PermStatus', default=1)),
|
||||
('PermDesc', models.CharField(db_column='PermDesc', default='', max_length=512)),
|
||||
('PermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PermUUID', primary_key=True, serialize=False, verbose_name='权限UUID')),
|
||||
('TenantUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='TenantUUID', verbose_name='租户UUID')),
|
||||
('PermName', models.CharField(db_column='PermName', max_length=128, verbose_name='权限名称')),
|
||||
('PermCode', models.CharField(db_column='PermCode', max_length=128, verbose_name='权限键')),
|
||||
('PermCategory', models.CharField(db_column='PermCategory', default='', max_length=64, verbose_name='权限分类')),
|
||||
('PermStatus', models.SmallIntegerField(db_column='PermStatus', default=1, verbose_name='是否启用')),
|
||||
('PermDesc', models.CharField(db_column='PermDesc', default='', max_length=512, verbose_name='描述')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '权限',
|
||||
@@ -75,16 +77,72 @@ class Migration(migrations.Migration):
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Position',
|
||||
fields=[
|
||||
('PositionUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionUUID', primary_key=True, serialize=False, verbose_name='职务UUID')),
|
||||
('TenantUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='TenantUUID', verbose_name='租户UUID')),
|
||||
('PositionName', models.CharField(db_column='PositionName', max_length=128, verbose_name='职务名称')),
|
||||
('PositionCode', models.CharField(db_column='PositionCode', default='', max_length=64, verbose_name='职务编码')),
|
||||
('PositionLevel', models.SmallIntegerField(db_column='PositionLevel', default=0, verbose_name='职务级别')),
|
||||
('PositionStatus', models.SmallIntegerField(db_column='PositionStatus', default=1, verbose_name='状态')),
|
||||
('PositionDesc', models.CharField(db_column='PositionDesc', default='', max_length=512, verbose_name='描述')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '人事职务标签',
|
||||
'verbose_name_plural': '人事职务标签',
|
||||
'db_table': 'Position',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PositionTemplate',
|
||||
fields=[
|
||||
('PositionTemplateUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionTemplateUUID', primary_key=True, serialize=False, verbose_name='岗位模板UUID')),
|
||||
('TenantUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='TenantUUID', verbose_name='租户UUID')),
|
||||
('TemplateName', models.CharField(db_column='TemplateName', max_length=128, verbose_name='模板名称')),
|
||||
('TemplateCode', models.CharField(db_column='TemplateCode', default='', max_length=64, verbose_name='模板编码')),
|
||||
('TemplateStatus', models.SmallIntegerField(db_column='TemplateStatus', default=1, verbose_name='模板状态')),
|
||||
('TemplateDesc', models.CharField(db_column='TemplateDesc', default='', max_length=512, verbose_name='模板描述')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '岗位模板',
|
||||
'verbose_name_plural': '岗位模板',
|
||||
'db_table': 'PositionTemplate',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PositionTemplatePermission',
|
||||
fields=[
|
||||
('PTPermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PTPermUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
|
||||
('PositionTemplateUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionTemplateUUID', verbose_name='岗位模板UUID')),
|
||||
('PermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PermUUID', verbose_name='权限UUID')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '岗位模板权限关联',
|
||||
'verbose_name_plural': '岗位模板权限关联',
|
||||
'db_table': 'PositionTemplatePermission',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Role',
|
||||
fields=[
|
||||
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', primary_key=True, serialize=False)),
|
||||
('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID')),
|
||||
('DeptUUID', gvsdsdk.models.UUIDField(binary_length=16, blank=True, db_column='DeptUUID', null=True)),
|
||||
('RoleName', models.CharField(db_column='RoleName', max_length=128)),
|
||||
('RoleType', models.SmallIntegerField(db_column='RoleType', default=2)),
|
||||
('RoleStatus', models.SmallIntegerField(db_column='RoleStatus', default=1)),
|
||||
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
|
||||
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', primary_key=True, serialize=False, verbose_name='角色UUID')),
|
||||
('TenantUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='TenantUUID', verbose_name='租户UUID')),
|
||||
('RoleName', models.CharField(db_column='RoleName', max_length=128, verbose_name='角色名称')),
|
||||
('RoleType', models.SmallIntegerField(db_column='RoleType', default=2, verbose_name='角色类型')),
|
||||
('RoleStatus', models.SmallIntegerField(db_column='RoleStatus', default=1, verbose_name='角色状态')),
|
||||
('AssignScope', models.SmallIntegerField(db_column='AssignScope', default=0, verbose_name='分配可见范围')),
|
||||
('AssignScopeUUID', gvsdsdk.models.UUIDField(binary_length=16, blank=True, db_column='AssignScopeUUID', null=True, verbose_name='分配范围UUID')),
|
||||
('RoleDesc', models.CharField(db_column='RoleDesc', default='', max_length=512, verbose_name='角色描述')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '角色',
|
||||
@@ -94,55 +152,69 @@ class Migration(migrations.Migration):
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RoleDept',
|
||||
name='RolePermission',
|
||||
fields=[
|
||||
('RoleDeptUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleDeptUUID', primary_key=True, serialize=False)),
|
||||
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')),
|
||||
('DeptUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='DeptUUID')),
|
||||
('IsMainDept', models.SmallIntegerField(db_column='IsMainDept', default=1)),
|
||||
('RolePermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RolePermUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
|
||||
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', verbose_name='角色UUID')),
|
||||
('PermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PermUUID', verbose_name='权限UUID')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '角色部门关联',
|
||||
'verbose_name_plural': '角色部门关联',
|
||||
'db_table': 'RoleDept',
|
||||
'verbose_name': '角色权限关联',
|
||||
'verbose_name_plural': '角色权限关联',
|
||||
'db_table': 'RolePermission',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
name='RoleShop',
|
||||
fields=[
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False)),
|
||||
('UserName', models.CharField(db_column='UserName', max_length=256, unique=True)),
|
||||
('UserPassword', models.CharField(db_column='UserPassword', default='', max_length=60)),
|
||||
('UserPositionStatus', models.SmallIntegerField(db_column='UserPositionStatus', default=0)),
|
||||
('UserPersonalSignature', models.CharField(db_column='UserPersonalSignature', default='', max_length=1024)),
|
||||
('UserEmail', models.CharField(db_column='UserEmail', default='', max_length=256)),
|
||||
('UserEmailVerify', models.SmallIntegerField(db_column='UserEmailVerify', default=0)),
|
||||
('UserAccountLicense', models.SmallIntegerField(db_column='UserAccountLicense', default=1)),
|
||||
('UserGender', models.SmallIntegerField(db_column='UserGender', default=-1)),
|
||||
('UserTele', models.CharField(blank=True, db_column='UserTele', max_length=16, null=True)),
|
||||
('UserCreateTime', models.DateTimeField(db_column='UserCreateTime', default=datetime.datetime.utcnow)),
|
||||
('UserLastLoginDate', models.DateTimeField(db_column='UserLastLoginDate', default=datetime.datetime.utcnow)),
|
||||
('IsStaff', models.BooleanField(db_column='IsStaff', default=False)),
|
||||
('IsSuperuser', models.BooleanField(db_column='IsSuperuser', default=False)),
|
||||
('RoleShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleShopUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
|
||||
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', verbose_name='角色UUID')),
|
||||
('ShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ShopUUID', verbose_name='商铺UUID')),
|
||||
('IsMainShop', models.SmallIntegerField(db_column='IsMainShop', default=0, verbose_name='主商铺')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户',
|
||||
'verbose_name_plural': '用户',
|
||||
'db_table': 'User',
|
||||
'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, verbose_name='商铺UUID')),
|
||||
('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID', verbose_name='公司UUID')),
|
||||
('ShopName', models.CharField(db_column='ShopName', max_length=128, verbose_name='商铺名称')),
|
||||
('ShopCode', models.CharField(db_column='ShopCode', default='', max_length=64, verbose_name='商铺编码')),
|
||||
('ShopType', models.SmallIntegerField(db_column='ShopType', default=0, verbose_name='商铺类型')),
|
||||
('ShopStatus', models.SmallIntegerField(db_column='ShopStatus', default=1, verbose_name='商铺状态')),
|
||||
('ShopAddress', models.CharField(db_column='ShopAddress', default='', max_length=512, verbose_name='商铺地址')),
|
||||
('ShopContact', models.CharField(db_column='ShopContact', default='', max_length=128, verbose_name='联系人')),
|
||||
('ShopPhone', models.CharField(db_column='ShopPhone', default='', max_length=32, verbose_name='联系电话')),
|
||||
('ShopDesc', models.CharField(db_column='ShopDesc', default='', max_length=512, verbose_name='描述')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商铺',
|
||||
'verbose_name_plural': '商铺',
|
||||
'db_table': 'Shop',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='User2FA',
|
||||
fields=[
|
||||
('User2FAUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='User2FAUUID', primary_key=True, serialize=False)),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', db_index=True)),
|
||||
('SecretKey', models.CharField(db_column='SecretKey', max_length=64)),
|
||||
('IsEnabled', models.SmallIntegerField(db_column='IsEnabled', default=0)),
|
||||
('Verified', models.SmallIntegerField(db_column='Verified', default=0)),
|
||||
('LastUsedAt', models.DateTimeField(blank=True, db_column='LastUsedAt', null=True)),
|
||||
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
|
||||
('User2FAUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='User2FAUUID', primary_key=True, serialize=False, verbose_name='2FA UUID')),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', db_index=True, verbose_name='用户UUID')),
|
||||
('SecretKey', models.CharField(db_column='SecretKey', max_length=64, verbose_name='密钥')),
|
||||
('IsEnabled', models.SmallIntegerField(db_column='IsEnabled', default=0, verbose_name='是否启用')),
|
||||
('Verified', models.SmallIntegerField(db_column='Verified', default=0, verbose_name='已验证')),
|
||||
('LastUsedAt', models.DateTimeField(blank=True, db_column='LastUsedAt', null=True, verbose_name='最后使用')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '双因素认证',
|
||||
@@ -154,9 +226,9 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='UserABAC',
|
||||
fields=[
|
||||
('UserABACUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserABACUUID', primary_key=True, serialize=False)),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID')),
|
||||
('HierarchicalPermissionIdentifiers', models.CharField(db_column='HierarchicalPermissionIdentifiers', max_length=256)),
|
||||
('UserABACUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserABACUUID', primary_key=True, serialize=False, verbose_name='ABAC UUID')),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', verbose_name='用户UUID')),
|
||||
('HierarchicalPermissionIdentifiers', models.CharField(db_column='HierarchicalPermissionIdentifiers', max_length=256, verbose_name='层级权限标识')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户ABAC',
|
||||
@@ -168,10 +240,10 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='UserAvatar',
|
||||
fields=[
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False)),
|
||||
('UserAvatar', models.BinaryField(db_column='UserAvatar', editable=True)),
|
||||
('AvatarUploadTime', models.DateTimeField(db_column='AvatarUploadTime', default=datetime.datetime.utcnow)),
|
||||
('AvatarFileType', models.CharField(db_column='AvatarFileType', default='png', max_length=16)),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False, verbose_name='用户UUID')),
|
||||
('UserAvatar', models.BinaryField(db_column='UserAvatar', editable=True, verbose_name='头像数据')),
|
||||
('AvatarUploadTime', models.DateTimeField(db_column='AvatarUploadTime', default=datetime.datetime.utcnow, verbose_name='上传时间')),
|
||||
('AvatarFileType', models.CharField(db_column='AvatarFileType', default='png', max_length=16, verbose_name='文件类型')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户头像',
|
||||
@@ -180,16 +252,33 @@ class Migration(migrations.Migration):
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserDept',
|
||||
fields=[
|
||||
('UserDeptUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserDeptUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', verbose_name='用户UUID')),
|
||||
('DeptUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='DeptUUID', verbose_name='部门UUID')),
|
||||
('PositionUUID', gvsdsdk.models.UUIDField(binary_length=16, blank=True, db_column='PositionUUID', null=True, verbose_name='职务标签UUID')),
|
||||
('IsMainDept', models.SmallIntegerField(db_column='IsMainDept', default=1, verbose_name='是否主部门')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '员工部门关联',
|
||||
'verbose_name_plural': '员工部门关联',
|
||||
'db_table': 'UserDept',
|
||||
'managed': True,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserLoginLog',
|
||||
fields=[
|
||||
('LogUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='LogUUID', primary_key=True, serialize=False)),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', db_index=True)),
|
||||
('LoginIP', models.CharField(db_column='LoginIP', max_length=39)),
|
||||
('LoginPlatform', models.CharField(db_column='LoginPlatform', max_length=32)),
|
||||
('LoginStatus', models.SmallIntegerField(db_column='LoginStatus')),
|
||||
('LoginTime', models.DateTimeField(db_column='LoginTime', default=datetime.datetime.utcnow)),
|
||||
('UserAgent', models.CharField(db_column='UserAgent', default='', max_length=512)),
|
||||
('LogUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='LogUUID', primary_key=True, serialize=False, verbose_name='日志UUID')),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', db_index=True, verbose_name='用户UUID')),
|
||||
('LoginIP', models.CharField(db_column='LoginIP', max_length=39, verbose_name='登录IP')),
|
||||
('LoginPlatform', models.CharField(db_column='LoginPlatform', max_length=32, verbose_name='登录平台')),
|
||||
('LoginStatus', models.SmallIntegerField(db_column='LoginStatus', verbose_name='登录状态')),
|
||||
('LoginTime', models.DateTimeField(db_column='LoginTime', default=datetime.datetime.utcnow, verbose_name='登录时间')),
|
||||
('UserAgent', models.CharField(db_column='UserAgent', default='', max_length=512, verbose_name='用户代理')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '登录日志',
|
||||
@@ -201,10 +290,10 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='UserPending2FA',
|
||||
fields=[
|
||||
('PendingUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PendingUUID', primary_key=True, serialize=False)),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', unique=True)),
|
||||
('SecretKey', models.CharField(db_column='SecretKey', max_length=64)),
|
||||
('ExpireAt', models.DateTimeField(db_column='ExpireAt')),
|
||||
('PendingUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PendingUUID', primary_key=True, serialize=False, verbose_name='待确认UUID')),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', unique=True, verbose_name='用户UUID')),
|
||||
('SecretKey', models.CharField(db_column='SecretKey', max_length=64, verbose_name='密钥')),
|
||||
('ExpireAt', models.DateTimeField(db_column='ExpireAt', verbose_name='过期时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '2FA待确认',
|
||||
@@ -216,10 +305,11 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='UserRole',
|
||||
fields=[
|
||||
('UserRoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserRoleUUID', primary_key=True, serialize=False)),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID')),
|
||||
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')),
|
||||
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
|
||||
('UserRoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserRoleUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
|
||||
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', verbose_name='用户UUID')),
|
||||
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', verbose_name='角色UUID')),
|
||||
('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID', verbose_name='公司UUID')),
|
||||
('CreateTime', models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户角色关联',
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
),
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
1643
gvsdsdk/models.py
1643
gvsdsdk/models.py
File diff suppressed because it is too large
Load Diff
532
gvsdsdk/msync/orm_handler.py
Normal file
532
gvsdsdk/msync/orm_handler.py
Normal file
@@ -0,0 +1,532 @@
|
||||
"""MSYNC ORM 中转处理器
|
||||
|
||||
子服务器端注册 Socket.IO 事件处理器,接收主服务器发来的 ORM 查询请求,
|
||||
在本地数据库执行 SQL 并返回结果。
|
||||
|
||||
用法(子服务器端):
|
||||
from gvsdsdk.msync.orm_handler import ORMRelayHandler
|
||||
|
||||
handler = ORMRelayHandler()
|
||||
handler.Register(sio_client) # 注册到 MSyncSocketClient 实例
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ORMRelayHandler:
|
||||
"""ORM 中转处理器——在子服务器端执行本地 SQL 查询
|
||||
|
||||
通过 MSYNC Socket.IO 事件协议,主服务器将 ORM 请求转发给子服务器,
|
||||
子服务器在本地数据库执行查询后返回结果。
|
||||
"""
|
||||
|
||||
def Register(self, sio_client):
|
||||
"""将 ORM 事件处理器注册到 MSyncSocketClient 实例
|
||||
|
||||
Args:
|
||||
sio_client: MSyncSocketClient 实例,需要有 _sio 属性
|
||||
"""
|
||||
sio = sio_client._sio
|
||||
|
||||
@sio.on('msync:orm:tables', namespace='/msync')
|
||||
def on_orm_tables(data):
|
||||
return self.HandleTables(data)
|
||||
|
||||
@sio.on('msync:orm:columns', namespace='/msync')
|
||||
def on_orm_columns(data):
|
||||
return self.HandleColumns(data)
|
||||
|
||||
@sio.on('msync:orm:list', namespace='/msync')
|
||||
def on_orm_list(data):
|
||||
return self.HandleList(data)
|
||||
|
||||
@sio.on('msync:orm:add', namespace='/msync')
|
||||
def on_orm_add(data):
|
||||
return self.HandleAdd(data)
|
||||
|
||||
@sio.on('msync:orm:modify', namespace='/msync')
|
||||
def on_orm_modify(data):
|
||||
return self.HandleModify(data)
|
||||
|
||||
@sio.on('msync:orm:delete', namespace='/msync')
|
||||
def on_orm_delete(data):
|
||||
return self.HandleDelete(data)
|
||||
|
||||
logger.info('[ORM] ORM 中转处理器已注册')
|
||||
|
||||
def _GetConnection(self):
|
||||
"""获取本地数据库连接(Django default)"""
|
||||
from django.db import connections
|
||||
conn = connections['default']
|
||||
conn.ensure_connection()
|
||||
return conn
|
||||
|
||||
# ─── 事件处理 ──────────────────────────────────────────────
|
||||
|
||||
def HandleTables(self, data):
|
||||
"""列出本地数据库中所有用户表"""
|
||||
try:
|
||||
conn = self._GetConnection()
|
||||
except Exception as e:
|
||||
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
|
||||
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
tables = self._ListTables(cursor)
|
||||
return {'code': 0, 'msg': 'success', 'data': tables}
|
||||
except Exception as e:
|
||||
logger.error(f'[ORM] HandleTables 异常: {e}')
|
||||
return {'code': 1, 'msg': f'查询失败: {e}'}
|
||||
|
||||
def HandleColumns(self, data):
|
||||
"""列出指定表的列信息"""
|
||||
table_name = (data or {}).get('Table', '')
|
||||
if not table_name:
|
||||
return {'code': 1, 'msg': '缺少 Table 参数'}
|
||||
|
||||
try:
|
||||
conn = self._GetConnection()
|
||||
except Exception as e:
|
||||
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
|
||||
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
columns = self._ListColumns(cursor, table_name)
|
||||
return {'code': 0, 'msg': 'success', 'data': columns}
|
||||
except Exception as e:
|
||||
logger.error(f'[ORM] HandleColumns 异常: {e}')
|
||||
return {'code': 1, 'msg': f'查询失败: {e}'}
|
||||
|
||||
def HandleList(self, data):
|
||||
"""分页查询表数据"""
|
||||
table_name = (data or {}).get('Table', '')
|
||||
if not table_name:
|
||||
return {'code': 1, 'msg': '缺少 Table 参数'}
|
||||
|
||||
try:
|
||||
conn = self._GetConnection()
|
||||
except Exception as e:
|
||||
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
|
||||
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
result = self._QueryTable(cursor, table_name, data or {})
|
||||
if result.get('error'):
|
||||
return {'code': 1, 'msg': result['error'], 'data': None}
|
||||
return {'code': 0, 'msg': 'success', 'data': result}
|
||||
except Exception as e:
|
||||
logger.error(f'[ORM] HandleList 异常: {e}')
|
||||
return {'code': 1, 'msg': f'查询失败: {e}'}
|
||||
|
||||
def HandleAdd(self, data):
|
||||
"""新增记录"""
|
||||
table_name = (data or {}).get('Table', '')
|
||||
record = (data or {}).get('Record', {})
|
||||
if not table_name:
|
||||
return {'code': 1, 'msg': '缺少 Table 参数'}
|
||||
|
||||
try:
|
||||
conn = self._GetConnection()
|
||||
except Exception as e:
|
||||
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
|
||||
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
result = self._AddRecord(cursor, table_name, record)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f'[ORM] HandleAdd 异常: {e}')
|
||||
return {'code': 1, 'msg': f'新增失败: {e}'}
|
||||
|
||||
def HandleModify(self, data):
|
||||
"""修改记录"""
|
||||
table_name = (data or {}).get('Table', '')
|
||||
record = (data or {}).get('Record', {})
|
||||
if not table_name:
|
||||
return {'code': 1, 'msg': '缺少 Table 参数'}
|
||||
|
||||
try:
|
||||
conn = self._GetConnection()
|
||||
except Exception as e:
|
||||
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
|
||||
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
result = self._ModifyRecord(cursor, table_name, record)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f'[ORM] HandleModify 异常: {e}')
|
||||
return {'code': 1, 'msg': f'修改失败: {e}'}
|
||||
|
||||
def HandleDelete(self, data):
|
||||
"""删除记录"""
|
||||
table_name = (data or {}).get('Table', '')
|
||||
record = (data or {}).get('Record', {})
|
||||
if not table_name:
|
||||
return {'code': 1, 'msg': '缺少 Table 参数'}
|
||||
|
||||
try:
|
||||
conn = self._GetConnection()
|
||||
except Exception as e:
|
||||
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
|
||||
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
result = self._DeleteRecord(cursor, table_name, record)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f'[ORM] HandleDelete 异常: {e}')
|
||||
return {'code': 1, 'msg': f'删除失败: {e}'}
|
||||
|
||||
# ─── SQL 执行 ──────────────────────────────────────────────
|
||||
|
||||
def _ListTables(self, cursor):
|
||||
"""列出所有用户表,含 i18n 标题"""
|
||||
cursor.execute(
|
||||
"SELECT TABLE_NAME, TABLE_ROWS, TABLE_COMMENT FROM information_schema.TABLES "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE' "
|
||||
"ORDER BY TABLE_NAME"
|
||||
)
|
||||
|
||||
from django.apps import apps
|
||||
# 构建表名→中文标题映射
|
||||
model_verbose = {}
|
||||
for model in apps.get_models():
|
||||
meta = model._meta
|
||||
# 优先使用 table_title(自定义 Meta 属性),其次 verbose_name
|
||||
table_title = getattr(meta, 'table_title', None)
|
||||
title = str(table_title) if table_title else (str(meta.verbose_name) if meta.verbose_name else '')
|
||||
if title:
|
||||
model_verbose[meta.object_name] = title
|
||||
model_verbose[meta.db_table] = title
|
||||
|
||||
result = []
|
||||
for row in cursor.fetchall():
|
||||
table_name, table_rows, table_comment = row
|
||||
# 优先级:TABLE_COMMENT > table_title/verbose_name > 表名
|
||||
title = table_comment or model_verbose.get(table_name) or table_name
|
||||
result.append({'name': table_name, 'title': title, 'count': table_rows or 0})
|
||||
return result
|
||||
|
||||
def _ListColumns(self, cursor, table_name):
|
||||
"""列出指定表的列信息"""
|
||||
cursor.execute(
|
||||
"SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA, COLUMN_TYPE, COLUMN_COMMENT "
|
||||
"FROM information_schema.COLUMNS "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s "
|
||||
"ORDER BY ORDINAL_POSITION",
|
||||
[table_name]
|
||||
)
|
||||
|
||||
model_verbose = self._FindModelVerboseNames(table_name)
|
||||
logger.info(f'[ORM] _ListColumns table={table_name} model_verbose={model_verbose}')
|
||||
|
||||
columns = []
|
||||
for row in cursor.fetchall():
|
||||
col_name, data_type, is_nullable, col_key, extra, col_type, col_comment = row
|
||||
is_pk = col_key == 'PRI'
|
||||
is_auto = 'auto_increment' in (extra or '').lower()
|
||||
|
||||
if data_type in ('int', 'bigint', 'tinyint', 'smallint', 'mediumint'):
|
||||
ftype = 'number'
|
||||
elif data_type in ('float', 'double', 'decimal'):
|
||||
ftype = 'number'
|
||||
elif data_type in ('datetime', 'timestamp'):
|
||||
ftype = 'datetime'
|
||||
elif data_type in ('date',):
|
||||
ftype = 'date'
|
||||
elif data_type in ('time',):
|
||||
ftype = 'time'
|
||||
elif data_type in ('json',):
|
||||
ftype = 'textarea'
|
||||
elif data_type in ('text', 'mediumtext', 'longtext', 'tinytext'):
|
||||
ftype = 'textarea'
|
||||
elif data_type in ('binary', 'varbinary', 'blob'):
|
||||
ftype = 'uuid'
|
||||
else:
|
||||
ftype = 'text'
|
||||
|
||||
title = col_comment or model_verbose.get(col_name) or col_name
|
||||
|
||||
columns.append({
|
||||
'field': col_name,
|
||||
'title': title,
|
||||
'type': ftype,
|
||||
'editable': not is_pk and not is_auto,
|
||||
'sortable': True,
|
||||
'filterable': True,
|
||||
'primaryKey': is_pk,
|
||||
'hideInForm': is_pk,
|
||||
'required': is_nullable == 'NO' and not is_auto,
|
||||
'dbType': col_type,
|
||||
})
|
||||
return columns
|
||||
|
||||
def _QueryTable(self, cursor, table_name, params):
|
||||
"""分页查询表数据"""
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM information_schema.TABLES "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s",
|
||||
[table_name]
|
||||
)
|
||||
if cursor.fetchone()[0] == 0:
|
||||
return {'records': [], 'total': 0, 'error': f'表不存在: {table_name}'}
|
||||
|
||||
col_info = self._GetTableColumns(cursor, table_name)
|
||||
pk_cols = {k: v for k, v in col_info.items() if v['is_pk']}
|
||||
|
||||
where_parts = []
|
||||
where_values = []
|
||||
for pk_name in pk_cols:
|
||||
pk_val = params.get(pk_name, '')
|
||||
if pk_val:
|
||||
where_parts.append(f'`{pk_name}` = %s')
|
||||
where_values.append(self._ConvertValueForDB(pk_val, pk_cols[pk_name]))
|
||||
|
||||
keyword = params.get('keyword', '')
|
||||
if not where_parts and keyword:
|
||||
cursor.execute(
|
||||
"SELECT COLUMN_NAME FROM information_schema.COLUMNS "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s "
|
||||
"AND DATA_TYPE IN ('varchar', 'char', 'text', 'mediumtext', 'longtext')",
|
||||
[table_name]
|
||||
)
|
||||
text_cols = [row[0] for row in cursor.fetchall()]
|
||||
if text_cols:
|
||||
conditions = [f'`{c}` LIKE %s' for c in text_cols]
|
||||
or_clause = ' OR '.join(conditions)
|
||||
where_parts.append(f'({or_clause})')
|
||||
where_values.extend([f'%{keyword}%'] * len(text_cols))
|
||||
|
||||
where_clause = (' WHERE ' + ' AND '.join(where_parts)) if where_parts else ''
|
||||
|
||||
if where_clause:
|
||||
cursor.execute(f'SELECT COUNT(*) FROM `{table_name}`{where_clause}', where_values)
|
||||
total = cursor.fetchone()[0]
|
||||
else:
|
||||
cursor.execute(f'SELECT COUNT(*) FROM `{table_name}`')
|
||||
total = cursor.fetchone()[0]
|
||||
|
||||
try:
|
||||
page = max(int(params.get('PageIndex', 1)), 1)
|
||||
except (ValueError, TypeError):
|
||||
page = 1
|
||||
try:
|
||||
page_size = min(max(int(params.get('PageSize', 20)), 1), 200)
|
||||
except (ValueError, TypeError):
|
||||
page_size = 20
|
||||
|
||||
offset = (page - 1) * page_size
|
||||
cursor.execute(
|
||||
f'SELECT * FROM `{table_name}`{where_clause} ORDER BY 1 LIMIT %s OFFSET %s',
|
||||
where_values + [page_size, offset]
|
||||
)
|
||||
desc = [d[0] for d in cursor.description]
|
||||
records = []
|
||||
for row in cursor.fetchall():
|
||||
record = {}
|
||||
for i, val in enumerate(row):
|
||||
val = self._SerializeValue(val)
|
||||
record[desc[i]] = val
|
||||
records.append(record)
|
||||
|
||||
return {'records': records, 'total': total, 'page': page, 'PageSize': page_size}
|
||||
|
||||
def _AddRecord(self, cursor, table_name, record):
|
||||
"""新增记录"""
|
||||
if not self._ValidateTableName(cursor, table_name):
|
||||
return {'code': 1, 'msg': f'表 {table_name} 不存在'}
|
||||
|
||||
col_info = self._GetTableColumns(cursor, table_name)
|
||||
|
||||
fields = []
|
||||
values = []
|
||||
for key, val in record.items():
|
||||
if key not in col_info:
|
||||
continue
|
||||
if col_info[key]['is_auto']:
|
||||
continue
|
||||
fields.append(f'`{key}`')
|
||||
values.append(self._ConvertValueForDB(val, col_info[key]))
|
||||
|
||||
if not fields:
|
||||
return {'code': 1, 'msg': '没有可插入的字段'}
|
||||
|
||||
placeholders = ', '.join(['%s'] * len(fields))
|
||||
sql = f'INSERT INTO `{table_name}` ({", ".join(fields)}) VALUES ({placeholders})'
|
||||
cursor.execute(sql, values)
|
||||
return {'code': 0, 'msg': '新增成功'}
|
||||
|
||||
def _ModifyRecord(self, cursor, table_name, record):
|
||||
"""修改记录"""
|
||||
if not self._ValidateTableName(cursor, table_name):
|
||||
return {'code': 1, 'msg': f'表 {table_name} 不存在'}
|
||||
|
||||
col_info = self._GetTableColumns(cursor, table_name)
|
||||
pk_cols = {k: v for k, v in col_info.items() if v['is_pk']}
|
||||
|
||||
if not pk_cols:
|
||||
return {'code': 1, 'msg': '表没有主键,无法定位记录'}
|
||||
|
||||
set_parts = []
|
||||
set_values = []
|
||||
for key, val in record.items():
|
||||
if key not in col_info or key in pk_cols:
|
||||
continue
|
||||
set_parts.append(f'`{key}` = %s')
|
||||
set_values.append(self._ConvertValueForDB(val, col_info[key]))
|
||||
|
||||
if not set_parts:
|
||||
return {'code': 0, 'msg': '没有需要更新的字段'}
|
||||
|
||||
where_parts = []
|
||||
where_values = []
|
||||
for pk_name in pk_cols:
|
||||
if pk_name not in record:
|
||||
return {'code': 1, 'msg': f'缺少主键字段: {pk_name}'}
|
||||
where_parts.append(f'`{pk_name}` = %s')
|
||||
where_values.append(self._ConvertValueForDB(record[pk_name], pk_cols[pk_name]))
|
||||
|
||||
sql = f'UPDATE `{table_name}` SET {", ".join(set_parts)} WHERE {" AND ".join(where_parts)}'
|
||||
cursor.execute(sql, set_values + where_values)
|
||||
|
||||
if cursor.rowcount == 0:
|
||||
return {'code': 1, 'msg': '记录不存在或未变更'}
|
||||
return {'code': 0, 'msg': '修改成功'}
|
||||
|
||||
def _DeleteRecord(self, cursor, table_name, record):
|
||||
"""删除记录"""
|
||||
if not self._ValidateTableName(cursor, table_name):
|
||||
return {'code': 1, 'msg': f'表 {table_name} 不存在'}
|
||||
|
||||
col_info = self._GetTableColumns(cursor, table_name)
|
||||
pk_cols = {k: v for k, v in col_info.items() if v['is_pk']}
|
||||
|
||||
if not pk_cols:
|
||||
return {'code': 1, 'msg': '表没有主键,无法定位记录'}
|
||||
|
||||
where_parts = []
|
||||
where_values = []
|
||||
for pk_name in pk_cols:
|
||||
if pk_name not in record:
|
||||
return {'code': 1, 'msg': f'缺少主键字段: {pk_name}'}
|
||||
where_parts.append(f'`{pk_name}` = %s')
|
||||
where_values.append(self._ConvertValueForDB(record[pk_name], pk_cols[pk_name]))
|
||||
|
||||
sql = f'DELETE FROM `{table_name}` WHERE {" AND ".join(where_parts)}'
|
||||
cursor.execute(sql, where_values)
|
||||
|
||||
if cursor.rowcount == 0:
|
||||
return {'code': 1, 'msg': '记录不存在'}
|
||||
return {'code': 0, 'msg': '删除成功'}
|
||||
|
||||
# ─── 工具方法 ──────────────────────────────────────────────
|
||||
|
||||
def _ValidateTableName(self, cursor, table_name):
|
||||
"""验证表名存在"""
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM information_schema.TABLES "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s",
|
||||
[table_name]
|
||||
)
|
||||
return cursor.fetchone()[0] > 0
|
||||
|
||||
def _GetTableColumns(self, cursor, table_name):
|
||||
"""获取表的列信息"""
|
||||
cursor.execute(
|
||||
"SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, COLUMN_KEY, EXTRA, IS_NULLABLE "
|
||||
"FROM information_schema.COLUMNS "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s "
|
||||
"ORDER BY ORDINAL_POSITION",
|
||||
[table_name]
|
||||
)
|
||||
cols = {}
|
||||
for row in cursor.fetchall():
|
||||
col_name, data_type, column_type, col_key, extra, is_nullable = row
|
||||
cols[col_name] = {
|
||||
'type': data_type,
|
||||
'column_type': column_type or '',
|
||||
'is_pk': col_key == 'PRI',
|
||||
'is_auto': 'auto_increment' in (extra or '').lower(),
|
||||
'is_nullable': is_nullable == 'YES',
|
||||
}
|
||||
return cols
|
||||
|
||||
def _ConvertValueForDB(self, value, col_info):
|
||||
"""将前端传来的值转换为数据库可接受的类型"""
|
||||
if value is None or value == '':
|
||||
if col_info['is_nullable']:
|
||||
return None
|
||||
return ''
|
||||
|
||||
data_type = col_info['type']
|
||||
|
||||
if data_type in ('int', 'bigint', 'tinyint', 'smallint', 'mediumint'):
|
||||
return int(value)
|
||||
elif data_type in ('float', 'double', 'decimal'):
|
||||
return float(value)
|
||||
elif data_type in ('binary', 'varbinary'):
|
||||
import re
|
||||
hex_str = re.sub(r'[^0-9a-fA-F]', '', str(value))
|
||||
if len(hex_str) == 32:
|
||||
return uuid.UUID(hex_str).bytes
|
||||
return value
|
||||
|
||||
return value
|
||||
|
||||
def _SerializeValue(self, val):
|
||||
"""将数据库值序列化为 JSON 安全格式"""
|
||||
if isinstance(val, bytes):
|
||||
if len(val) == 16:
|
||||
try:
|
||||
return str(uuid.UUID(bytes=val))
|
||||
except ValueError:
|
||||
return val.hex()
|
||||
return val.hex()
|
||||
elif isinstance(val, datetime.datetime):
|
||||
return val.strftime('%Y-%m-%d %H:%M:%S')
|
||||
elif isinstance(val, datetime.date):
|
||||
return val.isoformat()
|
||||
elif isinstance(val, datetime.time):
|
||||
return val.strftime('%H:%M:%S')
|
||||
elif isinstance(val, (bytearray, memoryview)):
|
||||
return bytes(val).hex()
|
||||
return val
|
||||
|
||||
def _FindModelVerboseNames(self, table_name):
|
||||
"""从 Django 模型中获取字段的 verbose_name"""
|
||||
from django.apps import apps
|
||||
verbose_map = {}
|
||||
try:
|
||||
for model in apps.get_models():
|
||||
if model._meta.db_table == table_name:
|
||||
logger.info(f'[ORM] _FindModelVerboseNames matched model={model.__name__} db_table={model._meta.db_table}')
|
||||
for f in model._meta.get_fields():
|
||||
# 获取数据库列名
|
||||
col_name = None
|
||||
if hasattr(f, 'column') and f.column:
|
||||
col_name = f.column
|
||||
elif hasattr(f, 'db_column') and f.db_column:
|
||||
col_name = f.db_column
|
||||
elif hasattr(f, 'attname'):
|
||||
col_name = f.attname
|
||||
|
||||
if not col_name:
|
||||
continue
|
||||
|
||||
vn = getattr(f, 'verbose_name', None)
|
||||
if vn and str(vn) != col_name:
|
||||
verbose_map[col_name] = str(vn)
|
||||
logger.debug(f'[ORM] {col_name} -> {str(vn)}')
|
||||
break
|
||||
else:
|
||||
# 没有匹配的模型
|
||||
available = [m._meta.db_table for m in apps.get_models()]
|
||||
logger.warning(f'[ORM] _FindModelVerboseNames: 未找到 db_table={table_name} 的模型, 可用表: {available[:20]}...')
|
||||
except Exception as e:
|
||||
logger.error(f'[ORM] _FindModelVerboseNames 异常: {e}')
|
||||
return verbose_map
|
||||
7
jituan/apps.py
Normal file
7
jituan/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class JituanConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'jituan'
|
||||
verbose_name = '集团多俱乐部'
|
||||
30
jituan/constants.py
Normal file
30
jituan/constants.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""集团多俱乐部常量。"""
|
||||
|
||||
CLUB_ID_DEFAULT = 'xq'
|
||||
CLUB_HEADER = 'X-Club-Id'
|
||||
CLUB_SCOPE_HEADER = 'X-Club-Scope'
|
||||
|
||||
DATA_SCOPE_ALL = 'ALL_CLUBS'
|
||||
DATA_SCOPE_SINGLE = 'SINGLE_CLUB'
|
||||
|
||||
# 管理任职角色码(数据范围 / 集团 vs 子公司)
|
||||
ADMIN_ROLE_LABELS = {
|
||||
'GROUP_OWNER': '集团总负责人',
|
||||
'GROUP_SUPER_ADMIN': '集团超管',
|
||||
'GROUP_AFTER_SALES': '集团售后',
|
||||
'GROUP_FINANCE': '集团财务',
|
||||
'GROUP_CONFIG': '集团配置',
|
||||
'CLUB_OWNER': '俱乐部总负责人',
|
||||
'CLUB_ADMIN': '俱乐部管理员',
|
||||
'CLUB_AFTER_SALES': '俱乐部售后',
|
||||
'CLUB_FINANCE': '俱乐部财务',
|
||||
'CLUB_OPERATOR': '俱乐部运营',
|
||||
}
|
||||
|
||||
# 功能权限仍走 gvsdsdk Permission.perm_code(如 caiwu、dingdan、000001)
|
||||
|
||||
ADMIN_SCOPE_GROUP = 'GROUP'
|
||||
ADMIN_SCOPE_CLUB = 'CLUB'
|
||||
|
||||
# 集团最高权限账号(写死超级管理员,拥有全部功能权限与集团视图)
|
||||
SUPER_ADMIN_PHONES = frozenset({'1383714110'})
|
||||
0
jituan/management/__init__.py
Normal file
0
jituan/management/__init__.py
Normal file
0
jituan/management/commands/__init__.py
Normal file
0
jituan/management/commands/__init__.py
Normal file
47
jituan/management/commands/backfill_display_club_id.py
Normal file
47
jituan/management/commands/backfill_display_club_id.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""回填 lunbo / gonggao / popup_page / tupianpeizhi 的 club_id 与 lunbo.page_key。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from config.models import Gonggao, Lunbo, PopupPage, Tupianpeizhi
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '回填展示配置表的 club_id,并为 lunbo 补 page_key'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
updated = {'lunbo_club': 0, 'lunbo_page': 0, 'gonggao': 0, 'popup': 0, 'tupian': 0}
|
||||
|
||||
with transaction.atomic():
|
||||
for row in Lunbo.query.all().only('id', 'club_id', 'page_key', 'ImageType'):
|
||||
fields = []
|
||||
if not row.club_id:
|
||||
row.club_id = CLUB_ID_DEFAULT
|
||||
fields.append('club_id')
|
||||
updated['lunbo_club'] += 1
|
||||
if not row.page_key:
|
||||
if row.ImageType == 2:
|
||||
row.page_key = 'dashou_center'
|
||||
else:
|
||||
row.page_key = 'order_pool'
|
||||
fields.append('page_key')
|
||||
updated['lunbo_page'] += 1
|
||||
if fields:
|
||||
row.save(update_fields=fields)
|
||||
|
||||
for row in Gonggao.query.filter(club_id='').only('id', 'club_id'):
|
||||
Gonggao.query.filter(id=row.id).update(club_id=CLUB_ID_DEFAULT)
|
||||
updated['gonggao'] += 1
|
||||
|
||||
for row in PopupPage.query.filter(club_id='').only('id', 'club_id'):
|
||||
PopupPage.query.filter(id=row.id).update(club_id=CLUB_ID_DEFAULT)
|
||||
updated['popup'] += 1
|
||||
|
||||
for row in Tupianpeizhi.query.filter(club_id='').only('id', 'club_id'):
|
||||
Tupianpeizhi.query.filter(id=row.id).update(club_id=CLUB_ID_DEFAULT)
|
||||
updated['tupian'] += 1
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'回填完成: lunbo_club={updated["lunbo_club"]}, lunbo_page={updated["lunbo_page"]}, '
|
||||
f'gonggao={updated["gonggao"]}, popup={updated["popup"]}, tupian={updated["tupian"]}'
|
||||
))
|
||||
23
jituan/management/commands/backfill_gsfenhong_club_id.py
Normal file
23
jituan/management/commands/backfill_gsfenhong_club_id.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""按充值订单 / 订单 / 用户回填 gsfenhong.club_id。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.services.club_penalty import resolve_gsfenhong_club_id
|
||||
from products.models import Gsfenhong
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '回填 gsfenhong.club_id'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
updated = 0
|
||||
with transaction.atomic():
|
||||
for row in Gsfenhong.query.all().only('dingdan_id', 'dashouid', 'club_id'):
|
||||
cid = resolve_gsfenhong_club_id(dingdan_id=row.dingdan_id, dashouid=row.dashouid)
|
||||
if not cid:
|
||||
cid = CLUB_ID_DEFAULT
|
||||
if row.club_id != cid:
|
||||
Gsfenhong.query.filter(dingdan_id=row.dingdan_id).update(club_id=cid)
|
||||
updated += 1
|
||||
self.stdout.write(self.style.SUCCESS(f'gsfenhong club_id 更新 {updated} 条'))
|
||||
41
jituan/management/commands/backfill_order_club_id.py
Normal file
41
jituan/management/commands/backfill_order_club_id.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""回填订单 ClubID(空值设为 xq)。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from orders.models import Order
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '回填 Order.ClubID:空或缺省设为 xq'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='仅统计不落库',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry_run = options.get('dry_run', False)
|
||||
updated = 0
|
||||
skipped = 0
|
||||
|
||||
for order in Order.query.all().only('id', 'OrderID', 'ClubID'):
|
||||
current = getattr(order, 'ClubID', None) or ''
|
||||
if current:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(f'[dry-run] {order.OrderID} -> ClubID={CLUB_ID_DEFAULT}')
|
||||
updated += 1
|
||||
continue
|
||||
|
||||
with transaction.atomic():
|
||||
order.ClubID = CLUB_ID_DEFAULT
|
||||
order.save(update_fields=['ClubID'])
|
||||
updated += 1
|
||||
|
||||
suffix = '(dry-run)' if dry_run else ''
|
||||
self.stdout.write(self.style.SUCCESS(f'完成{suffix}:更新 {updated},跳过 {skipped}'))
|
||||
42
jituan/management/commands/backfill_penalty_club_id.py
Normal file
42
jituan/management/commands/backfill_penalty_club_id.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""回填罚单 club_id(从关联订单或被罚用户)。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.services.club_penalty import resolve_penalty_club_id
|
||||
from orders.models import Order, Penalty
|
||||
from users.business_models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '按关联订单/被罚用户回填 fadan.club_id(默认仅处理仍为 xq 的记录)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--all', action='store_true', help='重算全部罚单(慎用)')
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry = options['dry_run']
|
||||
qs = Penalty.query.all()
|
||||
if not options['all']:
|
||||
qs = qs.filter(ClubID=CLUB_ID_DEFAULT)
|
||||
|
||||
updated = 0
|
||||
with transaction.atomic():
|
||||
for p in qs.to_list():
|
||||
order = None
|
||||
if p.RelatedOrderID:
|
||||
order = Order.query.filter(OrderID=p.RelatedOrderID).first()
|
||||
cid = resolve_penalty_club_id(
|
||||
order=order,
|
||||
penalized_user_id=p.PenalizedUserID,
|
||||
)
|
||||
if cid and cid != p.ClubID:
|
||||
if dry:
|
||||
self.stdout.write(f'would update penalty {p.id}: {p.ClubID} -> {cid}')
|
||||
else:
|
||||
p.ClubID = cid
|
||||
p.save(update_fields=['ClubID'])
|
||||
updated += 1
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'罚单 club_id 更新 {updated} 条'))
|
||||
@@ -0,0 +1,53 @@
|
||||
"""回填 chufajilu / fadan / duoci_fenhong club_id。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.services.club_penalty import (
|
||||
resolve_gsfenhong_club_id,
|
||||
resolve_penalty_club_id,
|
||||
resolve_penalty_record_club_id,
|
||||
)
|
||||
from orders.models import Penalty, PenaltyRecord
|
||||
from products.models import DuociFenhong
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '回填 PenaltyRecord / Penalty / DuociFenhong 的 club_id'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
pr_updated = 0
|
||||
fd_updated = 0
|
||||
df_updated = 0
|
||||
with transaction.atomic():
|
||||
for row in PenaltyRecord.query.all().only('id', 'OrderID', 'PlayerID', 'ClubID'):
|
||||
cid = resolve_penalty_record_club_id(order_id=row.OrderID, player_id=row.PlayerID)
|
||||
if row.ClubID != cid:
|
||||
PenaltyRecord.query.filter(id=row.id).update(ClubID=cid)
|
||||
pr_updated += 1
|
||||
for row in Penalty.query.all().only('id', 'RelatedOrderID', 'PenalizedUserID', 'ClubID'):
|
||||
cid = resolve_penalty_club_id(
|
||||
order=None,
|
||||
penalized_user_id=row.PenalizedUserID,
|
||||
)
|
||||
if row.RelatedOrderID:
|
||||
from orders.models import Order
|
||||
o = Order.query.filter(OrderID=row.RelatedOrderID).first()
|
||||
if o:
|
||||
cid = resolve_penalty_club_id(order=o, penalized_user_id=row.PenalizedUserID)
|
||||
if row.ClubID != cid:
|
||||
Penalty.query.filter(id=row.id).update(ClubID=cid)
|
||||
fd_updated += 1
|
||||
for row in DuociFenhong.query.all().only('huiyuan', 'yonghuid', 'cishu', 'club_id'):
|
||||
cid = resolve_gsfenhong_club_id(dashouid=row.yonghuid) or CLUB_ID_DEFAULT
|
||||
if row.club_id != cid:
|
||||
DuociFenhong.query.filter(
|
||||
club_id=row.club_id,
|
||||
huiyuan=row.huiyuan,
|
||||
yonghuid=row.yonghuid,
|
||||
cishu=row.cishu,
|
||||
).update(club_id=cid)
|
||||
df_updated += 1
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'PenaltyRecord={pr_updated}, Penalty={fd_updated}, DuociFenhong={df_updated}'
|
||||
))
|
||||
36
jituan/management/commands/backfill_role_daily_club_id.py
Normal file
36
jituan/management/commands/backfill_role_daily_club_id.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""回填角色日统计 club_id(按用户归属俱乐部)。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from backend.models import (
|
||||
LeaderDailyStats,
|
||||
ManagerDailyStats,
|
||||
ManagerRenewalDailyStats,
|
||||
MerchantDailyStats,
|
||||
PlayerDailyStats,
|
||||
)
|
||||
from backend.utils import _club_id_for_yonghuid
|
||||
|
||||
|
||||
MODELS = [
|
||||
(MerchantDailyStats, 'MerchantID'),
|
||||
(PlayerDailyStats, 'PlayerID'),
|
||||
(LeaderDailyStats, 'LeaderID'),
|
||||
(ManagerDailyStats, 'ManagerID'),
|
||||
(ManagerRenewalDailyStats, 'ManagerID'),
|
||||
]
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '回填 5 张角色日统计表的 club_id'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
updated = 0
|
||||
with transaction.atomic():
|
||||
for model, uid_field in MODELS:
|
||||
for row in model.query.all().only('id', uid_field, 'club_id'):
|
||||
cid = _club_id_for_yonghuid(getattr(row, uid_field))
|
||||
if row.club_id != cid:
|
||||
model.query.filter(id=row.id).update(club_id=cid)
|
||||
updated += 1
|
||||
self.stdout.write(self.style.SUCCESS(f'角色日统计 club_id 更新 {updated} 条'))
|
||||
42
jituan/management/commands/backfill_tixian_club_id.py
Normal file
42
jituan/management/commands/backfill_tixian_club_id.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""按用户 ClubID 回填提现流水 club_id。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.services.club_user import get_user_club_id
|
||||
from users.business_models import User
|
||||
from users.models import TixianAutoRecord, Tixianjilu, TixianShenheJilu
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '回填 tixianjilu / tixian_shenhe_jilu / tixian_auto_record 的 club_id'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
uid_club = {}
|
||||
for u in User.query.all().only('UserUID', 'ClubID'):
|
||||
uid_club[u.UserUID] = get_user_club_id(u)
|
||||
|
||||
updated = {'jilu': 0, 'shenhe': 0, 'auto': 0}
|
||||
|
||||
with transaction.atomic():
|
||||
for row in Tixianjilu.query.all().only('id', 'yonghuid', 'club_id'):
|
||||
cid = uid_club.get(row.yonghuid) or CLUB_ID_DEFAULT
|
||||
if row.club_id != cid:
|
||||
Tixianjilu.query.filter(id=row.id).update(club_id=cid)
|
||||
updated['jilu'] += 1
|
||||
|
||||
for row in TixianShenheJilu.query.all().only('id', 'yonghuid', 'club_id'):
|
||||
cid = uid_club.get(row.yonghuid) or CLUB_ID_DEFAULT
|
||||
if row.club_id != cid:
|
||||
TixianShenheJilu.query.filter(id=row.id).update(club_id=cid)
|
||||
updated['shenhe'] += 1
|
||||
|
||||
for row in TixianAutoRecord.query.all().only('id', 'yonghuid', 'club_id'):
|
||||
cid = uid_club.get(row.yonghuid) or CLUB_ID_DEFAULT
|
||||
if row.club_id != cid:
|
||||
TixianAutoRecord.query.filter(id=row.id).update(club_id=cid)
|
||||
updated['auto'] += 1
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'回填完成: jilu={updated["jilu"]}, shenhe={updated["shenhe"]}, auto={updated["auto"]}'
|
||||
))
|
||||
53
jituan/management/commands/backfill_user_club_id.py
Normal file
53
jituan/management/commands/backfill_user_club_id.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""回填用户 ClubID(缺省或空时设为 xq 或 user_wx_openid 绑定)。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.models import UserWxOpenid
|
||||
from users.business_models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '回填 User.ClubID:OpenID 绑定 → user_wx_openid,否则默认 xq'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='仅统计不落库',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry_run = options.get('dry_run', False)
|
||||
updated = 0
|
||||
skipped = 0
|
||||
|
||||
users = User.query.all()
|
||||
for user in users:
|
||||
current = getattr(user, 'ClubID', None) or ''
|
||||
if current and current != CLUB_ID_DEFAULT:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
new_club = CLUB_ID_DEFAULT
|
||||
if user.OpenID:
|
||||
binding = UserWxOpenid.query.filter(openid=user.OpenID).order_by('id').first()
|
||||
if binding:
|
||||
new_club = binding.club_id
|
||||
|
||||
if current == new_club:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(f'[dry-run] {user.UserUID} -> ClubID={new_club}')
|
||||
updated += 1
|
||||
continue
|
||||
|
||||
with transaction.atomic():
|
||||
user.ClubID = new_club
|
||||
user.save(update_fields=['ClubID'])
|
||||
updated += 1
|
||||
|
||||
suffix = '(dry-run)' if dry_run else ''
|
||||
self.stdout.write(self.style.SUCCESS(f'完成{suffix}:更新 {updated},跳过 {skipped}'))
|
||||
75
jituan/management/commands/bootstrap_xzj_invite.py
Normal file
75
jituan/management/commands/bootstrap_xzj_invite.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""清理重复任职 + 可选为星之界创建组长邀请链。"""
|
||||
import time
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.models import Club, ClubHuiyuanPrice
|
||||
from jituan.services.admin_assignments import dedupe_active_assignments
|
||||
from jituan.services.club_user import ensure_user_club_id
|
||||
from users.business_models import User
|
||||
from users.models import UserGuanshi, UserZuzhang
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '清理重复 admin_assignment;可选为 xzj 用户创建组长并输出邀请码'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--dedupe-only', action='store_true', help='只清理重复有效任职')
|
||||
parser.add_argument('--club-id', default='xzj', help='目标俱乐部')
|
||||
parser.add_argument('--phone', type=str, default='', help='用户手机号(须已微信登录过)')
|
||||
parser.add_argument('--make-zuzhang', action='store_true', help='将该用户设为 xzj 组长并生成邀请码')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
removed = dedupe_active_assignments()
|
||||
self.stdout.write(self.style.SUCCESS(f'已停用重复任职 {removed} 条'))
|
||||
|
||||
if options.get('dedupe_only'):
|
||||
return
|
||||
|
||||
club_id = (options.get('club_id') or 'xzj').strip()
|
||||
if not Club.query.filter(club_id=club_id).exists():
|
||||
raise CommandError(f'俱乐部 {club_id} 不存在')
|
||||
|
||||
price_cnt = ClubHuiyuanPrice.query.filter(club_id=club_id).count()
|
||||
self.stdout.write(f'club_huiyuan_price({club_id}) 共 {price_cnt} 条(创建俱乐部时从 xq 复制,价相同需后台单独改)')
|
||||
|
||||
phone = (options.get('phone') or '').strip()
|
||||
if options.get('make_zuzhang'):
|
||||
if not phone:
|
||||
raise CommandError('请指定 --phone')
|
||||
user = User.query.filter(Phone=phone).first()
|
||||
if not user:
|
||||
raise CommandError(f'未找到手机号 {phone},请先在星之界小程序登录一次')
|
||||
ensure_user_club_id(user, club_id)
|
||||
user.ClubID = club_id
|
||||
user.save(update_fields=['ClubID'])
|
||||
|
||||
if hasattr(user, 'ZuzhangProfile'):
|
||||
z = user.ZuzhangProfile
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'已是组长,邀请码: {z.yaoqingma}(管事注册用组长码)'
|
||||
))
|
||||
return
|
||||
|
||||
yaoqingma = f'ZZ{int(time.time())}{str(user.UserUID)[-4:]}'
|
||||
with transaction.atomic():
|
||||
UserZuzhang.query.create(
|
||||
user=user,
|
||||
yaoqingma=yaoqingma,
|
||||
zhuangtai=1,
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'已创建 xzj 组长 UserUID={user.UserUID},组长邀请码: {yaoqingma}\n'
|
||||
f'管事注册: 小程序内 guanshizhuce,inviteCode={yaoqingma}'
|
||||
))
|
||||
else:
|
||||
self.stdout.write(
|
||||
'查邀请码 SQL(表名 User 大写):\n'
|
||||
f"SELECT u.UserUID,u.Phone,z.yaoqingma FROM `User` u "
|
||||
f"JOIN user_zuzhang z ON z.UserUUID=u.UserUUID WHERE u.ClubID='{club_id}';\n"
|
||||
f"SELECT u.UserUID,u.Phone,g.yaoqingma FROM `User` u "
|
||||
f"JOIN user_guanshi g ON g.UserUUID=u.UserUUID WHERE u.ClubID='{club_id}';\n"
|
||||
'创建组长: python manage.py bootstrap_xzj_invite --phone 手机号 --make-zuzhang'
|
||||
)
|
||||
110
jituan/management/commands/create_club.py
Normal file
110
jituan/management/commands/create_club.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""从 xq 模板复制配置,创建新子公司俱乐部。"""
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.models import Club, ClubHuiyuanPrice, ClubLilubiao, ClubTixianQuota, ClubWithdrawConfig
|
||||
from jituan.services.szjilu_accounting import normalize_szjilu_club_id
|
||||
from jituan.services.display_config import copy_display_config
|
||||
from config.models import Szjilu
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '从 xq 复制支付/会员价/利率配置,创建新俱乐部'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('club_id', type=str, help='新俱乐部 ID,如 xy')
|
||||
parser.add_argument('--name', type=str, required=True, help='展示名称')
|
||||
parser.add_argument('--wx-appid', type=str, default='', help='小程序 appid')
|
||||
parser.add_argument('--from-club', type=str, default=CLUB_ID_DEFAULT, help='模板俱乐部')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
new_id = (options['club_id'] or '').strip()
|
||||
name = (options['name'] or '').strip()
|
||||
template_id = normalize_szjilu_club_id(options.get('from_club'))
|
||||
|
||||
if not new_id or len(new_id) > 16:
|
||||
raise CommandError('club_id 无效')
|
||||
if new_id == template_id:
|
||||
raise CommandError('新 club_id 不能与模板相同')
|
||||
|
||||
try:
|
||||
template = Club.query.get(club_id=template_id)
|
||||
except Club.DoesNotExist:
|
||||
raise CommandError(f'模板俱乐部 {template_id} 不存在,请先 seed_club_xq')
|
||||
|
||||
with transaction.atomic():
|
||||
if Club.query.filter(club_id=new_id).exists():
|
||||
raise CommandError(f'俱乐部 {new_id} 已存在')
|
||||
|
||||
Club.query.create(
|
||||
club_id=new_id,
|
||||
name=name,
|
||||
status=1,
|
||||
wx_appid=options.get('wx_appid') or template.wx_appid,
|
||||
wx_secret=template.wx_secret,
|
||||
mch_id=template.mch_id,
|
||||
pay_app_id=template.pay_app_id or options.get('wx_appid') or template.wx_appid,
|
||||
api_v3_key=template.api_v3_key,
|
||||
cert_serial_no=template.cert_serial_no,
|
||||
private_key_path=template.private_key_path,
|
||||
platform_cert_dir=template.platform_cert_dir,
|
||||
official_appid=template.official_appid,
|
||||
official_secret=template.official_secret,
|
||||
official_token=template.official_token,
|
||||
encoding_aes_key=template.encoding_aes_key,
|
||||
template_id=template.template_id,
|
||||
template_max_per_minute=template.template_max_per_minute,
|
||||
h5_domain=template.h5_domain,
|
||||
oss_prefix=template.oss_prefix or new_id,
|
||||
goeasy_appkey=template.goeasy_appkey,
|
||||
goeasy_secret=template.goeasy_secret,
|
||||
config_json=dict(template.config_json or {}),
|
||||
sort_order=template.sort_order + 1,
|
||||
)
|
||||
|
||||
for row in ClubHuiyuanPrice.query.filter(club_id=template_id):
|
||||
ClubHuiyuanPrice.query.create(
|
||||
club_id=new_id,
|
||||
huiyuan_id=row.huiyuan_id,
|
||||
jiage=row.jiage or Decimal('0'),
|
||||
guanshifc=row.guanshifc or Decimal('0'),
|
||||
zuzhangfc=row.zuzhangfc or Decimal('0'),
|
||||
formal_days=row.formal_days or 30,
|
||||
trial_enabled=bool(row.trial_enabled),
|
||||
trial_price=row.trial_price or Decimal('0'),
|
||||
trial_days=row.trial_days or 0,
|
||||
trial_guanshifc=row.trial_guanshifc or Decimal('0'),
|
||||
trial_zuzhangfc=row.trial_zuzhangfc or Decimal('0'),
|
||||
is_enabled=row.is_enabled,
|
||||
bankuai_id=row.bankuai_id,
|
||||
)
|
||||
|
||||
for row in ClubLilubiao.query.filter(club_id=template_id):
|
||||
ClubLilubiao.query.create(
|
||||
club_id=new_id,
|
||||
config_type=row.config_type,
|
||||
lilv=row.lilv or Decimal('0'),
|
||||
remark=row.remark or '',
|
||||
)
|
||||
|
||||
try:
|
||||
tpl_wd = ClubWithdrawConfig.query.get(club_id=template_id)
|
||||
ClubWithdrawConfig.query.create(club_id=new_id, mode=tpl_wd.mode)
|
||||
except ClubWithdrawConfig.DoesNotExist:
|
||||
ClubWithdrawConfig.query.create(club_id=new_id, mode=1)
|
||||
|
||||
for row in ClubTixianQuota.query.filter(club_id=template_id):
|
||||
ClubTixianQuota.query.create(
|
||||
club_id=new_id,
|
||||
leixing=row.leixing,
|
||||
daily_limit=row.daily_limit or Decimal('0'),
|
||||
)
|
||||
|
||||
Szjilu.objects.get_or_create(club_id=new_id)
|
||||
|
||||
copy_display_config(template_id, new_id)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'俱乐部 {new_id} ({name}) 已创建,配置自 {template_id} 复制'))
|
||||
89
jituan/management/commands/seed_admin_assignments.py
Normal file
89
jituan/management/commands/seed_admin_assignments.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""为后台客服账号初始化 admin_assignment(数据范围,非功能权限)。"""
|
||||
import logging
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_SINGLE, ADMIN_ROLE_LABELS
|
||||
from jituan.models import AdminAssignment
|
||||
from users.business_models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '为 kefu 账号创建默认子公司任职(club=xq, CLUB_ADMIN);超管可不建 assignment'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--club-id', default=CLUB_ID_DEFAULT, help='目标俱乐部 ID,默认 xq',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--role-code', default='CLUB_ADMIN', help='任职角色码(仅展示标签,不控制菜单功能权限)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--phone', type=str, default='', help='仅给该手机号客服添加任职(推荐)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--all-kefu',
|
||||
action='store_true',
|
||||
help='给全部客服账号批量添加(慎用,会扫所有有 KefuProfile 的账号)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run', action='store_true', help='只打印不写入',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
club_id = options['club_id']
|
||||
role_code = options['role_code']
|
||||
dry_run = options['dry_run']
|
||||
target_phone = (options.get('phone') or '').strip()
|
||||
all_kefu = options.get('all_kefu')
|
||||
|
||||
if not target_phone and not all_kefu:
|
||||
raise CommandError(
|
||||
'请指定 --phone 13800138000(只配一个人)或 --all-kefu(批量全部客服)。'
|
||||
'默认不会自动给所有人加任职。'
|
||||
)
|
||||
|
||||
if target_phone:
|
||||
target = User.query.filter(Phone=target_phone).first()
|
||||
if not target:
|
||||
raise CommandError(f'未找到手机号 {target_phone}')
|
||||
kefu_users = [target]
|
||||
else:
|
||||
kefu_users = list(
|
||||
User.query.filter(KefuProfile__isnull=False).select_related('KefuProfile')
|
||||
)
|
||||
created = 0
|
||||
skipped = 0
|
||||
|
||||
with transaction.atomic():
|
||||
for u in kefu_users:
|
||||
if u.IsSuperuser:
|
||||
skipped += 1
|
||||
continue
|
||||
exists = AdminAssignment.query.filter(
|
||||
yonghuid=u.UserUID, club_id=club_id, role_code=role_code,
|
||||
).exists()
|
||||
if exists:
|
||||
skipped += 1
|
||||
continue
|
||||
if dry_run:
|
||||
self.stdout.write(f'would assign {u.UserUID} -> {club_id} {role_code}')
|
||||
created += 1
|
||||
continue
|
||||
AdminAssignment.query.create(
|
||||
yonghuid=u.UserUID,
|
||||
club_id=club_id,
|
||||
role_code=role_code,
|
||||
data_scope=DATA_SCOPE_SINGLE,
|
||||
is_primary=True,
|
||||
status=1,
|
||||
)
|
||||
created += 1
|
||||
|
||||
label = ADMIN_ROLE_LABELS.get(role_code, role_code)
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'完成:新建 {created} 条,跳过 {skipped} 条(club={club_id}, role={label})'
|
||||
))
|
||||
183
jituan/management/commands/seed_club_xq.py
Normal file
183
jituan/management/commands/seed_club_xq.py
Normal file
@@ -0,0 +1,183 @@
|
||||
"""从现网配置初始化 xq 俱乐部及覆盖表。"""
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.models import (
|
||||
Club,
|
||||
ClubHuiyuanPrice,
|
||||
ClubLilubiao,
|
||||
ClubWithdrawConfig,
|
||||
UserWxOpenid,
|
||||
)
|
||||
from users.business_models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '初始化星阙(xq)俱乐部主数据,并从 huiyuan/lilubiao 复制覆盖配置'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
with transaction.atomic():
|
||||
club, created = self._ensure_club_xq()
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'club xq: {"新建" if created else "已存在"}'
|
||||
))
|
||||
self._copy_huiyuan_prices()
|
||||
self._copy_lilubiao()
|
||||
self._ensure_withdraw_config()
|
||||
migrated = self._migrate_openid_bindings()
|
||||
self.stdout.write(self.style.SUCCESS(f'user_wx_openid 迁移绑定: {migrated} 条'))
|
||||
|
||||
def _ensure_club_xq(self):
|
||||
wx_v3 = dict(getattr(settings, 'WECHAT_PAY_V3_CONFIG', {}) or {})
|
||||
defaults = {
|
||||
'name': '星阙电竞',
|
||||
'status': 1,
|
||||
'wx_appid': getattr(settings, 'WEIXIN_APPID', ''),
|
||||
'wx_secret': getattr(settings, 'WEIXIN_SECRET', ''),
|
||||
'mch_id': getattr(settings, 'WEIXIN_MCHID', '') or wx_v3.get('MCHID', ''),
|
||||
'pay_app_id': wx_v3.get('APPID', '') or getattr(settings, 'WEIXIN_APPID', ''),
|
||||
'api_v3_key': wx_v3.get('API_V3_KEY', ''),
|
||||
'cert_serial_no': wx_v3.get('CERT_SERIAL_NO', ''),
|
||||
'private_key_path': wx_v3.get('PRIVATE_KEY_PATH', ''),
|
||||
'platform_cert_dir': wx_v3.get('PLATFORM_CERT_DIR', ''),
|
||||
'config_json': {
|
||||
'mch_key': getattr(settings, 'WEIXIN_SHANGHUMIYAO', ''),
|
||||
'transfer_scene_id': wx_v3.get('TRANSFER_SCENE_ID', '1005'),
|
||||
},
|
||||
'official_appid': getattr(settings, 'WEIXIN_OFFICIAL_APPID', ''),
|
||||
'official_secret': getattr(settings, 'WEIXIN_OFFICIAL_SECRET', ''),
|
||||
'official_token': getattr(settings, 'WEIXIN_OFFICIAL_TOKEN', ''),
|
||||
'encoding_aes_key': getattr(settings, 'WEIXIN_OFFICIAL_ENCODING_AES_KEY', ''),
|
||||
'template_id': getattr(settings, 'WEIXIN_TEMPLATE_ID', ''),
|
||||
'template_max_per_minute': getattr(settings, 'WEIXIN_TEMPLATE_MAX_PER_MINUTE', 950),
|
||||
'h5_domain': getattr(settings, 'H5_DOMAIN', ''),
|
||||
'goeasy_appkey': getattr(settings, 'GOEASY_APPKEY', ''),
|
||||
'goeasy_secret': getattr(settings, 'GOEASY_SECRET', ''),
|
||||
'sort_order': 0,
|
||||
}
|
||||
try:
|
||||
club = Club.query.get(club_id=CLUB_ID_DEFAULT)
|
||||
for k, v in defaults.items():
|
||||
if k == 'config_json':
|
||||
merged = dict(club.config_json or {})
|
||||
for ck, cv in v.items():
|
||||
if cv and not merged.get(ck):
|
||||
merged[ck] = cv
|
||||
club.config_json = merged
|
||||
continue
|
||||
if v and not getattr(club, k, None):
|
||||
setattr(club, k, v)
|
||||
# V3 字段允许用 settings 补全(证书路径等)
|
||||
for field in ('api_v3_key', 'cert_serial_no', 'private_key_path', 'platform_cert_dir', 'pay_app_id'):
|
||||
val = defaults.get(field)
|
||||
if val and not getattr(club, field, None):
|
||||
setattr(club, field, val)
|
||||
club.save()
|
||||
return club, False
|
||||
except Club.DoesNotExist:
|
||||
club = Club.query.create(club_id=CLUB_ID_DEFAULT, **defaults)
|
||||
return club, True
|
||||
|
||||
def _copy_huiyuan_prices(self):
|
||||
from products.models import Huiyuan
|
||||
count = 0
|
||||
for h in Huiyuan.query.all():
|
||||
bankuai_id = getattr(h, 'bankuai_id', None)
|
||||
existing = ClubHuiyuanPrice.query.filter(
|
||||
club_id=CLUB_ID_DEFAULT, huiyuan_id=h.huiyuan_id
|
||||
).first()
|
||||
if existing:
|
||||
continue
|
||||
ClubHuiyuanPrice.query.create(
|
||||
club_id=CLUB_ID_DEFAULT,
|
||||
huiyuan_id=h.huiyuan_id,
|
||||
jiage=h.jiage or Decimal('0'),
|
||||
guanshifc=h.guanshifc or Decimal('0'),
|
||||
zuzhangfc=h.zuzhangfc or Decimal('0'),
|
||||
is_enabled=True,
|
||||
bankuai_id=bankuai_id,
|
||||
)
|
||||
count += 1
|
||||
self.stdout.write(f'club_huiyuan_price 新增 {count} 条')
|
||||
|
||||
def _copy_lilubiao(self):
|
||||
from orders.models import CommissionRate
|
||||
count = 0
|
||||
for row in CommissionRate.query.all():
|
||||
config_type = int(row.Platform or 0)
|
||||
existing = ClubLilubiao.query.filter(
|
||||
club_id=CLUB_ID_DEFAULT, config_type=config_type
|
||||
).first()
|
||||
if existing:
|
||||
continue
|
||||
ClubLilubiao.query.create(
|
||||
club_id=CLUB_ID_DEFAULT,
|
||||
config_type=config_type,
|
||||
lilv=row.Rate or Decimal('0'),
|
||||
remark='',
|
||||
)
|
||||
count += 1
|
||||
self.stdout.write(f'club_lilubiao 新增 {count} 条')
|
||||
|
||||
def _ensure_withdraw_config(self):
|
||||
from config.models import WithdrawConfig, TixianQuotaDefault
|
||||
mode = 1
|
||||
try:
|
||||
wc = WithdrawConfig.query.first()
|
||||
if wc:
|
||||
mode = wc.mode
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
cfg = ClubWithdrawConfig.query.get(club_id=CLUB_ID_DEFAULT)
|
||||
cfg.mode = mode
|
||||
cfg.save(update_fields=['mode'])
|
||||
except ClubWithdrawConfig.DoesNotExist:
|
||||
ClubWithdrawConfig.query.create(club_id=CLUB_ID_DEFAULT, mode=mode)
|
||||
self._copy_tixian_quota()
|
||||
|
||||
def _copy_tixian_quota(self):
|
||||
from config.models import TixianQuotaDefault
|
||||
from jituan.models import ClubTixianQuota
|
||||
count = 0
|
||||
for row in TixianQuotaDefault.query.all():
|
||||
existing = ClubTixianQuota.query.filter(
|
||||
club_id=CLUB_ID_DEFAULT, leixing=row.UserType,
|
||||
).first()
|
||||
if existing:
|
||||
continue
|
||||
ClubTixianQuota.query.create(
|
||||
club_id=CLUB_ID_DEFAULT,
|
||||
leixing=row.UserType,
|
||||
daily_limit=row.default_quota or Decimal('0'),
|
||||
)
|
||||
count += 1
|
||||
self.stdout.write(f'club_tixian_quota 新增 {count} 条')
|
||||
|
||||
def _migrate_openid_bindings(self):
|
||||
migrated = 0
|
||||
users = User.query.filter(OpenID__isnull=False).exclude(OpenID='')
|
||||
for u in users:
|
||||
exists = UserWxOpenid.query.filter(
|
||||
club_id=CLUB_ID_DEFAULT, openid=u.OpenID
|
||||
).exists()
|
||||
if exists:
|
||||
continue
|
||||
UserWxOpenid.query.create(
|
||||
club_id=CLUB_ID_DEFAULT,
|
||||
openid=u.OpenID,
|
||||
yonghuid=u.UserUID,
|
||||
unionid=u.UnionID,
|
||||
)
|
||||
if hasattr(u, 'ClubID') and (not u.ClubID or u.ClubID != CLUB_ID_DEFAULT):
|
||||
u.ClubID = CLUB_ID_DEFAULT
|
||||
u.save(update_fields=['ClubID'])
|
||||
migrated += 1
|
||||
return migrated
|
||||
61
jituan/management/commands/seed_club_xzj.py
Normal file
61
jituan/management/commands/seed_club_xzj.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""一键初始化星之界(xzj)俱乐部(默认不批量给所有客服加任职)。"""
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from jituan.models import Club
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '创建星之界电竞俱乐部(xzj);任职请单独 seed_admin_assignments --phone ...'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--wx-appid', default='wxdefa454152e78a03', help='星之界小程序 appid',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--assignments',
|
||||
action='store_true',
|
||||
help='同时给 --phone 指定客服加 xzj 任职(需配合 --phone)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--phone', type=str, default='', help='与 --assignments 联用:客服手机号',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
wx_appid = (options.get('wx_appid') or '').strip()
|
||||
club_id = 'xzj'
|
||||
name = '星之界电竞'
|
||||
|
||||
if Club.query.filter(club_id=club_id).exists():
|
||||
self.stdout.write(self.style.WARNING(f'俱乐部 {club_id} 已存在,跳过 create_club'))
|
||||
else:
|
||||
self.stdout.write(f'创建俱乐部 {club_id} ...')
|
||||
call_command(
|
||||
'create_club',
|
||||
club_id,
|
||||
name=name,
|
||||
wx_appid=wx_appid,
|
||||
)
|
||||
|
||||
if options.get('assignments'):
|
||||
phone = (options.get('phone') or '').strip()
|
||||
if not phone:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'未指定 --phone,跳过任职。示例:'
|
||||
'python manage.py seed_club_xzj --assignments --phone 你的手机号'
|
||||
))
|
||||
else:
|
||||
call_command(
|
||||
'seed_admin_assignments',
|
||||
club_id=club_id,
|
||||
phone=phone,
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
'未加任职。需要时在后台「数据范围」配置,或:'
|
||||
'python manage.py seed_admin_assignments --phone 手机号 --club-id xzj'
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'星之界 {club_id} 就绪。请在后台「俱乐部配置」检查支付密钥。'
|
||||
))
|
||||
49
jituan/management/commands/seed_gvsdsdk_permissions.py
Normal file
49
jituan/management/commands/seed_gvsdsdk_permissions.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""补齐 gvsdsdk 权限码(可重复执行)。"""
|
||||
import uuid
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
from gvsdsdk.models import Permission
|
||||
|
||||
# (PermCode, PermName, PermDesc, PermCategory)
|
||||
EXTRA_PERMISSIONS = [
|
||||
(
|
||||
'dsks666',
|
||||
'打手接单考试管理',
|
||||
'配置各俱乐部考试开关、抽题规则与题库(小程序配置-打手接单考试)',
|
||||
'小程序配置',
|
||||
),
|
||||
(
|
||||
'sfbq666',
|
||||
'身份装饰标签管理',
|
||||
'管理小程序身份装饰标签(与用户管理-身份装饰标签菜单对应)',
|
||||
'用户管理',
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '写入 gvsdsdk Permission 表缺失的权限码(如 sfbq666)'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
sample = Permission.objects.filter(PermStatus=1).first()
|
||||
tenant_uuid = sample.TenantUUID if sample else uuid.UUID('00000000-0000-0000-0000-000000000001').bytes
|
||||
created = 0
|
||||
for code, name, desc, category in EXTRA_PERMISSIONS:
|
||||
if Permission.objects.filter(PermCode=code).exists():
|
||||
self.stdout.write(f' 已存在: {code}')
|
||||
continue
|
||||
Permission.objects.create(
|
||||
PermUUID=uuid.uuid4().bytes,
|
||||
TenantUUID=tenant_uuid,
|
||||
PermCode=code,
|
||||
PermName=name,
|
||||
PermDesc=desc,
|
||||
PermCategory=category,
|
||||
PermStatus=1,
|
||||
CreateTime=timezone.now(),
|
||||
)
|
||||
created += 1
|
||||
self.stdout.write(self.style.SUCCESS(f' 新增权限: {code} - {name}'))
|
||||
self.stdout.write(self.style.SUCCESS(f'完成,新增 {created} 条权限'))
|
||||
152
jituan/management/commands/seed_kefu_menu.py
Normal file
152
jituan/management/commands/seed_kefu_menu.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""一次性写入客服后台菜单与权限映射。用法: python manage.py seed_kefu_menu"""
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from jituan.models import KefuMenuPage
|
||||
|
||||
# 固定 page_id,与前端 Layout 一致
|
||||
MENU_ROWS = [
|
||||
# 板块与财务
|
||||
{'page_id': 'finance', 'name': '板块与财务', 'path': '', 'parent_id': '', 'sort_order': 10},
|
||||
{'page_id': 'finance.bankuai', 'name': '板块配置', 'path': '/finance/bankuai', 'parent_id': 'finance', 'sort_order': 11,
|
||||
'perm_codes': ['bankuai']},
|
||||
{'page_id': 'finance.data', 'name': '财务数据', 'path': '/finance/data', 'parent_id': 'finance', 'sort_order': 12,
|
||||
'perm_codes': ['caiwu']},
|
||||
# 考核
|
||||
{'page_id': 'assessment', 'name': '考核管理', 'path': '', 'parent_id': '', 'sort_order': 20},
|
||||
{'page_id': 'assessment.examiner', 'name': '考核官管理', 'path': '/assessment/examiner', 'parent_id': 'assessment', 'sort_order': 21,
|
||||
'perm_codes': ['kaohepeizhi']},
|
||||
{'page_id': 'assessment.config', 'name': '考核配置', 'path': '/assessment/config', 'parent_id': 'assessment', 'sort_order': 22,
|
||||
'perm_codes': ['kaohepeizhi']},
|
||||
{'page_id': 'assessment.record', 'name': '考核记录', 'path': '/assessment/record', 'parent_id': 'assessment', 'sort_order': 23,
|
||||
'perm_codes': ['kaohepeizhi']},
|
||||
# 订单
|
||||
{'page_id': 'order', 'name': '订单管理', 'path': '', 'parent_id': '', 'sort_order': 30},
|
||||
{'page_id': 'order.platform', 'name': '平台订单', 'path': '/order/platform', 'parent_id': 'order', 'sort_order': 31,
|
||||
'perm_codes': ['002ab']},
|
||||
{'page_id': 'order.merchant', 'name': '商家订单', 'path': '/order/merchant', 'parent_id': 'order', 'sort_order': 32,
|
||||
'perm_codes': ['002ac']},
|
||||
# 跨平台
|
||||
{'page_id': 'cross-platform', 'name': '跨平台管理', 'path': '', 'parent_id': '', 'sort_order': 40},
|
||||
{'page_id': 'cross-platform.order', 'name': '跨平台订单', 'path': '/cross-order', 'parent_id': 'cross-platform', 'sort_order': 41,
|
||||
'perm_codes': ['003aa']},
|
||||
{'page_id': 'cross-platform.settings', 'name': '跨平台设置', 'path': '/cross-settings', 'parent_id': 'cross-platform', 'sort_order': 42,
|
||||
'perm_codes': ['003aa']},
|
||||
{'page_id': 'cross-platform.data', 'name': '跨平台数据', 'path': '/cross-data', 'parent_id': 'cross-platform', 'sort_order': 43,
|
||||
'perm_codes': ['003aa']},
|
||||
{'page_id': 'cross-platform.pre', 'name': '预结算管理', 'path': '/cross-pre', 'parent_id': 'cross-platform', 'sort_order': 44,
|
||||
'perm_codes': ['003aa']},
|
||||
# 用户
|
||||
{'page_id': 'user', 'name': '用户管理', 'path': '', 'parent_id': '', 'sort_order': 50},
|
||||
{'page_id': 'user.player', 'name': '打手管理', 'path': '/user/player', 'parent_id': 'user', 'sort_order': 51,
|
||||
'perm_codes': ['001aa', '001bb', '001cc', '001dd', '001ee', '001ff', '001gg']},
|
||||
{'page_id': 'user.guanli', 'name': '管事管理', 'path': '/user/guanli', 'parent_id': 'user', 'sort_order': 52,
|
||||
'perm_codes': ['4400a', '4400b', '4400c', '4400d', '4400e', '4400f']},
|
||||
{'page_id': 'user.shangjia', 'name': '商家管理', 'path': '/user/shangjia', 'parent_id': 'user', 'sort_order': 53,
|
||||
'perm_codes': ['1100a', '1100b']},
|
||||
{'page_id': 'user.zuzhang', 'name': '组长管理', 'path': '/user/zuzhang', 'parent_id': 'user', 'sort_order': 54,
|
||||
'perm_codes': ['6600a', '6600b', '6600c', '6600d', '6600e']},
|
||||
{'page_id': 'user.identity-tag', 'name': '身份装饰标签', 'path': '/user/identity-tag', 'parent_id': 'user', 'sort_order': 55,
|
||||
'perm_codes': ['8080a', 'sfbq666']},
|
||||
# 提现
|
||||
{'page_id': 'withdraw', 'name': '提现管理', 'path': '', 'parent_id': '', 'sort_order': 60},
|
||||
{'page_id': 'withdraw.audit', 'name': '提现审核', 'path': '/withdraw/audit', 'parent_id': 'withdraw', 'sort_order': 61,
|
||||
'perm_codes': ['005bb']},
|
||||
{'page_id': 'withdraw.detail', 'name': '提现详情', 'path': '/withdraw/detail', 'parent_id': 'withdraw', 'sort_order': 62,
|
||||
'perm_codes': ['005bb']},
|
||||
{'page_id': 'withdraw.data', 'name': '提现数据', 'path': '/withdraw/data', 'parent_id': 'withdraw', 'sort_order': 63,
|
||||
'perm_codes': ['005bb']},
|
||||
{'page_id': 'withdraw.settings', 'name': '提现设置', 'path': '/withdraw/settings', 'parent_id': 'withdraw', 'sort_order': 64,
|
||||
'perm_codes': ['5500a', '5500b', '5500c']},
|
||||
# 管理员
|
||||
{'page_id': 'admin', 'name': '管理员管理', 'path': '', 'parent_id': '', 'sort_order': 70},
|
||||
{'page_id': 'admin.role', 'name': '角色管理', 'path': '/admin/role', 'parent_id': 'admin', 'sort_order': 71,
|
||||
'require_super': True},
|
||||
{'page_id': 'admin.data-scope', 'name': '数据范围配置', 'path': '/admin/data-scope', 'parent_id': 'admin', 'sort_order': 72,
|
||||
'require_group_manage': True},
|
||||
{'page_id': 'admin.club-config', 'name': '俱乐部密钥配置', 'path': '/admin/club-config', 'parent_id': 'admin', 'sort_order': 73,
|
||||
'require_super': True},
|
||||
{'page_id': 'admin.user', 'name': '后台用户', 'path': '/admin/user', 'parent_id': 'admin', 'sort_order': 74,
|
||||
'require_super': True},
|
||||
{'page_id': 'admin.operation-log', 'name': '操作日志', 'path': '/admin/operation-log', 'parent_id': 'admin', 'sort_order': 75,
|
||||
'perm_codes': ['czrz666']},
|
||||
# 处罚
|
||||
{'page_id': 'punishment', 'name': '处罚管理', 'path': '/punishment', 'parent_id': '', 'sort_order': 80,
|
||||
'perm_codes': ['005aa', '66693a', '66693b', '66693c', '66694c', '99933abs']},
|
||||
{'page_id': 'punishment.detail', 'name': '积分处罚详情', 'path': '/punishment/detail', 'parent_id': 'punishment', 'sort_order': 81,
|
||||
'perm_codes': ['005aa', '66693a', '66693b', '66693c', '66694c', '99933abs']},
|
||||
# 商品
|
||||
{'page_id': 'product', 'name': '商品管理', 'path': '', 'parent_id': '', 'sort_order': 90},
|
||||
{'page_id': 'product.list', 'name': '商品列表', 'path': '/product/list', 'parent_id': 'product', 'sort_order': 91,
|
||||
'perm_codes': ['2200a']},
|
||||
{'page_id': 'product.type-zone', 'name': '商品类型专区管理', 'path': '/product/type-zone', 'parent_id': 'product', 'sort_order': 92,
|
||||
'perm_codes': ['7007a']},
|
||||
{'page_id': 'product.data', 'name': '商品数据分析', 'path': '/product/data', 'parent_id': 'product', 'sort_order': 93,
|
||||
'perm_codes': ['2200a']},
|
||||
# 会员
|
||||
{'page_id': 'member', 'name': '会员管理', 'path': '', 'parent_id': '', 'sort_order': 100},
|
||||
{'page_id': 'member.list', 'name': '会员管理', 'path': '/member/list', 'parent_id': 'member', 'sort_order': 101,
|
||||
'perm_codes': ['3300a']},
|
||||
{'page_id': 'member.data', 'name': '会员数据分析', 'path': '/member/data', 'parent_id': 'member', 'sort_order': 102,
|
||||
'perm_codes': ['huiyuancaiwu']},
|
||||
# 小程序
|
||||
{'page_id': 'miniapp', 'name': '小程序配置', 'path': '', 'parent_id': '', 'sort_order': 110},
|
||||
{'page_id': 'miniapp.carousel', 'name': '轮播图与公告', 'path': '/miniapp/carousel', 'parent_id': 'miniapp', 'sort_order': 111,
|
||||
'perm_codes': ['8080a']},
|
||||
{'page_id': 'miniapp.assets', 'name': '图标与频道', 'path': '/miniapp/assets', 'parent_id': 'miniapp', 'sort_order': 112,
|
||||
'perm_codes': ['8080a']},
|
||||
{'page_id': 'miniapp.popup', 'name': '弹窗公告管理', 'path': '/miniapp/popup-notice', 'parent_id': 'miniapp', 'sort_order': 113,
|
||||
'perm_codes': ['8080a']},
|
||||
{'page_id': 'miniapp.withdraw-settings', 'name': '提现与收款设置', 'path': '/withdraw/settings', 'parent_id': 'miniapp', 'sort_order': 114,
|
||||
'perm_codes': ['5500a', '5500b', '5500c']},
|
||||
{'page_id': 'miniapp.leixing', 'name': '抢单商品类型', 'path': '/miniapp/leixing', 'parent_id': 'miniapp', 'sort_order': 115,
|
||||
'perm_codes': ['8080a']},
|
||||
{'page_id': 'miniapp.kaohe-tags', 'name': '抢单考核标签', 'path': '/miniapp/kaohe-tags', 'parent_id': 'miniapp', 'sort_order': 116,
|
||||
'perm_codes': ['8080a']},
|
||||
{'page_id': 'miniapp.dashou-exam', 'name': '打手接单考试', 'path': '/miniapp/dashou-exam', 'parent_id': 'miniapp', 'sort_order': 117,
|
||||
'perm_codes': ['dsks666', '8080a']},
|
||||
# 店铺
|
||||
{'page_id': 'shop', 'name': '店铺管理', 'path': '', 'parent_id': '', 'sort_order': 120},
|
||||
{'page_id': 'shop.list', 'name': '店铺列表', 'path': '/shop/list', 'parent_id': 'shop', 'sort_order': 121,
|
||||
'perm_codes': ['999a', '999b', '999c', '999d', '999e']},
|
||||
{'page_id': 'shop.withdraw', 'name': '店铺提现申请', 'path': '/shop/withdraw-apply', 'parent_id': 'shop', 'sort_order': 122,
|
||||
'perm_codes': ['999a', '999b', '999c', '999d', '999e']},
|
||||
{'page_id': 'shop.products', 'name': '店铺商品管理', 'path': '/shop/products', 'parent_id': 'shop', 'sort_order': 123,
|
||||
'perm_codes': ['999a', '999b', '999c', '999d', '999e']},
|
||||
# 会话
|
||||
{'page_id': 'chat.agent', 'name': '会话管理', 'path': '/chat/agent', 'parent_id': '', 'sort_order': 130,
|
||||
'perm_codes': ['abca1', 'baac2', 'cabc3', 'cb3a2', 'bcaa4']},
|
||||
]
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '写入客服后台菜单页与权限映射(可重复执行,幂等更新)'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
created, updated = 0, 0
|
||||
for row in MENU_ROWS:
|
||||
defaults = {
|
||||
'name': row['name'],
|
||||
'path': row.get('path', ''),
|
||||
'parent_id': row.get('parent_id', ''),
|
||||
'sort_order': row.get('sort_order', 0),
|
||||
'perm_codes': row.get('perm_codes', []),
|
||||
'require_super': row.get('require_super', False),
|
||||
'require_group_manage': row.get('require_group_manage', False),
|
||||
}
|
||||
existing = KefuMenuPage.query.filter(page_id=row['page_id']).first()
|
||||
if existing:
|
||||
for key, val in defaults.items():
|
||||
setattr(existing, key, val)
|
||||
existing.save()
|
||||
updated += 1
|
||||
else:
|
||||
KefuMenuPage(page_id=row['page_id'], **defaults).save()
|
||||
created += 1
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'kefu_menu_page 种子完成:新增 {created},更新 {updated},共 {len(MENU_ROWS)} 条'
|
||||
))
|
||||
try:
|
||||
from django.core.management import call_command
|
||||
call_command('seed_gvsdsdk_permissions')
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.WARNING(f'seed_gvsdsdk_permissions 跳过: {e}'))
|
||||
55
jituan/management/commands/seed_super_admin.py
Normal file
55
jituan/management/commands/seed_super_admin.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""将指定手机号设为超级管理员(功能权限 + 集团视图)。"""
|
||||
import uuid
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
|
||||
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
|
||||
from jituan.constants import SUPER_ADMIN_PHONES
|
||||
from users.business_models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '将 SUPER_ADMIN_PHONES 设为 IsSuperuser,并绑定「管理员」角色(含 000001)'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
admin_role = Role.objects.filter(RoleName='管理员', RoleStatus=1).first()
|
||||
super_perm = Permission.objects.filter(PermCode='000001', PermStatus=1).first()
|
||||
if admin_role and super_perm:
|
||||
if not RolePermission.objects.filter(
|
||||
RoleUUID=admin_role.RoleUUID, PermUUID=super_perm.PermUUID,
|
||||
).exists():
|
||||
RolePermission.objects.create(
|
||||
RolePermUUID=uuid.uuid4().bytes,
|
||||
RoleUUID=admin_role.RoleUUID,
|
||||
PermUUID=super_perm.PermUUID,
|
||||
CreateTime=timezone.now(),
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS('已为「管理员」角色绑定 000001 权限'))
|
||||
|
||||
updated = 0
|
||||
for phone in SUPER_ADMIN_PHONES:
|
||||
users = list(User.query.filter(Phone=phone))
|
||||
if not users:
|
||||
self.stdout.write(self.style.WARNING(f'未找到手机号 {phone}'))
|
||||
continue
|
||||
for user in users:
|
||||
with transaction.atomic():
|
||||
user.IsSuperuser = True
|
||||
user.IsStaff = True
|
||||
user.save(update_fields=['IsSuperuser', 'IsStaff'])
|
||||
if admin_role and not UserRole.objects.filter(
|
||||
UserUUID=user.UserUUID, RoleUUID=admin_role.RoleUUID,
|
||||
).exists():
|
||||
UserRole.objects.create(
|
||||
UserRoleUUID=uuid.uuid4().bytes,
|
||||
UserUUID=user.UserUUID,
|
||||
RoleUUID=admin_role.RoleUUID,
|
||||
CreateTime=timezone.now(),
|
||||
)
|
||||
updated += 1
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'已设置超管: {phone} (UserUID={user.UserUID})'
|
||||
))
|
||||
self.stdout.write(self.style.SUCCESS(f'完成,更新 {updated} 个账号'))
|
||||
40
jituan/management/commands/seed_szjilu_clubs.py
Normal file
40
jituan/management/commands/seed_szjilu_clubs.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""为每个启用俱乐部初始化 szjilu 收支流水行。"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from config.models import Szjilu
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.models import Club
|
||||
from jituan.services.szjilu_accounting import normalize_szjilu_club_id
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '为每个俱乐部创建 szjilu 收支流水行(已有则跳过)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--club-id',
|
||||
type=str,
|
||||
default='',
|
||||
help='仅处理指定 club_id,默认处理全部启用俱乐部',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
club_id = (options.get('club_id') or '').strip()
|
||||
if club_id:
|
||||
club_ids = [normalize_szjilu_club_id(club_id)]
|
||||
else:
|
||||
club_ids = list(
|
||||
Club.query.filter(status=1).values_list('club_id', flat=True)
|
||||
)
|
||||
if CLUB_ID_DEFAULT not in club_ids:
|
||||
club_ids.append(CLUB_ID_DEFAULT)
|
||||
|
||||
created = 0
|
||||
with transaction.atomic():
|
||||
for cid in club_ids:
|
||||
_, was_created = Szjilu.objects.get_or_create(club_id=cid)
|
||||
if was_created:
|
||||
created += 1
|
||||
self.stdout.write(f'创建 szjilu: club_id={cid}')
|
||||
self.stdout.write(self.style.SUCCESS(f'完成,新建 {created} 条 szjilu 记录'))
|
||||
133
jituan/management/commands/seed_xzj_invite_data.py
Normal file
133
jituan/management/commands/seed_xzj_invite_data.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""星之界(xzj)邀请链种子数据:自动插入组长+管事(含邀请码),无需先登录小程序。"""
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.models import Club, ClubHuiyuanPrice
|
||||
from users.business_models import User
|
||||
from users.models import UserBoss, UserGuanshi, UserZuzhang
|
||||
from utils.invitationcode_utils import CreateInvitationCode
|
||||
|
||||
CLUB_ID = 'xzj'
|
||||
WX_APPID = 'wxdefa454152e78a03'
|
||||
|
||||
# 固定种子账号(可重复执行,幂等)
|
||||
SEED_ZUZHANG = {
|
||||
'UserUID': '9900001',
|
||||
'OpenID': 'xzj_seed_openid_zuzhang_v1',
|
||||
'Phone': '19900000001',
|
||||
'UserName': 'xzj_seed_zuzhang',
|
||||
'nickname': '星之界种子组长',
|
||||
}
|
||||
SEED_GUANSHI = {
|
||||
'UserUID': '9900002',
|
||||
'OpenID': 'xzj_seed_openid_guanshi_v1',
|
||||
'Phone': '19900000002',
|
||||
'UserName': 'xzj_seed_guanshi',
|
||||
'nickname': '星之界种子管事',
|
||||
}
|
||||
|
||||
|
||||
def _ensure_club():
|
||||
if Club.query.filter(club_id=CLUB_ID).exists():
|
||||
return
|
||||
call_command(
|
||||
'create_club',
|
||||
CLUB_ID,
|
||||
name='星之界电竞',
|
||||
wx_appid=WX_APPID,
|
||||
)
|
||||
|
||||
|
||||
def _ensure_user(meta):
|
||||
user = User.query.filter(UserUID=meta['UserUID']).first()
|
||||
if not user:
|
||||
user = User.query.filter(OpenID=meta['OpenID']).first()
|
||||
if user:
|
||||
if user.ClubID != CLUB_ID:
|
||||
user.ClubID = CLUB_ID
|
||||
user.save(update_fields=['ClubID'])
|
||||
return user, False
|
||||
|
||||
user = User.query.create(
|
||||
UserUID=meta['UserUID'],
|
||||
UserName=meta['UserName'],
|
||||
OpenID=meta['OpenID'],
|
||||
Phone=meta['Phone'],
|
||||
ClubID=CLUB_ID,
|
||||
)
|
||||
UserBoss.query.create(user=user, nickname=meta['nickname'])
|
||||
return user, True
|
||||
|
||||
|
||||
def _ensure_zuzhang(user):
|
||||
if hasattr(user, 'ZuzhangProfile'):
|
||||
return user.ZuzhangProfile, False
|
||||
yaoqingma = CreateInvitationCode(str(user.UserUID))
|
||||
z = UserZuzhang.query.create(
|
||||
user=user,
|
||||
yaoqingma=yaoqingma,
|
||||
zhuangtai=1,
|
||||
)
|
||||
return z, True
|
||||
|
||||
|
||||
def _ensure_guanshi(user, zuzhang_uid):
|
||||
if hasattr(user, 'GuanshiProfile'):
|
||||
return user.GuanshiProfile, False
|
||||
yaoqingma = CreateInvitationCode(str(user.UserUID))
|
||||
g = UserGuanshi.query.create(
|
||||
user=user,
|
||||
yaoqingma=yaoqingma,
|
||||
yaoqingren=zuzhang_uid,
|
||||
zhuangtai=1,
|
||||
)
|
||||
return g, True
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '插入星之界种子组长+管事(含邀请码),一条命令即可邀请注册'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--force-new-codes',
|
||||
action='store_true',
|
||||
help='已存在时重新生成邀请码(慎用)',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
with transaction.atomic():
|
||||
_ensure_club()
|
||||
|
||||
zz_user, zz_new = _ensure_user(SEED_ZUZHANG)
|
||||
zz_profile, zz_profile_new = _ensure_zuzhang(zz_user)
|
||||
if options.get('force_new_codes') and not zz_profile_new:
|
||||
zz_profile.yaoqingma = CreateInvitationCode(str(zz_user.UserUID))
|
||||
zz_profile.save(update_fields=['yaoqingma'])
|
||||
|
||||
gs_user, gs_new = _ensure_user(SEED_GUANSHI)
|
||||
gs_profile, gs_profile_new = _ensure_guanshi(gs_user, zz_user.UserUID)
|
||||
if options.get('force_new_codes') and not gs_profile_new:
|
||||
gs_profile.yaoqingma = CreateInvitationCode(str(gs_user.UserUID))
|
||||
gs_profile.save(update_fields=['yaoqingma'])
|
||||
|
||||
price_cnt = ClubHuiyuanPrice.query.filter(club_id=CLUB_ID).count()
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('=' * 60))
|
||||
self.stdout.write(self.style.SUCCESS('星之界 xzj 邀请链种子数据已就绪'))
|
||||
self.stdout.write(self.style.SUCCESS('=' * 60))
|
||||
self.stdout.write(f'club_huiyuan_price 条数: {price_cnt}(与星阙相同则需后台 xzj 视图下单独改价)')
|
||||
self.stdout.write('')
|
||||
self.stdout.write('【组长】管事注册用这个邀请码(guanshizhuce / zuzhangyqmzc):')
|
||||
self.stdout.write(f' UserUID={zz_user.UserUID} Phone={SEED_ZUZHANG["Phone"]}')
|
||||
self.stdout.write(f' 组长邀请码 inviteCode = {zz_profile.yaoqingma}')
|
||||
self.stdout.write('')
|
||||
self.stdout.write('【管事】打手注册用这个邀请码(dashouzhuce):')
|
||||
self.stdout.write(f' UserUID={gs_user.UserUID} Phone={SEED_GUANSHI["Phone"]}')
|
||||
self.stdout.write(f' 管事邀请码 inviteCode = {gs_profile.yaoqingma}')
|
||||
self.stdout.write('')
|
||||
self.stdout.write('新建: 组长=%s 管事=%s' % (
|
||||
'是' if zz_new or zz_profile_new else '否(已存在)',
|
||||
'是' if gs_new or gs_profile_new else '否(已存在)',
|
||||
))
|
||||
self.stdout.write(self.style.SUCCESS('=' * 60))
|
||||
20
jituan/middleware.py
Normal file
20
jituan/middleware.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""请求级俱乐部上下文中间件。"""
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
|
||||
|
||||
|
||||
class ClubContextMiddleware:
|
||||
"""
|
||||
为每个请求注入 club_id / club_scope。
|
||||
旧客户端不传 Header 时 club_id 恒为 xq,不影响现网。
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
request.club_id = resolve_club_id_from_request(request)
|
||||
request.club_scope = resolve_club_scope(request)
|
||||
if not request.club_id:
|
||||
request.club_id = CLUB_ID_DEFAULT
|
||||
return self.get_response(request)
|
||||
183
jituan/migrations/0001_initial.py
Normal file
183
jituan/migrations/0001_initial.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# Generated for jituan multi-club platform
|
||||
|
||||
import gvsdsdk.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Club',
|
||||
fields=[
|
||||
('club_id', models.CharField(max_length=16, primary_key=True, serialize=False, verbose_name='俱乐部ID')),
|
||||
('name', models.CharField(max_length=64, verbose_name='展示名')),
|
||||
('status', models.IntegerField(default=1, verbose_name='1启用0停用')),
|
||||
('wx_appid', models.CharField(blank=True, default='', max_length=32)),
|
||||
('wx_secret', models.CharField(blank=True, default='', max_length=128)),
|
||||
('mch_id', models.CharField(blank=True, default='', max_length=32)),
|
||||
('pay_app_id', models.CharField(blank=True, default='', max_length=32)),
|
||||
('api_v3_key', models.CharField(blank=True, default='', max_length=128)),
|
||||
('cert_serial_no', models.CharField(blank=True, default='', max_length=64)),
|
||||
('private_key_path', models.CharField(blank=True, default='', max_length=255)),
|
||||
('platform_cert_dir', models.CharField(blank=True, default='', max_length=255)),
|
||||
('official_appid', models.CharField(blank=True, default='', max_length=32)),
|
||||
('official_secret', models.CharField(blank=True, default='', max_length=128)),
|
||||
('official_token', models.CharField(blank=True, default='', max_length=64)),
|
||||
('encoding_aes_key', models.CharField(blank=True, default='', max_length=64)),
|
||||
('template_id', models.CharField(blank=True, default='', max_length=64)),
|
||||
('template_max_per_minute', models.IntegerField(default=950)),
|
||||
('h5_domain', models.CharField(blank=True, default='', max_length=128)),
|
||||
('oss_prefix', models.CharField(blank=True, default='', max_length=128)),
|
||||
('goeasy_appkey', models.CharField(blank=True, default='', max_length=64)),
|
||||
('goeasy_secret', models.CharField(blank=True, default='', max_length=128)),
|
||||
('config_json', models.JSONField(blank=True, default=dict)),
|
||||
('company_uuid', models.BinaryField(blank=True, null=True)),
|
||||
('sort_order', models.IntegerField(default=0)),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '俱乐部',
|
||||
'db_table': 'club',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserWxOpenid',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16)),
|
||||
('openid', models.CharField(db_index=True, max_length=64)),
|
||||
('yonghuid', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
|
||||
('unionid', models.CharField(blank=True, max_length=64, null=True)),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'user_wx_openid',
|
||||
'unique_together': {('club_id', 'openid')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AdminAssignment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('yonghuid', models.CharField(db_index=True, max_length=7)),
|
||||
('club_id', models.CharField(blank=True, db_index=True, max_length=16, null=True, verbose_name='NULL=集团任职')),
|
||||
('role_code', models.CharField(default='CLUB_ADMIN', max_length=50)),
|
||||
('data_scope', models.CharField(default='SINGLE_CLUB', max_length=20)),
|
||||
('is_primary', models.BooleanField(default=False)),
|
||||
('granted_by', models.CharField(blank=True, default='', max_length=7)),
|
||||
('status', models.IntegerField(default=1, verbose_name='1有效0停用')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'admin_assignment',
|
||||
'unique_together': {('yonghuid', 'club_id', 'role_code')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AdminAuditLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(blank=True, db_index=True, max_length=16, null=True)),
|
||||
('operator_yonghuid', models.CharField(db_index=True, max_length=7)),
|
||||
('operator_role_code', models.CharField(blank=True, default='', max_length=50)),
|
||||
('target_yonghuid', models.CharField(blank=True, max_length=7, null=True)),
|
||||
('action', models.CharField(db_index=True, max_length=64)),
|
||||
('resource_type', models.CharField(blank=True, default='', max_length=32)),
|
||||
('resource_id', models.CharField(blank=True, default='', max_length=64)),
|
||||
('field_name', models.CharField(blank=True, default='', max_length=64)),
|
||||
('value_before', models.TextField(blank=True, null=True)),
|
||||
('value_after', models.TextField(blank=True, null=True)),
|
||||
('request_ip', models.CharField(blank=True, default='', max_length=64)),
|
||||
('remark', models.CharField(blank=True, default='', max_length=500)),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'admin_audit_log',
|
||||
'indexes': [models.Index(fields=['club_id', '-CreateTime'], name='admin_audit_club_time_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClubHuiyuanPrice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16)),
|
||||
('huiyuan_id', models.CharField(db_index=True, max_length=6)),
|
||||
('jiage', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
|
||||
('guanshifc', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
|
||||
('zuzhangfc', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
|
||||
('is_enabled', models.BooleanField(default=True)),
|
||||
('bankuai_id', models.IntegerField(blank=True, null=True)),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'club_huiyuan_price',
|
||||
'unique_together': {('club_id', 'huiyuan_id')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClubLilubiao',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16)),
|
||||
('config_type', models.IntegerField(db_index=True, verbose_name='同 lilubiao.fadanpingtai')),
|
||||
('lilv', models.DecimalField(decimal_places=4, default=0, max_digits=10)),
|
||||
('remark', models.CharField(blank=True, default='', max_length=255)),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'club_lilubiao',
|
||||
'unique_together': {('club_id', 'config_type')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClubWithdrawConfig',
|
||||
fields=[
|
||||
('club_id', models.CharField(max_length=16, primary_key=True, serialize=False)),
|
||||
('mode', models.IntegerField(default=1, verbose_name='1自动2手动')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'club_withdraw_config',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClubTixianQuota',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16)),
|
||||
('leixing', models.IntegerField(db_index=True)),
|
||||
('daily_limit', models.DecimalField(decimal_places=2, default=0, max_digits=12)),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'club_tixian_quota',
|
||||
'unique_together': {('club_id', 'leixing')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClubFadanFenhongLilv',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16)),
|
||||
('shenfen', models.IntegerField(db_index=True)),
|
||||
('lilv', models.DecimalField(decimal_places=4, default=0, max_digits=10)),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'club_fadan_fenhong_lilv',
|
||||
'unique_together': {('club_id', 'shenfen')},
|
||||
},
|
||||
),
|
||||
]
|
||||
16
jituan/migrations/0002_admin_audit_operator_widen.py
Normal file
16
jituan/migrations/0002_admin_audit_operator_widen.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('jituan', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminauditlog',
|
||||
name='operator_yonghuid',
|
||||
field=models.CharField(db_index=True, max_length=16),
|
||||
),
|
||||
]
|
||||
40
jituan/migrations/0003_role_club_id.py
Normal file
40
jituan/migrations/0003_role_club_id.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Generated manually — Role.club_id + 放宽 tupianpeizhi 唯一约束(关注快手可多图)
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('jituan', '0002_admin_audit_operator_widen'),
|
||||
('config', '0006_display_club_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(
|
||||
sql="""
|
||||
ALTER TABLE Role ADD COLUMN club_id VARCHAR(16) NOT NULL DEFAULT '';
|
||||
CREATE INDEX role_club_id_idx ON Role (club_id);
|
||||
UPDATE Role SET club_id = '' WHERE RoleName = '管理员';
|
||||
UPDATE Role SET club_id = 'xq' WHERE club_id = '' AND RoleName <> '管理员';
|
||||
""",
|
||||
reverse_sql="ALTER TABLE Role DROP COLUMN club_id;",
|
||||
),
|
||||
migrations.RunSQL(
|
||||
sql="""
|
||||
SET @idx_exists := (
|
||||
SELECT COUNT(1) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'tupianpeizhi'
|
||||
AND index_name = 'tupianpeizhi_club_id_ImageType_uniq'
|
||||
);
|
||||
SET @sql := IF(@idx_exists > 0,
|
||||
'ALTER TABLE tupianpeizhi DROP INDEX tupianpeizhi_club_id_ImageType_uniq',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
""",
|
||||
reverse_sql=migrations.RunSQL.noop,
|
||||
),
|
||||
]
|
||||
32
jituan/migrations/0004_kefu_menu_page.py
Normal file
32
jituan/migrations/0004_kefu_menu_page.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Generated manually for kefu menu pages
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('jituan', '0003_role_club_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='KefuMenuPage',
|
||||
fields=[
|
||||
('page_id', models.CharField(max_length=64, primary_key=True, serialize=False, verbose_name='页面ID')),
|
||||
('name', models.CharField(max_length=64, verbose_name='展示名')),
|
||||
('path', models.CharField(blank=True, default='', max_length=128, verbose_name='路由路径,分组为空')),
|
||||
('parent_id', models.CharField(blank=True, default='', db_index=True, max_length=64)),
|
||||
('sort_order', models.IntegerField(default=0)),
|
||||
('perm_codes', models.JSONField(blank=True, default=list, verbose_name='拥有任一权限即可见')),
|
||||
('require_super', models.BooleanField(default=False, verbose_name='仅超级管理000001')),
|
||||
('require_group_manage', models.BooleanField(default=False, verbose_name='仅集团超管任职')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'kefu_menu_page',
|
||||
'ordering': ['sort_order', 'page_id'],
|
||||
},
|
||||
),
|
||||
]
|
||||
56
jituan/migrations/0005_identity_tag.py
Normal file
56
jituan/migrations/0005_identity_tag.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('jituan', '0004_kefu_menu_page'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='IdentityTag',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16)),
|
||||
('name', models.CharField(max_length=64, verbose_name='标签名')),
|
||||
('color', models.CharField(default='#000000', max_length=32, verbose_name='主色')),
|
||||
('flash_enabled', models.BooleanField(default=False, verbose_name='闪光特效')),
|
||||
('texiao_json', models.TextField(blank=True, default='', verbose_name='特效JSON')),
|
||||
('sort_order', models.IntegerField(default=0)),
|
||||
('status', models.IntegerField(default=1, verbose_name='1启用0停用')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'identity_tag',
|
||||
'ordering': ['sort_order', 'id'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IdentityTagBind',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16)),
|
||||
('yonghuid', models.CharField(db_index=True, max_length=7)),
|
||||
('shenfen', models.IntegerField(verbose_name='1打手2管事3商家4组长')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True)),
|
||||
('tag', models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='binds', to='jituan.identitytag',
|
||||
)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'identity_tag_bind',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='identitytagbind',
|
||||
index=models.Index(fields=['club_id', 'yonghuid', 'shenfen'], name='identity_tag_bind_lookup'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='identitytagbind',
|
||||
unique_together={('club_id', 'yonghuid', 'shenfen', 'tag')},
|
||||
),
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user