refactor(gerp): FormService 继承 gvsdsdk FormServiceBase 复用字段工具并委托 code_display
This commit is contained in:
1 parent
010ff46765
commit
187aebe811
2 files changed
+26
-138
No files matched your search
+20
-136
@@ -4,7 +4,6 @@ import re
|
||||
import logging
|
||||
from django.db import connection, transaction, IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.db.models.fields import NOT_PROVIDED
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -18,6 +17,13 @@ from gvsds.apps.gerp.models import (
|
||||
AuditLog,
|
||||
)
|
||||
from gvsds.apps.core.models import User, UserAvatar, User2FA, UserPending2FA
|
||||
from gvsdsdk.form_service import (
|
||||
FormServiceBase,
|
||||
_PascalizeRecord,
|
||||
_BuildFieldMap,
|
||||
_ResolveFieldName,
|
||||
_ConvertValueForField,
|
||||
)
|
||||
from gvsdsdk.order.models import Order, OrderItem, OrderLevel, OrderTemplate, OrderTemplateItem
|
||||
from gvsds.apps.gerp.i18n import translate_field, translate_table
|
||||
|
||||
@@ -87,35 +93,7 @@ def create_tenant_admin_role(tenant):
|
||||
return RoleManager.EnsureTenantAdminRole(tenant.TenantUUID)
|
||||
|
||||
|
||||
def _pascalize_record(record, model=None):
|
||||
result = {}
|
||||
column_map = {}
|
||||
if model is not None:
|
||||
for f in model._meta.get_fields():
|
||||
if hasattr(f, 'db_column') and f.db_column:
|
||||
column_map[f.name] = f.db_column
|
||||
if hasattr(f, 'attname') and f.attname != f.name:
|
||||
column_map[f.attname] = f.db_column
|
||||
for key, val in record.items():
|
||||
out_key = column_map.get(key, key)
|
||||
# UUIDObj 处理:gvsdsdk UUIDField 返回 UUIDObj 对象(非 bytes),
|
||||
# 需转为字符串以便 JSON 序列化,否则前端收不到数据
|
||||
if val is not None and val.__class__.__name__ == 'UUIDObj':
|
||||
val = val.str
|
||||
elif isinstance(val, bytes) and len(val) == 16:
|
||||
try:
|
||||
val = str(uuid.UUID(bytes=val))
|
||||
except ValueError:
|
||||
pass
|
||||
elif isinstance(val, datetime.datetime):
|
||||
val = val.strftime('%Y-%m-%d %H:%M:%S')
|
||||
elif isinstance(val, datetime.date):
|
||||
val = val.isoformat()
|
||||
result[out_key] = val
|
||||
return result
|
||||
|
||||
|
||||
class FormService:
|
||||
class FormService(FormServiceBase):
|
||||
TABLE_MAP = {
|
||||
'User': User,
|
||||
'UserAvatar': UserAvatar,
|
||||
@@ -182,6 +160,7 @@ class FormService:
|
||||
HIDE_PK_UUID_IN_TABLE = {'Company', 'Department', 'Workgroup'}
|
||||
|
||||
def __init__(self, user, current_company_uuid=None, admin_verify=False, current_role_uuid=None, current_tenant_uuid=None, ip_address='', role_scope=None):
|
||||
super().__init__(user=user)
|
||||
self.user = user
|
||||
self.ip_address = ip_address or ''
|
||||
self.role_scope = role_scope
|
||||
@@ -254,14 +233,6 @@ class FormService:
|
||||
from gvsdsdk.auth.admin_service import IsSuperAdmin
|
||||
return IsSuperAdmin(user=user, role_uuid=self.current_role_uuid)
|
||||
|
||||
def _build_field_map(self, model):
|
||||
pascal_map = {}
|
||||
for f in model._meta.get_fields():
|
||||
if not hasattr(f, 'name'):
|
||||
continue
|
||||
pascal_map[f.name] = f
|
||||
return pascal_map
|
||||
|
||||
def _get_entity_picker_fields(self, model):
|
||||
"""获取模型中所有 entityPicker 类型的字段信息"""
|
||||
result = []
|
||||
@@ -435,74 +406,6 @@ class FormService:
|
||||
pass
|
||||
return records
|
||||
|
||||
def _resolve_field_name(self, key, pascal_map):
|
||||
if key in pascal_map:
|
||||
return pascal_map[key].name
|
||||
return None
|
||||
|
||||
def _convert_value_for_field(self, field, val):
|
||||
if val is None or val == '':
|
||||
if getattr(field, 'null', False):
|
||||
return None
|
||||
# 非空字段:数值类型返回默认值或0,其他类型返回空字符串
|
||||
field_type = field.__class__.__name__
|
||||
if field_type in ('SmallIntegerField', 'IntegerField', 'BigIntegerField',
|
||||
'PositiveIntegerField', 'PositiveSmallIntegerField',
|
||||
'FloatField', 'DecimalField'):
|
||||
default = getattr(field, 'default', None)
|
||||
if default is not None and default is not NOT_PROVIDED:
|
||||
return default
|
||||
return 0
|
||||
if field_type == 'BooleanField':
|
||||
return False
|
||||
return val
|
||||
field_type = field.__class__.__name__
|
||||
if field_type == 'UUIDField' and isinstance(val, str):
|
||||
try:
|
||||
return uuid.UUID(val).bytes
|
||||
except ValueError:
|
||||
return val
|
||||
if field_type == 'BinaryField' and isinstance(val, str):
|
||||
try:
|
||||
return uuid.UUID(val).bytes
|
||||
except ValueError:
|
||||
return val
|
||||
if field_type == 'ForeignKey' and isinstance(val, str):
|
||||
try:
|
||||
return uuid.UUID(val).bytes
|
||||
except ValueError:
|
||||
return val
|
||||
if field_type in ('SmallIntegerField', 'IntegerField', 'BigIntegerField',
|
||||
'PositiveIntegerField', 'PositiveSmallIntegerField',
|
||||
'FloatField', 'DecimalField') and isinstance(val, str):
|
||||
try:
|
||||
if field_type == 'FloatField':
|
||||
return float(val)
|
||||
return int(val)
|
||||
except (ValueError, TypeError):
|
||||
return val
|
||||
if field_type == 'DateTimeField' and isinstance(val, str):
|
||||
try:
|
||||
dt = datetime.datetime.fromisoformat(val)
|
||||
# USE_TZ=False + MySQL 不接受 aware datetime:
|
||||
# 统一转 naive UTC(UTC-0 存储,展示时由前端按用户时区转换)
|
||||
if dt.tzinfo is not None:
|
||||
dt = dt.astimezone(datetime.timezone.utc).replace(tzinfo=None)
|
||||
return dt
|
||||
except (ValueError, TypeError):
|
||||
return val
|
||||
if field_type in ('BooleanField', 'NullBooleanField') and isinstance(val, str):
|
||||
low = val.strip().lower()
|
||||
if low in ('true', '1', 't', 'yes'):
|
||||
return True
|
||||
if low in ('false', '0', 'f', 'no'):
|
||||
return False
|
||||
return val
|
||||
max_length = getattr(field, 'max_length', None)
|
||||
if max_length and isinstance(val, str) and len(val) > max_length:
|
||||
val = val[:max_length]
|
||||
return val
|
||||
|
||||
def get_tables(self):
|
||||
tables = []
|
||||
for name, model in self.TABLE_MAP.items():
|
||||
@@ -1110,7 +1013,7 @@ class FormService:
|
||||
f_name = f.attname # e.g. UserUUID_id -> 但实际 attname 是字段名_id
|
||||
val = getattr(obj, f_name, None)
|
||||
row[f.name] = val
|
||||
records.append(_pascalize_record(row, model))
|
||||
records.append(_PascalizeRecord(row, model))
|
||||
# 为 entityPicker 字段附带关联实体的 label
|
||||
if ep_fields:
|
||||
records = self._enrich_entity_labels(obj_list, ep_fields, records)
|
||||
@@ -1183,12 +1086,12 @@ class FormService:
|
||||
# 订单明细由订单统一管理,禁止直接新增(防绕过 OrderManager 破坏金额一致性)
|
||||
if table_name == 'OrderItem':
|
||||
return {'error': '订单明细由订单统一管理,不允许直接新增', 'field': 'Table'}
|
||||
pascal_map = self._build_field_map(model)
|
||||
pascal_map = self._BuildFieldMap(model)
|
||||
create_data = {}
|
||||
for key, val in data.items():
|
||||
if key in ('table', 'action', 'Table', 'Action', 'pk', 'PK', 'uuid', 'UUID'):
|
||||
continue
|
||||
resolved = self._resolve_field_name(key, pascal_map)
|
||||
resolved = self._ResolveFieldName(key, pascal_map)
|
||||
if resolved is None:
|
||||
continue
|
||||
field = pascal_map.get(resolved) or pascal_map.get(key)
|
||||
@@ -1196,7 +1099,7 @@ class FormService:
|
||||
continue
|
||||
if isinstance(val, list) and len(val) == 1:
|
||||
val = val[0]
|
||||
val = self._convert_value_for_field(field, val)
|
||||
val = self._ConvertValueForField(field, val)
|
||||
# 状态字段校验:只允许 STATUS_FIELD_OPTIONS 中的合法状态码(防手动提交非法值)
|
||||
if resolved in self.STATUS_FIELD_OPTIONS:
|
||||
valid_values = [opt['value'] for opt in self.STATUS_FIELD_OPTIONS[resolved]]
|
||||
@@ -1468,7 +1371,7 @@ class FormService:
|
||||
# 使用 pk 字段名显式查找,避免自定义 BinaryField pk 的 lookup 问题
|
||||
pk_field_name = model._meta.pk.name
|
||||
pk_field = model._meta.pk
|
||||
pk_value = self._convert_value_for_field(pk_field, pk)
|
||||
pk_value = self._ConvertValueForField(pk_field, pk)
|
||||
# 构造查询条件:附加租户/公司过滤防止跨域越权
|
||||
lookup = {pk_field_name: pk_value}
|
||||
if table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES:
|
||||
@@ -1511,12 +1414,12 @@ class FormService:
|
||||
old_snapshot = self._get_record_snapshot(obj) if table_name in self.AUDIT_TABLES else None
|
||||
# 工单状态流转:保存修改前的 Status,用于流转判定(时间戳 + 通知)
|
||||
_wo_old_status = getattr(obj, 'Status', None) if table_name == 'WorkOrder' else None
|
||||
pascal_map = self._build_field_map(model)
|
||||
pascal_map = self._BuildFieldMap(model)
|
||||
update_fields = []
|
||||
for key, val in data.items():
|
||||
if key in ('table', 'action', 'pk', 'uuid', 'Table', 'Action', 'PK', 'UUID'):
|
||||
continue
|
||||
resolved = self._resolve_field_name(key, pascal_map)
|
||||
resolved = self._ResolveFieldName(key, pascal_map)
|
||||
if resolved is None:
|
||||
continue
|
||||
if resolved == model._meta.pk.name:
|
||||
@@ -1530,7 +1433,7 @@ class FormService:
|
||||
continue
|
||||
if isinstance(val, list) and len(val) == 1:
|
||||
val = val[0]
|
||||
val = self._convert_value_for_field(field, val)
|
||||
val = self._ConvertValueForField(field, val)
|
||||
# 状态字段校验:只允许 STATUS_FIELD_OPTIONS 中的合法状态码(防手动提交非法值)
|
||||
if resolved in self.STATUS_FIELD_OPTIONS:
|
||||
valid_values = [opt['value'] for opt in self.STATUS_FIELD_OPTIONS[resolved]]
|
||||
@@ -1634,7 +1537,7 @@ class FormService:
|
||||
# 使用 pk 字段名显式查找,避免自定义 BinaryField pk 的 lookup 问题
|
||||
pk_field_name = model._meta.pk.name
|
||||
pk_field = model._meta.pk
|
||||
pk_value = self._convert_value_for_field(pk_field, pk)
|
||||
pk_value = self._ConvertValueForField(pk_field, pk)
|
||||
# 构造查询条件:附加租户/公司过滤防止跨域越权
|
||||
lookup = {pk_field_name: pk_value}
|
||||
if table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES:
|
||||
@@ -1817,24 +1720,5 @@ class FormService:
|
||||
logger.warning('[AuditLog] 记录审计日志失败: table=%s action=%s', table_name, action, exc_info=True)
|
||||
|
||||
def code_display(self, data):
|
||||
table_name = data.get('Table', '')
|
||||
code_field = data.get('CodeField', 'PermCode')
|
||||
name_field = data.get('NameField', 'PermName')
|
||||
model = self.TABLE_MAP.get(table_name)
|
||||
if not model:
|
||||
return []
|
||||
fields = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')}
|
||||
if code_field not in fields or name_field not in fields:
|
||||
return []
|
||||
qs = model.objects.all()
|
||||
keyword = data.get('Keyword', '')
|
||||
if keyword:
|
||||
filter_kw = {f'{code_field}__icontains': keyword}
|
||||
qs = qs.filter(**filter_kw)
|
||||
results = []
|
||||
for obj in qs[:50]:
|
||||
results.append({
|
||||
'code': getattr(obj, code_field, ''),
|
||||
'name': getattr(obj, name_field, ''),
|
||||
})
|
||||
return results
|
||||
# 委托 gvsdsdk FormServiceBase.CodeDisplay(values() 两列查询,更优)
|
||||
return self.CodeDisplay(data)
|
||||
@@ -23,6 +23,7 @@ import datetime
|
||||
import logging
|
||||
from django.db import DatabaseError
|
||||
from django.db.models import Q
|
||||
from django.db.models.fields import NOT_PROVIDED
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -39,7 +40,10 @@ def _PascalizeRecord(record, model=None):
|
||||
column_map[f.attname] = f.db_column
|
||||
for key, val in record.items():
|
||||
out_key = column_map.get(key, key)
|
||||
if isinstance(val, bytes) and len(val) == 16:
|
||||
# UUIDObj 处理:gvsdsdk UUIDField 返回 UUIDObj 对象(非 bytes),需转为字符串
|
||||
if val is not None and val.__class__.__name__ == 'UUIDObj':
|
||||
val = val.str
|
||||
elif isinstance(val, bytes) and len(val) == 16:
|
||||
try:
|
||||
val = str(uuid.UUID(bytes=val))
|
||||
except ValueError:
|
||||
@@ -92,7 +96,7 @@ class FormServiceBase:
|
||||
'PositiveIntegerField', 'PositiveSmallIntegerField',
|
||||
'FloatField', 'DecimalField'):
|
||||
default = getattr(field, 'default', None)
|
||||
if default is not None:
|
||||
if default is not None and default is not NOT_PROVIDED:
|
||||
return default
|
||||
return 0
|
||||
if field_type == 'BooleanField':
|
||||
|
||||
Reference in new issue
Block a user